创建对象及原型

一、使用Object构造函数或对象字面量创建对象     缺点:使用同一个接口创建对象,会产生大量的重复性代码。     解决方法:使用工厂模式创建对象。
1 //构造函数2 let obj1 = new Object();3 obj1.name = '我和鸽子有个约会';4 obj1.age = 22;5 //字面量6 let obj2 = {7    name: '我和鸽子有个约会',8    age: 22,9  };
二、使用工厂模式创建对象    核心思想:创建一种函数,用函数来封装以特定接口创建对象的细节。    优点:        createPerson()可以根据接收来的三个参数来构建一个包含所有必要信息的person对象。     可以无数次地调用这个函数,每次都能返回一个包含三个属性和一个方法的对象。     这样就解决了创建多个相似对象,产生大量重复代码的问题。    缺点:    无法识别对象的类型,比如:我们想创建一个person类型(人类)的对象和cat类型(猫类)的对象,    但是我们无法将它们区分开来,因为它们本质上都是通过Object构造函数创建的。    解决方法:构造函数模式
1 function createPerson(name, age, job) { 2         let obj = new Object(); 3         obj.name = name; 4         obj.age = 22; 5         obj.job = job; 6         obj.sayName = function () { 7             console.log(this.name); 8         }; 9         return obj;10     }11 12 let person1 = createPerson('鸽子1', 22, 'programmer');13 let person2 = createPerson('鸽子2', 20, 'student');14 let cat = createPerson('猫', 3, 'Catch mice');
三、构造函数模式  ECMAScript中的构造函数可以用来创建特定类型的对象,像Object、Array这样的原生构造函数,在运行时会自动出现在  执行环境中,所以我们可以直接使用它们创建Object或者Array类型的对象。  当然,我们也可以自定义构造函数,从而定义自定义对象类型的属性和方法。

    自定义构造函数与工厂模式中的函数的不同:        1.没有显示地创建对象;        2.直接将属性和方法赋给了this对象;        3.没有return语句        4.函数名首字母大写,不是硬性要求只是一种规范,目的是为了将构造函数和普通函数区分开来,      因为构造函数也是函数,只不过它可以用来创建对象而已。

    使用构造函数创建对象这一行为,被称为该构造函数的实例化(类的实例),其对象被称为构造函数的实例或实例对象。    构造函数与其它函数的唯一区别就在于调用它们的方式不同。任何函数,只要通过new操作符调用,那它就是构造函数,    如果不通过new操作符来调用,那和普通函数没什么区别。

  注意:        使用构造函数创建对象需要使用 new 操作符。        这种方式调用构造函数实际上会经历以下4个步骤:            1.创建一个空对象;            2.将构造函数的作用域赋给新对象(因此this就指向了这个新对象);            3.执行构造函数中的代码(为这个新对象添加属性);            4.返回新对象。

  缺点:        所有方法都要在每个实例上重新创建一遍。        例如:person3和person4中的sayName()方法,虽然它们同属于Person的实例,但其中的方法是独立的,     也就是说,每实例化一个对象,都会在该对象中创建一个新的方法,这样十分损耗性能。

   解决方法:            1.将函数定义到构造函数外面,这样每个实例对象中的方法都是同一个方法。
1 function Person(name, age, job) { 2       this.name = name; 3       this.age = age; 4       this.job = job; 5       this.sayName = sayName; 6     } 7  function sayName() { 8        console.log(this.name); 9       }10  console.log(person3.sayName === person4.sayName); //true 说明两个实例对象中的方法是同一个
        但这样也有一个缺点:污染命名空间,比如一个构造函数有好多方法,就要定义好多变量,代码多了就        有可能出现重名的情况。            2.使用原型模式
四、原型模式    每个函数都有一个prototype(原型)属性,该属性是一个指针,指向一个对象,而这个对象的用途是包含由构造    函数创建的所有实例对象共享的属性和方法。

    优点:      可以让所有实例对象共享它所包含的属性和方法。换句话说,不必在构造函数中定义实例对象的信息,可以将      这些信息直接添加到原型对象中,这样每个实例对象就能共用一个函数了,并且也不会污染命名空间。
