Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

原型和原型链 #3

Open
Zijue opened this issue Nov 9, 2020 · 0 comments
Open

原型和原型链 #3

Zijue opened this issue Nov 9, 2020 · 0 comments

Comments

@Zijue
Copy link
Owner

Zijue commented Nov 9, 2020

最近学习前端知识看到一个大佬写的 blog 感觉特别的棒,于是在阅读和学习之后打算总结( 😂 )到自己的知识体系中,建议直接看大佬的总结

JavaScript 中除了基础数据类型外的数据类型都是对象(引用类型),没有类(class),为了实现类似继承以便复用代码的能力,JavaScript 采用了原型和原型链的方式。虽然 ES6 提供了 class 关键字构造类,但其实只是语法糖而已,本质上仍然是一个对象。ES6 实现的继承,本质上仍然是基于原型和原型链。

原型、prototype__proto__

首先,我们需要正确的理解原型、prototype、__proto__之间的关系

  • 原型 是一个对象
  • prototype 是函数的一个属性而已,也是一个对象;它和原型没有绝对的关系。JavaScript 里函数也是一种对象,每个对象都有一个原型,但不是所有对象都有 prototype 属性,实际上只有函数才有这个属性
  • 每个对象(实例)都有一个属性 __proto__,指向它的构造函数(constructor)的 prototype 属性
  • 一个对象的原型就是它的构造函数的 prototype 属性的值,因此 __proto__ 也即原型的代名词
  • 对象的 __proto__ 也有自己的 __proto__,层层向上,直到 __proto__null。这种由原型层层链接起来的数据结构为原型链,因为 null 不再有原型,所以原型链的末端是 null

我们通过下面的代码验证一下以上总结

var a = function(){};
var b = [1, 2, 3];

//函数才有 prototype 属性
console.log(a.prototype);  // >> {constructor: ƒ}
//非函数没有 prototype 属性
console.log(b.prototype);  // >> undefined

//a 的构造函数是「Function 函数」,b 的构造函数是「Array 函数」
console.log(a.constructor);  // >> ƒ Function() { [native code] }
console.log(b.constructor);  // >> ƒ Array() { [native code] }

//根据我们上面的总结,a、b 对象的原型(__proto__)分别指向 Function、Array 的 prototype 属性
console.log(a.__proto__ === Function.prototype);  // >> true
console.log(b.__proto__ === Array.prototype);  // >> true

//同时「Function 函数」和「Array 函数」又都是对象,其构造函数是「Object 函数」,所以 a 和 b 的原型的原型都是 Object.prototype
console.log(a.__proto__.__proto__ === Object.prototype);  // >> true
console.log(b.__proto__.__proto__ === Object.prototype);  // >> true

//「Object 函数」作为顶级对象的构造函数,它的实例的原型本身就不再有原型了,因此它原型的 __proto__ 属性为 null
console.log(new Object().__proto__.__proto__);  // >> null
//也即 Object 类型对象,其原型(Object.prototype)的 __proto__ 指向 null
console.log(Object.prototype.__proto__);  // >> null

对象、构造函数和原型三者关系图如下:

原型继承

使用最新的方法 Object.setPrototypeOf 可以很方便地给对象设置原型,这个对象会继承改原型所有属性和方法;
但是,setPrototypeOf 的性能很差,我们应该尽量使用 Object.create() 来为某个对象设置原型

var obj={
    methodA(){
        console.log("coffe");
    }
}

//obj 的原型是 Object.prototype
console.log(obj.__proto__ === Object.prototype);  // >> true

var newObj = Object.create(obj);  //以 obj 为原型创建一个新的对象
//newObj 继承了它的原型对象 obj 的属性和方法
newObj.methodA(); // >> coffe 

原型链的查找机制

当我们访问某个对象的方法或者属性,如果该对象上没有该属性或者方法,JS 引擎就会遍历原型链上的每一个原型对象,在这些原型对象里面查找该属性或方法,直到找到为止,若遍历了整个原型链仍然找不到,则报错

var obj={
    methodA(){
        console.log("coffe");
    }
}

var newObj = Object.create(obj);  //以obj为原型创建一个新的对象
newObj.hasOwnProperty("methodA");  //>> false

上面的代码中,hasOwnProperty 方法并未在 newObj 上定义,也没有在它的原型 obj 上定义,是它原型链上原型 Object.prototype 的方法。其原型链查找顺序如下图所示:

类(class)的 prototype__proto__

ES6之后,增加的 class 本质上是构造函数的语法糖。我们可以通过如下代码进行演示:

class A {
}

typeof A;  // >> "function"
A.prototype;  // >> {constructor: ƒ}

通过上面的代码,可以发现 class 本质上也是函数,类的 prototype 是一个对象,包含有 constructor 属性。这和函数的 prototype 属性表现具有一致性。
而且,类的所有方法都定义在类的 prototype 属性上面。可以看如下代码:

class A {
    constructor() {
        // ...
    }
    toString() {
        // ...
    }
    toValue() {
        // ...
    }
}

console.log(A.prototype);  // {constructor: ƒ, toString: ƒ, toValue: ƒ}

class 作为构造函数的语法糖,同时有 prototype 属性和 __proto__ 属性,因此同时存在两条继承链

  1. 子类的 __proto__ 属性,表示构造函数的继承,总是指向父类
  2. 子类的 prototype 属性的 __proto__ 属性,表示方法的继承,总是指向父类的 prototype 属性
class A {
}

class B extends A {
}

B.__proto__ === A;  //>> true
B.prototype.__proto__ === A.prototype;  //>> true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant