资讯专栏INFORMATION COLUMN

JavaScript继承背后的场景-prototype,__proto__, [[prototype

邹立鹏 / 2706人阅读

摘要:实例可以通过代理来找到它,并用来检测其构造函数。经典继承图这也是通过构造函数来创建对象,但是在这一系列的对象和实例之间我们的焦点是放在原型链上。尽管,但构造函数的属性并不是对象自己的属性,它实际上是通过寻找原型链获得的,即所指向的地方。

继承是面向对象编程语言的一大核心功能点,虽然JavaScript并不是一门真正意义上的面向对象的编程语言,但也通过某种手段实现了继承这一功能,最常见的JavaScript继承是通过原型链去实现,这就涉及到了prototype、__proto__、[[prototype]]三者之间的关系了。


如上图所示,理解JavaScript中的继承的关键是要理解母鸡如何产蛋的过程。

[[prototype]]、__proto__、prototype三者之间的联系

每个对象都可以有另一个对象作为其原型。然后前一个对象继承了它的所有原型属性。对象通过内部属性[[Prototype]]指定其原型。由[[Prototype]]属性连接的对象链称为原型链。


为了理解基于原型的继承是怎么样的,我们先来看一个例子。

var proto = {
    describe: function () {
        return "name: "+this.name;
    }
};
var obj = {
    [[Prototype]]: proto,
    name: "obj"
};

> obj.describe
[Function]

> obj.describe()
"name: obj"

__proto__是Object.prototype对象的访问者属性。它暴露了访问它的对象的内部原型([[Prototype]])

function Foo(name) {
    this.name = name
    this.whoAmI = function () {
        return this.name
    }
}
var b = new Foo("b")
var a = new Foo("a")
b.say = function () {
    console.log(`Hi from ${this.whoAmI()}`)
}
console.log(a.__proto__ === Foo.prototype); // true
console.log(a.__proto__ === b.__proto__); // true


JavaScript引擎在对象b上添加了一个say方法,而不是在Foo原型对象(Foo.prototype)上

正如上图中看到的那样,a.__ proto__暴露了指向Foo.prototype对象的[[Prototype]]。同样,b.__ proto__也指向与a.__ proto__相同的对象。

通过构造函数来创建对象

除了通过指定模式创建对象之外,构造函数还可以执行另一个有用的操作 - 它会自动为新创建的对象设置原型对象。此原型对象存储在构造函数的原型对象属性中。

我们可以使用构造函数用b对象重写前面的例子。因此,对象a(原型)Foo.prototype的作用:

创建拥有原型对象属性x及方法calculate()的一个Foo对象。

function Foo(y) {
  this.y = y;
}
Foo.prototype.x = 10;
Foo.prototype.calculate = function (z) {
  return this.x + this.y + z;
};

使用对象Foo创建一个b对象实例。

var b = new Foo(20);
b.calculate(30); // 60
console.log(
  b.__proto__ === Foo.prototype, // true
  b.__proto__.calculate === Foo.prototype.calculate // true
  b.__proto__.calculate === b.calculate, // true
  Foo === b.constructor, // true
  Foo === Foo.prototype.constructor, // true
);

正如上面打印出来的信息,对象b从Foo()继承了方法calculate。“Foo.prototype”自动创建一个特殊属性“constructor”,它是对构造函数本身的引用。实例b可以通过代理来找到它,并用来检测其构造函数。

JavaScript经典继承图

这也是通过构造函数来创建对象,但是在这一系列的对象和实例之间我们的焦点是放在原型链上。原型对象其实也是普通的对象,也有属于它们自己的属性。如果原型具有对其原型的非空引用,依此类推,则称为原型链。

以下是JavaScript经典继承的图表。构造函数Foo只是虚构类的类名。 foo对象是Foo的一个实例。


现在我们可以从图中看到为什么当我们从Dog类继承Animal 时,我们会这样做:

function Dog() {} // the usual constructor function
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;

当通过new操作符创建一个实例的时候,都发生了些什么:

注意Foo.prototype的原型(prototype)并不是来自原型链。Foo.prototype指向原型链中的某些位置,但Foo的这个原型属性不是来自原型链。构成原型链的是proto指向链,以及proto指向的对象。比如从foo .__ proto__,链接到到foo .__ proto . proto__,等等,直到达到null(也就是咱们常说的原型链的顶部是null)。

proto and prototype的关系:

JavaScript的经典继承就像这样:我是一个构造函数,我只是一个函数,我持有一个原型引用,每当调用foo = new Foo()时,我会让foo .__ proto__指向我的原型对象。所以,Foo.prototype 与 obj.__proto__是两个不同的概念。Foo.prototype指示,当创建Foo的对象时,这是新对象的原型链应该指向的点 - 也就是说,foo .__ proto__应该指向Foo.prototype指向的位置。

如果需要添加一个方法怎么办?

如果woofie这个对象没有movie方法,它将通过原型链去寻找,就像任何原型继承场景一样,首先这个对象通过woofie.__proto__,它与Dog.prototype所指的对象相同。如果move方法不是这个对象的一个属性(意味着Dog这个类并没有move这个方法),则在原型链中上升一级(去原型链上寻找),即woofie .__ proto . proto__,或者与Animal.prototype相同。

Animal.prototype.move = function() { ... };


*尽管foo.constructor === Foo,但构造函数的属性并不是foo对象自己的属性,它实际上是通过寻找原型链获得的,即foo .__ proto__所指向的地方。对Function.constructor也是一样。

当我们看到Constructor.prototype,foo .__ proto__,Foo.prototype.constructor时,该图可能很复杂,有时会令人困惑。

要验证图表,请注意,即使foo.constructor将显示一个值,foo.constructor也不是foo自己的属性,而是通过跟踪原型链获得的,因为可以沟通过foo.hasOwnProperty(“constructor”)来进行验证。
*

总结

[[Prototype]]:对象通过其内部属性指定的原型对象

proto :创建的对象实例所拥有的内部属性,在语言层面可以直接访问[[Prototype]]

prototype:prototype是在使用new操作符创建对象时用于构建__proto__的对象,在实例化的对象上(或其他对象)不可使用,仅在构造函数上使用,因为它是从Funtion和Object上复制的。而__proto__随处可用

    ( new Foo ).__proto__ === Foo.prototype  //true
    ( new Foo ).prototype === undefined      //true
    
参考资料

http://speakingjs.com/es5/ind...

http://www.javascripttutorial...

http://stackoverflow.com/ques...

http://dmitrysoshnikov.com/ec...

https://www.quora.com/What-is...

http://www.jisaacks.com/proto...

http://kenneth-kin-lum.blogsp...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/105828.html

相关文章

  • 深入理解 JavaScript class

    摘要:在规范中,引入了的概念。使用中的声明一个类,是非常简单的事。中面向对象实例化的背后原理,实际上就是原型对象。与区别理解上述原理后,还需要注意与属性的区别。实际上,在中,类继承的本质依旧是原型对象。 在 ES6 规范中,引入了 class 的概念。使得 JS 开发者终于告别了,直接使用原型对象模仿面向对象中的类和类继承时代。 但是JS 中并没有一个真正的 class 原始类型, clas...

    Vicky 评论0 收藏0
  • Lodash 严重安全漏洞背后 你不得不知道 JavaScript 知识

    摘要:可能有信息敏感的同学已经了解到库爆出严重安全漏洞,波及万项目。以此为例,可见这次漏洞算是比较严重了。此外,冻结一个对象后该对象的原型也不能被修改。 摘要: 详解原型污染。 原文:Lodash 严重安全漏洞背后 你不得不知道的 JavaScript 知识 作者:Lucas HC Fundebug经授权转载,版权归原作者所有。 可能有信息敏感的同学已经了解到:Lodash 库爆出严...

    YorkChen 评论0 收藏0
  • 最新:Lodash 严重安全漏洞背后你不得不知道 JavaScript 知识

    摘要:可能有信息敏感的同学已经了解到库爆出严重安全漏洞,波及万项目。以此为例,可见这次漏洞算是比较严重了。此外,冻结一个对象后该对象的原型也不能被修改。使用数据结构,不会存在原型污染状况。 可能有信息敏感的同学已经了解到:Lodash 库爆出严重安全漏洞,波及 400万+ 项目。这个漏洞使得 lodash 连夜发版以解决潜在问题,并强烈建议开发者升级版本。 我们在忙着看热闹或者升级版本的同时...

    Integ 评论0 收藏0
  • 深入 JavaScript 原型继承原理——babel 编译码解读

    摘要:目录无继承简单的字段声明无继承简单的方法声明简单继承一层继承字段覆盖无继承静态函数无继承静态变量神秘的类无继承简单的字段声明先来看个最简单的例子,我们仅仅使用了关键字并定义了一个变量最后编译出来的代码如下。无继承静态变量还有个小例子。 在[上一篇文章][]中,我们提到 ES6 的 class 语法糖是个近乎完美的方案,并且讲解了实现继承的许多内部机制,如 prototype/__pro...

    stdying 评论0 收藏0
  • js继承从入门到理解

    摘要:问题修改实例的,即修改了构造函数的原型对象的共享属性到此处,涉及到的内容大家可以再回头捋一遍,理解了就会觉得醍醐灌顶。 开场白 大三下学期结束时候,一个人跑到帝都来参加各厂的面试,免不了的面试过程中经常被问到的问题就是JS中如何实现继承,当时的自己也是背熟了实现继承的各种方法,回过头来想想却不知道__proto__是什么,prototype是什么,以及各种继承方法的优点和缺点,想必有好...

    不知名网友 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<