乐趣区

关于javascript:JavaScript-之原型原型链

前言

其余编程语言如 Java 等应用 new 命令时,都会调用“”的构造函数。然而,JavaScript 没有“”,自身并不提供一个 class 实现(尽管在 ES6 中提供了 class 关键字,但其只是语法糖,JavaScript 依然是基于原型的)。于是,JavaScript 作了一个简化的思维,new 命令前面跟的不是类,而是构造函数,用构造函数生成实例对象,但其毛病是无奈共享属性和办法。于是,就为构造函数设置了一个 prototype 属性,这个属性蕴含一个对象(prototype 对象)。所有实例对象须要共享的属性和办法都放在这个对象里,那些不须要共享的属性和办法就放在构造函数里。

💡舒适提醒:本文全文 1986 个字,举荐浏览工夫为 10m,加油老铁!

一、显式原型(prototype)

1.1 介绍

每个函数在创立之后都会有一个名为prototype 属性:

function Parent() {}
Parent.prototype.name = 'kite';
let child = new Parent();
console.log(child.name);

这个属性指向函数的原型对象(通过 Function.prototype.bind 办法结构进去的函数是个例外,它没有 prototype 属性),即调用构造函数创立的实例的原型,也就是例子中 child 的原型。

何为原型:每一个 JavaScript 对象(null 除外)都会和另一个对象相关联,这个对象就是原型,下面也提到每个对象都会从原型中“继承属性”。

原型表明了构造函数和实例原型的关系

1.2 作用

显式原型用来实现基于原型的继承与属性的共享。

二、隐式原型(__proto__)

2.1 介绍

JS 中任意对象都具备一个内置属性 [[prototype]],在 ES5 之前没有规范办法拜访,大多数浏览器通过__proto__ 来拜访。ES5 中有了对这个内置属性规范的 get 办法:Object.getPrototypeOfObject.prototype 是个例外,它的__proto__null)。

function Parent() {}
let child = new Parent();
console.log(child.__proto__ === Parent.prototype); // true

2.2 作用

隐式原型形成原型链,同样用于实现基于原型的继承。举例来说:
当咱们拜访对象中的某个属性时,如果在对象中找不到,就会始终沿着__proto__(原型的原型)顺次查找,直到找到最顶层为止。

function Parent() {}
Parent.prototype.name = 'dave';
let child = new Parent();
child.name = 'kite';
console.log(child.name); // 'kite'
delete child.name;
console.log(child.name); // 'dave'

下面的例子中,给对象 child 增加 name 属性,当拜访 name 属性时,找到了对象自身的属性值 kite。删除 name 属性之后,再次拜访 name 属性,在对象中找不到该属性,再在原型中寻找,找到dave。假如属性在原型中也没有找到该属性,则会再去原型的原型中查找。

咱们晓得原型是个对象,既然是对象就能够用最原始的形式创立:

let obj = new Object();

原型就是通过Object 结构对象生成的。那 Object.prototype 的原型又是什么?

Object.prototype.__proto__ = null; // true

null示意没有对象,表明在此处能够进行查找了。

2.3 指向

__proto__ 指向创立这个对象的函数的显式原型,其关键在于找到创立这个对象的构造函数。创建对象有三种模式:对象字面量;newclass);ES5 的Object.create()。实质只有一种new

2.3.1 对象字面量

let obj = {name: 'ctt'}

对象字面量申明的对象继承自 Object,和 new Object一样,其原型为
Object.prototype。而 Object.prototype 不继承任何属性和办法。

2.3.2 new

