【JS深入学习】02_从原型到原型链

实际应用

我发现在学习某个东西之前,我先要知道它的作用是什么,才学得更深入。所以在描述知识点之前,先举一个例子,表明一下原型和原型链的作用:

// 构造函数
function Person(){}
Person.prototype.name = 'sugarMei';

// 创建实例
let person = new Person();
person.name = '王花花';
console.log(person.name);   // 王花花

delete person.name;
console.log(person.name);  // sugarMei

我们创建了一个构造函数叫做Person,在它的prototype上定义了一个属性name;之后我们使用new Person()创建了一个person实例,在person上定义了属性name

当我们打印person.name时,第一次是显示王花花,之后我们删除了person.name,然后再次读取person.name时,由于person上没有name属性了,这时候应该怎么办?

此时它会沿着原型链去找它的原型上是否存在这个属性,如果存在就会读取,即读取到了Person上的name值,打印了sugarMei

什么是原型

在学习之前我们先学习几个概念:

prototype

person中的name属性不存在时,打印出来的person.name的值就是Person.prototype.name的值

function Person(){}
Person.prototype.name = 'sugarMei';

规定,每一个函数都有一个prototype属性Person.prototype指向了一个对象,在这个对象上面你可以定义或者读取属性或者函数。prototype称为原型

当在person上查找不到name时,就会去找Person的原型对象,即Person.prototype,查找它上的name属性。那么person是通过什么去找Person.prototype呢,答案是__proto__

__proto__

规定,除了null外,每一个JS对象都有一个__proto__属性,这个属性会指向该对象的原型。

console.log(person.__proto__ === Person.prototype);  // true

当在person上找不到name属性时,系统会隐形地通过__proto__找到这个对象的原型Person.prototype,从而找到它上面的name值。

【JS深入学习】02_从原型到原型链

什么是原型链

在上面我们了解了prototype__proto__,如果你看懂了上面的内容,那么就能理解原型链了。

当我们访问一个对象的属性的时候,如果这个属性在对象内部存在,就直接读取,如果不存在,就沿着__proto__找到它的原型,查找这个属性,如果还是不存在,则继续查找原型的原型

这就是原型链了。拿回原先的例子:

// 构造函数
function Person(){}
Person.prototype.name = 'sugarMei';

// 创建实例
let person = new Person();
person.name = '王花花';
console.log(person.name);   // 王花花

delete person.name;
console.log(person.name);  // sugarMei
  • 第一次访问name属性时,由于person上存在该属性,则直接读取,打印的值是王花花
  • 删除person上的name属性;
  • 第二次访问name属性时,由于person上不存在该属性,则系统隐性地通过__proto__查找这个对象的原型,即Person.prototype,它存在着name属性,则打印出了sugarMei

原型链的顶层

前面我们说过Person.prototype指向了一个对象,即可以说原型也是一个对象。又说道:除了null外,每一个JS对象都有一个__proto__属性。那么Person.prototype这个对象也拥有了__proto__这个属性。

由于所有对象(除了null)都是可以由new Object()创建,故所有对象的原型链顶层就指向了Object。即Person.prototype.__proto__ === Object.prototype

// person.__proto__ 指向了构造函数的prototype
console.log(person.__proto__ === Person.prototype);    // true

// Person.prototype.__proto__指向了构造函数的prototype
console.log(Person.prototype.__proto__ === Object.prototype); // true

如果上面的例子修改为这样的:

// 构造函数
function Person(){}
// Person.prototype.name = 'sugarMei';

// 创建实例
let person = new Person();
person.name = '王花花';
console.log(person.name);   // 王花花

delete person.name;
console.log(person.name);  // undefined

那么访问的顺序应该是这样的:

  • 第一次访问name属性时,由于person上存在该属性,则直接读取,打印的值是王花花
  • 删除person上的name属性;
  • 第二次访问name属性时,由于person上不存在该属性,则系统隐性地通过__proto__查找这个对象的原型,即Person.prototype,它不存在着name属性,系统隐性地通过__proto__查找这个对象的原型,即使用Person.prototype.__proto__
  • Person.prototype.__proto__指向的是Object.prototype,由于Object.prototype.name不存在,然后查找Object.prototype.__proto__,由于
console.log(Object.prototype.__proto__ === null);

​ 即Object.prototype__proto__没有原型,查找属性就到这里为止了,输出undefined

【JS深入学习】02_从原型到原型链
(这里蓝色的线条即为原型链)

constructor

在上面的图我们发现:

function Person(){};
console.log(Person.prototype.constructor === Person); // true

规定,每个原型都有一个constructor属性指向关联的构造函数。

Object.getPrototypeOf()

其实我们现在很少推荐用__proto__来获得原型,会使用Object.getPrototypeOf():

console.log(person.__proto__ === Person.prototype); // true
console.log(Object.getPrototypeOf(person) === Person.prototype); // true

实践

在学习JS的时候,我们会通过instanceof来判断是不是一个引用类型:

let arr = new Array();
console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true

再结合我们学的知识:

let arr = new Array();
console.log(arr.__proto__ === String.prototype); // false
console.log(arr.__proto__ === Array.prototype); // true
console.log(Array.prototype.__proto__ === Object.prototype); //true
console.log(arr.__proto__.__proto__ === Object.prototype); // true

console.log(Object.prototype.__proto__ === null); // true
console.log(arr.__proto__.__proto__.__proto__ === null); // true

可以来手写一下instanceof的实现方法:


function myInstanceof(left, right){
    while(true){
        if(left.__proto__ === right.prototype){
            return true;
        }else if(left.__proto__ === null){
            return false;
        }else {
            left = left.__proto__;
        }
    }
}

let arr = new Array();
console.log(myInstanceof(arr, String));  // false
console.log(myInstanceof(arr, Array));   // true
console.log(myInstanceof(arr, Object));  // true

总结

  • 每一个构造函数都有prototype属性,这个属性我们称为原型;
  • 每一个对象都有一个__proto__属性,这个属性指向了这个对象的构造函数的原型对象,即person.__proto__ === Person.prototype
  • 原型对象即对象,是对象就有__proto__属性,即存在Person.prototype.__proto__
  • 在访问一个对象的属性时,如果不存在就顺着原型链去它的原型对象上找,一直找到这个属性或者其没有原型对象为止;
  • 一般不使用__proto__来访问原型,而使用Object.getPrototypeOf()

参考

JavaScript深入之从原型到原型链

上一篇:轻松理解JS中的面向对象,顺便搞懂prototype和__proto__


下一篇:原型链