js 中的原型

127 阅读4分钟
  1. 理解原型对象

无论什么时候,只要新建一个函数,就会根据特定规则为函数创建一个prototype 属性,这个属性指向这个函数的原型对象。所有原型对象都会自动获得一个constructor 属性,这个属性指向prototype属性所在的函数。

创建来自定义的构造函数后,其原型对象默认只会取得constructor属性,至于其它方法,都是从 Object 继承而来。

调用构造函数创建实例后,实例的内部会有一个指针指向该函数的原型对象。ECMA-262第5版中将这个指针叫做 [[ Prototype ]] ,虽然在脚本中没有标准的方式访问这个指针,但 Firefox 、Safari、chrome在每个对象上都支持一个属性 _proto_。 很重要的一点就是,这个连接存在与实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间

function Person() {}

Person.prototype.name = "张三";
Person.prototype.age = 24;
Person.prototype.job = "chinese";
Person.prototype.sayName = function () {
    console.log(this.name);
}

let person1 = new Person();

let person2 = new Person();

console.log(Person.prototype.isPrototypeOf(person1)); // true

虽然在所有实现中都无法访问到 [[ Prototype ]] ,但可以通过 isPrototypeOf() 方法来确定对象之间是否有这种关系。如果 [[ Prototype ]] 指向调用 isPrototypeOf()方法的对象,那么这个方法就返回 true。

ECMAscript 5 新增加了一个方法Object.getPrototypeOf()。这个方法返回 [[ Prototype ]] 的值。

console.log(Object.getPrototypeOf(person1) === Person.prototype); // true
console.log(Object.getPrototypeOf(person1).name); // 张三

使用 Object.getPrototypeOf()可以很方便的取得一个对象的原型,这在利用原型实现继承的情况下是非常重要的。

当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性,搜索首先从对象实例本身开始,如果在实例中找到了这个属性,就返回属性值,如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找这个属性,找到后就返回,找不到就报错。这就是多个对象实例共享原型所保存的属性和方法的基本原理。

虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。如果在实例中添加一个跟原型对象中的属性同名的属性,那么这个属性就会添加到实例上,并且该属性会屏蔽原型中的那个属性。

function Person() {}

Person.prototype.name = "张三";
Person.prototype.age = 24;
Person.prototype.job = "chinese";
Person.prototype.brother = ['李四', '王五'];
Person.prototype.sayName = function () {
    console.log(this.name);
}

let person1 = new Person();

let person2 = new Person();

person2.brother = ['李四', '马六'];
console.log(person1.brother); // [ '李四', '王五' ]
console.log(person2.brother); //[ '李四', '马六' ]

上面的例子中,我们为 person2 添加了一个 brother 属性,而这个属性与原型对象的属性 brother 同名,这样当我们访问person2的 brother 属性时,就会返回添加在 person2 实例上的 brother 属性。而原型中的 brother 属性也并没有被重写。

使用 hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中。这个方法只在给定属性存在于实例中时,才会返回 true。

function Person() {}

Person.prototype.name = "张三";
Person.prototype.age = 24;
Person.prototype.job = "chinese";
Person.prototype.brother = ['李四', '王五'];
Person.prototype.sayName = function () {
    console.log(this.name);
}

let person1 = new Person();

let person2 = new Person();

person1.name = '李斯特';
console.log(person1.hasOwnProperty('name')); // true
console.log(person2.hasOwnProperty('name')); // false
  1. 原型与 in 操作符

有两种方式使用 in 操作符,单独使用 和 在 for-in中使用。单独使用时,in 操作符会在通过对象能够访问给定属性时返回 true;无论该属性是存在于实例中,还是存在于原型中。

利用 hasOwnProperty() 方法和 in 操作符就可以判断属性是存在于实例上,还是存在于原型中。

function testPropertyOf (object, name) {
    if (!(name in object)) {
        console.log('不存在这个属性');
    } else if (!object.hasOwnProperty(name) && (name in object)) {
        console.log('这个属性存在于原型上');
    } else if (object.hasOwnProperty(name) && (name in object)){
        console.log('这个属性存在于实例中');
    }
}

在使用 for-in 循环时,返回的是所有能够通过对象访问的、可枚举的( 内部属性 enumerable 为 true)属性,既包括实例中的属性,也包括原型中的属性。

要取得对象上可枚举的实例属性,可以使用 Object.keys()方法,这个方法接受一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。

function Person() {}

Person.prototype.name = "张三";
Person.prototype.age = 24;
Person.prototype.job = "chinese";
Person.prototype.brother = ['李四', '王五'];
Person.prototype.sayName = function () {
    console.log(this.name);
}

let person1 = new Person();

let person2 = new Person();

person1.name = '李斯特';

let tempArr = [];
for (var keyName in person1) {
    tempArr.push(keyName);
}
let arr2 = Object.keys(person1);
console.log(tempArr);// [ 'name', 'age', 'job', 'brother', 'sayName' ]

console.log(arr2); // [ 'name' ]

如果想得到所有属性,不论是否可以枚举,可以使用Object.getOwnPropertyNames() 方法。