创建对象及原型

  • A+
所属分类:Web前端
摘要

 

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

一、使用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); //true 2 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));//true 2  console.log(Object.getPrototypeOf(person5) === Person.prototype); //true 3  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);//true 2     console.log(person5.hasOwnProperty('name'));//true 3     console.log(person6.hasOwnProperty('name'));//false 4     //创建一个函数判断一个属性到底是存在于对象中,还是存在于其原型中 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);//undefined 10  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

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