共计 3910 个字符,预计需要花费 10 分钟才能阅读完成。
前言
其余编程语言如 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.getPrototypeOf
(Object.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__
指向创立这个对象的函数的显式原型,其关键在于找到创立这个对象的构造函数。创建对象有三种模式:对象字面量;new
(class
);ES5 的Object.create()
。实质只有一种new
。
2.3.1 对象字面量
let obj = {name: 'ctt'}
对象字面量申明的对象继承自 Object
,和 new Object
一样,其原型为 Object.prototype
。而 Object.prototype
不继承任何属性和办法。
2.3.2 new
应用构造函数创立的对象,它的属性继承自构造函数。
具体分以下几种状况:
- 内建对象
如Array()
,它继承于Array.prototype
,Array.prototype
为一个对象,这个对象由Object()
这个构建函数创立。因而,Array.prototype.__proto__ === Object.prototype
,原型链为:Array.prototype -> Object.prototype -> null
。 - 自定义对象
-
默认状况下:
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
切断了分割。
- 构造函数
构造函数就是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 定义方法的区别
- 利用
this
实现的办法,能够拜访类中的公有变量和公有办法。而利用原型对象实现的办法,无法访问类中的公有变量和办法。 - 实例拜访对象的属性或者办法时,将依照搜寻原型链
prototype chain
的规定进行。首先查找本身的动态属性、办法,继而查找结构上下文的可拜访属性、办法,最初查找结构的原型链。 this
和prototype
定义的另一个不同点是在内存中占用空间不同。应用“this”关键字,实例初始化时为每个实例开拓构造方法所蕴含的所有属性、办法和所需空间,而应用prototype
定义的,因为prototype
实际上是指向父级的援用,因而在初始化和存储上比“this”节约资源。
五、总结
- 每个函数在创立之后都会有一个名为
prototype
属性,这个属性指向函数的原型对象(显式原型),所有实例对象须要共享的属性和办法都放在这个对象里; - 任意对象都具备一个内置属性
__proto__
指向创立这个对象的函数的显式原型; - class 作为构造函数的语法糖,同时具备
prototype
属性和__proto__
属性,作为一个对象,子类Child
的原型(__proto__
)是父类Parent
;作为一个构造函数,子类的原型对象(prototype
)是父类原型对象(prototype
)的实例。 - 每个原型都有一个
constructor
属性指向构造函数,即Parent === Parent.prototype.constructor
参考
JavaScript – 原型、原型链 · Issue #13 · cttin/cttin.github.io · GitHub