应用构造函数创立的对象,它的属性继承自构造函数。
具体分以下几种状况:

  1. 内建对象
    Array(),它继承于 Array.prototype Array.prototype 为一个对象,这个对象由 Object() 这个构建函数创立。因而,Array.prototype.__proto__ === Object.prototype,原型链为:Array.prototype -> Object.prototype -> null
  2. 自定义对象
  3. 默认状况下:

    function Foo() {};
    let foo = new Foo();
    Foo.prototype.__proto__ === Object.prototype;
  • 其余状况:

    // 想让 Foo 继承 Bar
    function Bar() {}
    Foo.prototype = new Bar();
    Foo.prototype.__prototype__ = Bar.prototype;
    // 从新定义 Foo.prototype
    Foo.prototype = {
      a: 1,
      b: 2
    };
    Foo.prototype.__proto__ = Object.prototype;

以上两种状况改写了 Foo.prototype,所以Foo.prototype.constructor 跟着扭转,constructor和原构造函数 Foo切断了分割。

  1. 构造函数
    构造函数就是 Function() 的实例,因而构造函数的隐式原型指向
    Function.prototype 。引擎创立了Object.prototype,而后又创立了
    Function.prototype,通过__proto__ 将两者分割起来。

Function.prototype === Function.__proto__,其余所有的构造函数都能够通过原型链找到 Function.prototype,并且 function Function() 实质也是一个函数,为了不产生凌乱,将 function Function__proto__ 分割到了Function.prototype 上。

2.3.3 class

在 ES5 中,每个对象都有一个 __proto__ 属性,指向对应构造函数的 prototype 属性,而 class 作为构造函数的语法糖,同时具备 prototype 属性和 __proto__ 属性,所以同时存在两条继承链。

  • 子类的 __proto__ 示意构造函数的继承,总是指向父类;
  • 子类的 prototype 属性的 __proto__ 示意办法的继承,总是指向父类的
    prototype
class Parent {

}
class Child extends Parent {

}
Child.__proto__ === Parent;
Child.prototype.__proto__ === Parent.prototype;

作为一个对象,子类 Child 的原型(__proto__)是父类 Parent ;作为一个构造函数,子类的原型对象(prototype)是父类原型对象(prototype)的实例。

2.3.4 Object.create()

Object.create() 是 ES5 的办法,能够调用这个办法来创立一个新对象。新对象的原型就是传入的第一个参数。

三、构造函数(constructor)

下面阐明构造函数和实例都能够指向原型,接下来讲的这个属性就是让原型指向构造函数的(没有指向实例的属性,因为构造函数能够生成多个实例),它就是 constructor 属性,每个原型都有一个 constructor 属性指向构造函数。

function Parent() {}
console.log(Parent === Parent.prototype.constructor);

对于原型的各类关系总结如图:

四、this 和 prototype 定义方法的区别

  1. 利用 this 实现的办法,能够拜访类中的公有变量和公有办法。而利用原型对象实现的办法,无法访问类中的公有变量和办法。
  2. 实例拜访对象的属性或者办法时,将依照搜寻原型链 prototype chain 的规定进行。首先查找本身的动态属性、办法,继而查找结构上下文的可拜访属性、办法,最初查找结构的原型链。
  3. thisprototype 定义的另一个不同点是在内存中占用空间不同。应用“this”关键字,实例初始化时为每个实例开拓构造方法所蕴含的所有属性、办法和所需空间,而应用 prototype 定义的,因为 prototype 实际上是指向父级的援用,因而在初始化和存储上比“this”节约资源。

五、总结

  1. 每个函数在创立之后都会有一个名为 prototype 属性,这个属性指向函数的原型对象(显式原型),所有实例对象须要共享的属性和办法都放在这个对象里;
  2. 任意对象都具备一个内置属性 __proto__ 指向创立这个对象的函数的显式原型;
  3. class 作为构造函数的语法糖,同时具备 prototype 属性和 __proto__ 属性,作为一个对象,子类 Child 的原型(__proto__)是父类 Parent;作为一个构造函数,子类的原型对象(prototype)是父类原型对象(prototype)的实例。
  4. 每个原型都有一个 constructor 属性指向构造函数,即 Parent === Parent.prototype.constructor

参考

JavaScript – 原型、原型链 · Issue #13 · cttin/cttin.github.io · GitHub

退出移动版