1     function Person() { 2     } 3  4     Person.prototype.name = '鸽子1'; 5     Person.prototype.age = 22; 6     Person.prototype.job = 'programmer'; 7     Person.prototype.sayName = function () { 8         console.log(this.name); //鸽子1 9     };10     let person5 = new Person();11     let person6 = new Person();12     person5.sayName();13     person6.sayName();14     console.log(person5.sayName === person6.sayName); //true
4.1 原型对象        在创建函数的时候,系统会为该函数创建一个prototype指针,这个指针指向的对象被称为原型对象。        每个原型对象中都有一个constructor(构造函数)属性,该属性也是一个指针,指向其对应的构造函数。        每个实例对象中有两个指针: constructor(指向其构造函数) __proto__(指向其构造函数的原型对象)
1 console.log(person5.__proto__ === Person.prototype); //true2 console.log(person5.constructor === Person); //true
两个方法:    A.prototype.isProtypeof(B)        判断实例对象B中是否存在一个指针,指向构造函数A的原型对象,是返回true,否返回false,      因为实例对象中有一个指针指向其构造函数的原型对象,所以我们可以间接的判断B是不是A的实例对象。    Object.getPrototypeOf(obj)    返回实例对象的原型对象
1  console.log(Person.prototype.isPrototypeOf(person5));//true2  console.log(Object.getPrototypeOf(person5) === Person.prototype); //true3  console.log(Object.getPrototypeOf(person5).name); //鸽子1
代码读取实例对象属性的过程:  每当代码读取某个对象的某个属性时,都会先在实例对象中查找该属性,如果查找到,则返回该属性的值;如果没找到,  则去往该对象的原型对象中查找,如果找到了,则返回该属性的值。也就是说,在我们通过person5调用sayName方法  时,会先后执行两次搜索,第一次是在person5对象中,第二次是在person5的原型对象中。当我们通过person6调用  sayName方法时,会重现相同的搜索过程,这正是多个实例对象共享原型对象中保存的属性和方法的基本原理。    注意:    虽然可以通过实例对象访问其原型对象中的值,但是却不能通过实例对象重写原型中的值。如果我们在实例对象中添    加一个属性,并且该属性与实例对象的原型中的一个属性同名,那么该属性就会将原型中的同名属性屏蔽,换句话说,    添加这个属性只会阻止我们访问原型中的那个属性,而不会修改那个属性,因为代码读取对象属性的时候,首先会在    该对象中查找,查找到就不再搜索其原型中的属性了。
1 console.log(person5.name);// 鸽子1  name来自原型对象2 person5.name = '兔子'; //将原型中的name属性屏蔽3 console.log(person5.name);// 兔子  name来自实例对象
in 操作符  语法: 'name' in obj  判断是否可以通过对象obj访问到name属性,可以则返回true,否则返回false,无论是该属性存在于实例中还是  原型中。hasOwnProperty()方法     语法:obj.hasOwnProperty('name')     判断对象obj【自身中】是否含有name属性,有则返回true,无则返回false     该方法用于检测某属性是存在于实例对象中还是原型对象中,只有给定属性存在于实例对象中,才会返回true。
1     console.log('name' in person6);//true2     console.log(person5.hasOwnProperty('name'));//true3     console.log(person6.hasOwnProperty('name'));//false4     //创建一个函数判断一个属性到底是存在于对象中,还是存在于其原型中5     function hasPrototypeProperty(object, name) {6         return !object.hasOwnProperty(name) && name in object;//返回false则表示存在于对象中,返回true表示存在于原型中7     }8 9     hasPrototypeProperty(person5, 'name');//false
4.2.更简单的原型语法    上面例子中,每给原型对象添加一个属性或方法就要敲一遍Person.prototype。    为减少代码量,也为了从视觉上更好地封装原型的功能,更常见的做法是用一个包含所有属性和方法的字面量对    象来重写整个原型对象。
1     function Person() { 2  3     } 4  5     Person.prototype = { 6         name: '鸽子1', 7         age: 22, 8         job: 'programmer', 9         sayName: function () {10             console.log(this.name);11         },12     };13     console.log(Person.prototype.constructor === Person);//false
注意:  上面的代码中,我们将Person.prototype指向一个新的对象,虽然结果相同,但有一个例外:  该原型对象的constructor属性不再指向Person了,而是指向Object。

  这是因为每创建一个函数,都会生成一个prototype属性指向它的原型对象,并且该原型对象有一个constructor  属性指向这个函数,然而,Person.prototype = {},本质上是将prototype属性指向一个新的对象,而这个新对  象的构造函数是Object。  如果constructor的值真的很重要,我们可以手动改回来
1   Person.prototype = { 2         constructor: Person,//手动更改原型对象中的constructor指向 3          name: '鸽子1', 4          age: 22, 5          job: 'programmer', 6          sayName: function () { 7             console.log(this.name); 8          }, 9      };10   console.log(Person.prototype.constructor === Person);//true
4.3 原型的动态性  因为在原型中查找值的过程是一次搜索,因此我们对原型对象的任何修改都能够立即从实例上反映出来,即使是先创  建了实例后修改原型也是如此。  就像下边的例子:    虽然friend实例是在新方法之前创建的,但是它仍能访问到这个新方法,其原因可以归结为实例与原型之间的    松散连接关系。当我们调用friend.sayHello()方法时,首先会在friend对象中搜索sayHello属性,在没    找到的情况下,会继续搜索原型。
1     let friend = new Person();2     Person.prototype.sayHello = function () {3         console.log('hello');4     };5     friend.sayHello();//hello
注意:  尽管可以随时为原型添加属性和方法,并且修改能够立即在对象实例中反映出来,但是如果重写整个原型对象,那么  情况就不一样了。  因为调用构造函数时会为实例添加一个指向最初原型的指针__proto__,而把原型修改为另一个对象就等于切断了  构造函数与原型之间的联系。
1  function Person() { 2     } 3  4  let person7 = new Person(); 5  Person.prototype = { 6       constructor: Person, 7       gugugu: '鸽子', 8   }; 9  console.log(person7.gugugu);//undefined10  console.log(person7.__proto__ === Person.prototype);//false
原型模式的缺点:  1.我们通过上面的例子可以发现,它省略了为构造函数传递初始化参数这一环节,结果所有实例对象在默认情况下都   将取得相同的属性值。  2.原型模式最大的问题是由原型对象共享的特性所导致的。

  原型中所有属性是被多个实例所共享的,这种共享对于函数非常适合,对于那些包含基本数据类型值的属性倒也说的  过去,毕竟,通过在实例上添加一个同名属性,就可以隐藏原型中对应的属性。然而,对于包含引用类型值的属性来说,  这个问题就比较突出了。
1     function Animal() { 2  3     } 4  5     Animal.prototype = { 6         constructor: Animal, 7         name: '鸽子', 8         friends: ['猫咪', '大黄', '二哈'], 9     };10     let animal1 = new Animal();11     let animal2 = new Animal();12     animal1.friends.push('小白');13     console.log(animal1.friends);//['猫咪', '大黄', '二哈','小白']14     console.log(animal2.friends);//['猫咪', '大黄', '二哈','小白']15     console.log(animal1.friends === animal2.friends);//true
从上面的代码中,我们就能看出问题:  当我们修改了animal1.friends引用的数组,向该数组中添加了一个字符串'小白'。由于friends数组存在于  Animal.prototype而非animal1中,所以刚才的修改也会通过animal2.friends(因为animal1.friends  和animal2.friends指向同一个数组)反映出来。  假如我们的初衷就是像这样所有实例共享一个数组,那没什么问题,但是实例一般都是要有属于自己的全部属性的,  所以很少有人会单独使用原型模式。

  解决方法:组合使用构造函数模式和原型模式
五、组合使用构造函数模式和原型模式  创建自定义类型的最常见的方式,就是组合使用构造函数模式和原型模式,构造函数模式用于定义每个实例独立的属  性,而原型模式用于定义方法和共享的属性。

  优点:    1.每个实例都会有自己的一份实例属性副本,但同时又能共享者对方法的引用,最大限度地节省了内存;    2.支持向构造函数传递参数
1     function Animal(name, age, job) { 2         this.name = name; 3         this.age = age; 4         this.job = job; 5         this.friends = ['猫咪', '大黄', '二哈']; 6     } 7  8     Animal.prototype.sayName = function () { 9         console.log(this.name);10     };11     let animal3 = new Animal('鸽子', 3, 'gugugu');12     let animal4 = new Animal('大白鹅', 2, 'gugugu');13     animal3.sayName();14     animal4.sayName();15     animal3.friends.push('小白');16     console.log(animal3.friends);//['猫咪', '大黄', '二哈','小白']17     console.log(animal4.friends);//['猫咪', '大黄', '二哈']18     console.log(animal3.friends === animal4.friends);//false

作者:我和鸽子有个约会
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

(0)

相关推荐