写给Java开发者看的JavaScript对象机制

协助面向对象开发者明白关于JavaScript对象机制

本文是以一个熟习OO言语的开发者视角,来诠释JavaScript中的对象。

关于不相识JavaScript 言语,尤其是习惯了OO言语的开发者来讲,由于语法上些许的相似会让人发生心思预期,JavaScript中的原型继续机制和class语法糖是让人迷惑的。

假如你已对prototype机制已有相识,然则由于二者对象机制的庞大(实质)差别,对它和组织函数,实例对象的关联仍有迷惑,本文也许能够解答你的题目。

我们看下面的代码,能够看出和OO言语比拟,语法上也有很大离别:

// 定义一个类
class Foo {
  constructor() {
    this.a = 'a';
  }
}

//实例化对象
const foo = new Foo();

//定义原型的属性
Foo.prototype.b = 'b';

//实例能够接见属性
foo.b // "b"

//修正原型的属性
Foo.prototype.b= 'B';


//实例属性值没有被修正
foo.b // "b"

类已定义了怎样还能修正呢?prototype又是什么?

不存在面向对象

关于熟习了面向对象的开发者而言JS中各种非预期操纵的存在,都是由于JavaScript中基础没有面向对象的观点,只要对象,没有类。

纵然ES6新添了class语法,不意味着JS引入了面向对象,只是原型继续的语法糖。

原型是什么

什么是原型?假如说类是面向对象言语中对象的模版,原型就是 JS中制造对象的模版。

在面向类的言语中,实例化类,就像用模具制造东西一样。实例化一个类就意味着“把类的形状复制到物理对象中”,关于每个新实例来讲都邑反复这个历程。

然则在JavaScript中,并没有相似的复制机制。你不能建立一个类的多个实例,只能建立多个对象,它们[[Prototype]]关联的是同一个对象。

//组织函数
function Foo(){
}
//在函数的原型上增加属性
Foo.prototype.prototypeAttribute0 = {status: 'initial'};

const foo0 = new Foo();
const foo1 = new Foo();
foo0.prototypeAttribute0 === foo1.prototypeAttribute0 //true

对象、组织函数和原型的关联

当我们建立一个新对象的时刻,发生了什么,对象、组织函数和原型究竟什么。

先简朴地归纳综合:

原型用于定义同享的属性和要领。

组织函数用于定义实例属性和要领,仅担任制造对象,与对象不存在直接的援用关联。

我们先不必class语法糖,如许便于读者明白和暴露出他们之间真正的关联。

// 先建立一个组织函数 定义原型的属性和要领
function Foo() {
    this.attribute0 = 'attribute0';
}

当建立了一个函数,就会为该函数建立一个prototype属性,它指向函数原型。

一切的原型对象都邑自动取得一个constructor属性,这个属性的值是指向原型地点的组织函数的指针。

《写给Java开发者看的JavaScript对象机制》

如今定义原型的属性和要领


Foo.prototype.prototypeMethod0 = function() {
    console.log('this is prototypeMethod0');
}

Foo.prototype.prototypeAttribute0 = 'prototypeAttribute0';

好了,如今,新建一个对象,

const foo = new Foo();

foo.attribute0 // "attribute0"
foo.prototypeAttribute0 //"prototypeAttribute0"
foo.prototypeMethod0() // this is prototypeMethod0

它具有自身的实例属性attribute0,而且能够接见在原型上定义的属性和要领,他们之间的援用关联如图所示。

《写给Java开发者看的JavaScript对象机制》

当挪用组织函数建立实例后,该实例的内部会包括一个指针(内部对象),指向组织函数的原型对象。

当读取实例对象的属性时,会在实例中先征采,没有找到,就会去原型链中搜刮,且总是会挑选原型链中最底层的属性举行接见。<!–原型对象自身也能够有原型对象,如许就构成了原型链。关于原型链这里不作过量引见–>

对象的原型能够经由过程__proto__在chrome等浏览器上接见。

__proto__是对象的原型指针,prototype是组织函数所对应的原型指针。

语法糖做了什么

ES6推出了class语法,为定义组织函数和原型增加了便利性和可读性。

class Foo {
    constructor(){
        this.attribute0 = 'attribute0';
    }

    prototypeMethod0(){
        console.log('this is prototypeMethod0')
    }
}

/* 相当于下面的声明*/
function Foo() {
    this.attribute0 = 'attribute0';
}

Foo.prototype.prototypeMethod0 = function() {
    console.log('this is prototypeMethod0')
}

class中的constractor相当于组织函数,而class中的要领相当于原型上的要领。、

值得注意的特征

属性屏障 —— 防止实例对象无意修正原型

看这段代码,思索输出的效果。

class Foo {
    prototypeMethod0(){
        console.log('this is prototypeMethod0')
    }
}

const foo0 = new Foo();
const foo1 = new Foo();

foo0.prototypeMethod0 === foo0.__proto__.prototypeMethod0 // true

foo0.prototypeMethod0 = () => console.log('foo0 method');
foo0.prototypeMethod0(); //??
foo1.prototypeMethod0(); //??
foo0.prototypeMethod0 === foo0.__proto__.prototypeMethod0 // ??

输出的效果是

foo0.prototypeMethod0(); // foo0 method
foo1.prototypeMethod0(); // this is prototypeMethod0
foo0.prototypeMethod0 === foo0.__proto__.prototypeMethod0 // false

我们晓得对象(即便是原型对象),都是运行时的。

建立之初,foo自身没有prototypeMethod0这个属性,接见foo0.prototypeMethod0将会读取foo0.__proto__.prototypeMethod0

直接修正foo0.prototypeMethod0没有转变__proto__上的要领缘由是存在属性屏障

如今的状况是:想要修正foo0.prototypeMethod0prototypeMethod0foo中不存在而在上层(即foo.__proto__中存在),而且这不是一个特别属性(如只读)。

那末会在foo中增加一个新的属性。

这便是为何直接修正却没有影响__proto__的缘由。

<!–更多属性屏障的场景也不做赘述–>

小结

再复习一遍这些定义:

原型用于定义同享的属性和要领。

组织函数用于定义实例属性和要领,仅担任制造对象,与对象不存在直接的援用关联。

__proto__是对象的原型指针,prototype是组织函数的原型指针。

在诠释原型作用的文章或书本中,我们会听到继续如许的术语,实在更正确地,托付关于JavaScript中的对象模子来讲,是一个更适宜的术语。

托付行动意味着某些对象在找不到属性或许要领援用时会把这个要求托付给另一个对象。对象之间的关联不是复制而是托付。

参考

《JavaScript高等程序设计》

《你不晓得的JavaScript》

本文仅供解惑,要在脑壳里构成体系的观点,照样要看书呀。

有疑问迎接人人一同议论。

    原文作者:imirror707
    原文地址: https://segmentfault.com/a/1190000016016319
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