-
原型对象的概念
ECMAScript中,用户每创建一个新函数,都会根据一组特定的规则给这个函数创建一个prototype属性,它指向这个函数的原型对象,这个原型对象包含了这个函数所有实例共享的属性和方法。这里就解决了构造函数模式所存在的最大的问题。
-
构造函数、原型对象、实例之间的关系
- 构造函数的prototype属性指向原型对象,原型对象的constructor属性指向构造函数;
- 实例的有一个指针指向原型对象,ECMA-262第五版中叫做[[prototype]],在Firefox、chorme、Safari浏览器中,实例有一个__ proto__属性指向原型对象,在其他的实现中,脚本是没有直接访问这个指针的方法;
- 实例和原型对象之间并没有直接联系,但值得注意的是,原型对象的constructor属性也会共享给所有实例,所以,每个实例都可以通过constructor属性访问构造函数,即判断实例的类型;
-
与原型对象相关的方法
- Person.prototype.isPrototypeOf(person1):判断实例的[[prototype]]指针是否指向构造函数的原型对象;
- Object.getPrototypeOf(person1):返回实例的[[prototype]]指针的值,也就是对应构造函数的原型对象,这是ECMAScript5提供的方法,ie9+
- person1.hasOwnProperty():判断属性是否存在于实例中,而不是实例的原型中;
- Object.getOwnPropertyDescriptor(person1,‘name’):当传入的name属性是person1的原型属性时,这样只能得到undefined,此时需要使用Object.getOwnPropertyDescriptor(Person.prototy, ‘name’),直接在原型对象上调用此方法;
- Object.keys():获取对象的可枚举的实例属性;
- Object.getOwnPropertyNames():获取对象的所有实例属性,包括不可枚举的实例属性。
-
与原型对象相关的操作符
- delete:原型对象中的属性对于实例来说,名义上只可读不可改(其实,如果属性的值是基本类型的值,这样说是正确的;当属性的值是引用类型的时候,可以修改原型的属性值,这种修改并不会给实例增加实例属性,只是不能修改该属性指向的地址。这里的原理类似ES6新增的const),当在实例对象中重写了同名属性的值时(person1.name=“s”),并不会改变原型对象上对应属性的值,而是会给实例创建一个新的实例属性。经过这样的操作后,即使我们把这个属性的值设为null,也无法访问到原型中的属性,delete操作符就是用来完全删除实例属性的,这样我们才能重新访问原型中的属性;
- in:in操作符单独使用时,用于判断对象是否拥有某个属性,不管这个属性是实例属性还是原型属性,返回一个Boolean值;还可以使用for-in遍历对象的所有可枚举的属性,若是实例重写了原型中不可枚举的属性,for-in也能遍历到(ie8以及更早版本无法成功遍历)。
-
更改简单的原型语法
为了避免多次书写Person.prototype,我们可以采用更简单的原型语法——直接把包含我们需要的属性的字面量对象赋值给原型对象,这样操作之后,我们依然能通过instanceof判断对象的类型,但是此时原型对象的constructor属性已经指向了Object构造函数;虽然我们可以将constructor手动设置为Person,但同时也让constructor属性变为了可枚举属性,此时又需要重新将constructor设置为不可枚举属性,具体实现如下:
function Person () {} // 使用简单原型语法添加原型属性 Person.prototype = { name: 'nicho', sayname: function () { console.log(this.name) } } // 重设原型对象的constructor属性 Object.defineProperty(Person.prototype, 'constructor', { enumerable: false, value: Person })
通过上面的方式,我们可以解决简单语法带来的constructor属性的问题。但对于下面person1的情况,我们就无能为力了:
function Person () {} // 这里先创建了实例 var person1 = new Person() // 使用简单原型语法添加原型属性,为了更好的说明,这里声明变量存储字面量对象 var proto = { name: 'nicho', sayname: function () { console.log(this.name) } } Person.prototype = proto // 重设原型对象的constructor属性 Object.defineProperty(Person.prototype, 'constructor', { enumerable: false, value: Person }) // 后创建实例则能访问 var person2 = new Person() person2.sayname() // nicho person1.sayname() // Uncaught TypeError: person1.sayname is not a function
person1创建的时候,Person的原型对象还没发生改变,此时person1的[[prototype]]指针指向的最初的原型对象,我们改变了Person.prototype指向的对象后,person1并没有指向新的原型对象proto,也就无法访问新的原型对象上定义的属性和方法了。无论何时,请记住实例的[[prototype]]指针指向的是原型对象,而不是构造函数。
-
原型模式的问题
原型模式省略了给构造函数传参并初始化相关属性的步骤,导致所有的实例默认的属性值都相同;另一个更大的问题则是共享性引起的,当属性的值是引用类型时,会导致一个实例修改了这个属性,会反映到所有的实例中去。为了解决这两个问题,我们可以组合使用构造函数模式和原型模式。
-
组合使用构造函数模式和原型模式
在这个模式中,我们使用构造函数模式定义实例属性,使用原型模式定义方法及其它共享的属性:
function Person (name, height) { // 实例属性定义在构造函数中 this.name = name this.height = height } // 方法和共享的属性定义在原型对象中 Person.prototype = { sayname: function () { console.log(this.name) } } // 重设原型对象的constructor属性 Object.defineProperty(Person.prototype, 'constructor', { enumerable: false, value: Person })
ECMAScript创建对象2 —— 原型模式
最新推荐文章于 2022-08-18 20:18:23 发布