在上篇文章中,咱们使用ES5
经过构造函数和原型对象实现了「类」,经过原型链实现了「类」的继承。在ES6
中,新增class
和extend
实现了类和继承,提供了更接近传统语言的写法。javascript
和大多数面向对象的语言不一样,JavaScript
在诞生之初并不支持类,也没有把类继承做为建立类似或关联的对象的主要的定义方式。因此从ES1
到 ES5
这段时期,不少库都建立了一些工具,让JavaScript
看起来也能支持类。尽管一些JavaScript
开发者仍强烈主张该语言不须要类,但在流行库中实现类已成趋势,ES6
也顺势将其引入。但ES6
中的类和其余语言相比并非彻底等同的,目的是为了和JavaScript
的动态特性相配合。java
经过class
关键字,能够定义类。能够把class
看作一个语法糖,一个在ES5
中必须很是复杂才能完成的实现的封装。它使得定义一个类更加清晰明了,更符合面向对象编程的语法。git
咱们来对比一下:es6
// es5
function Person5 (name) {
this.age = 12
this.name = name
this.sayAge = function () {
return this.age
}
}
Person5.prototype.sayName = function () {
return this.name
}
let p1 = new Person5('zhu')
p1.age // 12
p1.sayName() // 'zhu'
// es6
class Person6 {
constructor (name) {
this.age = 12
this.name = name
this.sayAge = function () {
return this.age
}
}
sayName () {
return this.name
}
}
let p2 = new Person6('zhu')
p2.age // 12
p2.sayName() // 'zhu'
复制代码
类的原型对象的方法(sayName
),直接定义在类上便可。类的实例属性(name
)在constructor
方法里面定义。github
二者相比,ES5
更能说请ECMAScript
经过prototype
实现类的原理,ES6
写法更加清晰规范。 而生成实例的方法仍是一致的:经过new
命令。由于,class
只是定义类的语法糖。express
至于类原型对象的属性的定义,目前还在提案阶段编程
// es5
function Person5 () {}
Person5.prototype.shareSex = 'man'
let p1 = new Person5()
p1.shareSex // 'man'
// es6
class Person6 {
shareSex = 'man'
}
let p2 = new Person6()
p2.shareSex // 'man'
复制代码
类的constructor
方法的行为模式彻底与ES5的构造函数同样(关于构造函数能够参考{% post_link JavaScript高级程序设计第三版 %} 第6.2.2章节)。若是未定义,会默认添加。如下两个定义是等效的。数组
class Person {}
class Person {
constructor () {
return this
}
}
复制代码
上面的例子中,类的定义方式是声明式定义。与函数类似,类也有表达式定义的形式。浏览器
let Person = class {}
复制代码
虽然使用了声明变量,可是类表达式并不会提高。因此,声明式声明和表达式式声明除了写法不一样,彻底等价。安全
若是两种形式同时使用,声明式定义的名称可做为内部名称使用,指向类自己。但不能在外部使用,会报错。
let PersonMe = class Me {
constructor () {
Me.age = 12
}
sayAge () {
return Me.age
}
}
let p2 = new PersonMe()
p2.age // undefined
PersonMe.age // 12
p2.sayAge() // 12
Me.name // Uncaught ReferenceError: Me is not defined
PersonMe.name // Me
复制代码
咱们看到PersonMe.name
的值是Me
,而不是PersonMe
。由此可知,变量PersonMe
只是存储了一个执行Me
这个类的指针。
而类名之因此能够在内部使用,是由于具名表达式实际是这样的:
let PersonMe = (function() {
const Me = function() {
Me.age = 12
}
Me.prototype.sayAge = function () {
return Me.age
}
return Me
})()
复制代码
也可使用类表达式当即调用,以建立单例。
let p1 = new class {
constructor (name) {
this.name = name
}
sayName () {
return this.name
}
}('zhu')
复制代码
在编程中,能被当作值来使用的就称为一级公民(first-class citizen)。这意味着它能作函数的参数、返回值、给变量赋值等。在ECMAScript
中,函数是一级公民;在ES6
中,类一样也是一级公民。
在类的内部,类名是使用const
声明的,因此不能在类的内部重写类名。可是在类的外部能够,由于不管是声明仍是表达式的形式,在定义类的上下文中,函数名都只是存储指向类对象的指针。
虽然咱们说class
是语法糖,可是其某些地方表现与ES5
中也是有些区别的。
new
是从构造函数生成实例对象的命令。类必须使用new
调用,不然会报错,这点与ES5
中的构造函数不一样。
// es5
function Person5 (name) {
return name
}
Person5('zhu') // zhu
// es6
class Person6 {
constructor (name) {
return name
}
}
Person6('zhu') // Uncaught TypeError: Class constructor Person6 cannot be invoked without 'new'
复制代码
而这正是经过new
命令在ES6
中新增的target
属性实现的。该属性通常用在构造函数之中,返回new
命令做用于的那个构造函数。若是构造函数不是经过new
命令调用的,new.target
会返回undefined,反之会返回做用的类。
class Person6 {
constructor () {
console.log(new.target)
}
}
Person6() // undefined
new Person6() // Person6
复制代码
值得注意的是,子类继承父类时,new.target
会返回子类。
class Father {
constructor () {
console.log(new.target)
}
}
class Son extends Father {}
new Son() // Son
复制代码
最后,咱们使用new.target
在ES5
中模拟一下ES6
中class
的行为。
function Person5 () {
if(new.target === undefined) {
throw new TypeError("Class constructor Person6 cannot be invoked without 'new'")
}
console.log('success,', new.target === Person5)
}
Person5() // Uncaught TypeError: Class constructor Person6 cannot be invoked without 'new'
new Person5() // success, true
复制代码
ES6
中,在类上定义的方法,都是不可枚举的(non-enumerable)。在ES5
中是能够的。
// es5
function Person5 (name) {
this.age = 12
this.name = name
}
Person5.prototype.sayName = function () {
return this.name
}
// es6
class Person6 {
constructor (name) {
this.age = 12
this.name = name
}
sayName () {
return this.name
}
}
Object.getOwnPropertyDescriptor(Person5.prototype, 'sayName').enumerable // true
Object.getOwnPropertyDescriptor(Person6.prototype, 'sayName').enumerable // false
Object.keys(Person5.prototype) // ['sayName']
Object.keys(Person6.prototype) // []
Object.getOwnPropertyNames(Person5.prototype) // ["constructor", "sayName"]
Object.getOwnPropertyNames(Person6.prototype) // ["constructor", "sayName"]
复制代码
函数能够在当前做用域的任意位置定义,在任意位置调用。类不是函数,不存在变量提高。
// es5
new Person5()
function Person5 () {}
// es6
new Person6() // Uncaught ReferenceError: Person6 is not defined
class Person6 {}
复制代码
类的静态方法、实例的方法内部都没有[[Construct]]
属性,也没有原型对象(没有prototype
属性)。所以使用new
来调用它们会抛出错误。
class Person6 {
sayHi () {
return 'hi'
}
}
new Person6.sayHi // Uncaught TypeError: Person6.sayHi is not a constructor
Person6.prototype.sayHi.prototype // undefined
复制代码
一样的,箭头函数(() => {}}
)也同样。
let Foo = () => {}
new Foo // Uncaught TypeError: Person6.sayHi is not a constructor
复制代码
这种不是构造函数的函数,在ES5
中,只有内置对象的方法属于这种状况。
Array.prototype.concat.prototype // undefined
复制代码
除了区别,class
命令也有一些对ES5
构造函数的改进。好比,写法的改变,更加灵活、规范等等。
在类和模块的内部,默认开启了严格模式,也就是默认使用了use strict
ES6
中,方法名能够动态命名。访问器属性也可使用动态命名。
let methodName1 = 'sayName'
let methodName2 = 'sayAge'
class Person {
constructor (name) {
this.name = name
}
[methodName1] () {
return this.name
}
get [methodName2] () {
return 24
}
}
let p1 = new Person('zhu')
p1.sayName() // zhu
复制代码
在ES5
中,若是要将构造函数的实例属性设置成访问器属性,你要这样作:
function Person5 () {
this._age = 12
Object.defineProperty(this, 'age', {
get: function () {
console.log('get')
return this._age
},
set: function (val) {
console.log('set')
this._age = val
}
})
}
let p1 = new Person5()
p1.age // get 12
p1.age = 15 // set
p1.age // get 15
复制代码
在ES6
中咱们有了更方便的写法:
class Person6 {
constructor () {
this._age = 12
}
get age () {
console.log('get')
return this._age
}
set age (val) {
console.log('set')
this._age = val
}
}
let p2 = new Person6()
p2.age // get 12
p2.age = 15 // set
p2.age // get 15
复制代码
类的静态属性和静态方法是定义在类上的,也能够说是定义在构造函数的。它们不能被实例对象继承,可是能够被子类继承。须要注意的是,静态属性若是是引用类型,子类继承的是指针。 在ES6
中,除了constructor
方法,在类的其余方法名前面加上static
关键字,就表示这是一个静态方法。
// es5
function Person5 () {}
Person5.age = 12
Person5.sayAge = function () {
return this.age
}
Person5.age // 12
Person5.sayAge() // 12
let p1 = new Person5()
p1.age // undefined
p1.sayAge // undefined
// 继承
Sub5.__proto__ = Person5
Sub5.age // 12
Sub5.sayAge() // 12
// es6
class Person6 {
static sayAge () {
return this.age
}
}
Person6.age = 12
Person6.age // 12
Person6.sayAge() // 12
let p2 = new Person5()
p2.age // undefined
p2.sayAge // undefined
// 继承
class Sub6 extends Person6 {}
Sub6.age // 12
Sub6.sayAge() // 12
复制代码
须要注意的是,静态方法里面的this
关键字,指向的是类,而不是实例。因此为了不混淆,建议在静态方法中,直接使用类名。
class Person1 {
constructor (name) {
this.name = name
}
static getName () {
return this.name
}
getName () {
return this.name
}
}
let p1 = new Person1('zhu')
p1.getName() // 'zhu'
Person1.getName() // 'Person1'
class Person2 {
constructor (name) {
this.name = name
}
static getName () {
return Person2.name
}
getName () {
return this.name
}
}
let p2 = new Person2('zhu')
p2.getName() // 'zhu'
Person2.getName() // 'Person2'
复制代码
从上面的实例中咱们能够看到,静态方法与非静态方法是能够重名的。
ES6
明确规定,Class
内部只有静态方法,没有静态属性。因此,目前只能在Class
外部定义(Person6.age = 12
)。 可是,如今已经有了相应的提案
class Person6 {
static age = 12
static sayAge () {
return this.age
}
}
复制代码
私有属性其实就在类中提早声明的,只能在类内部使用的属性。以下示例:
class PersonNext {
static x; // 静态属性;定义在类上,会被子类继承
public y; // 实例属性。通常简写为 [y;],忽略public关键字,定义在实例上。
#z; // 私有属性。相似于其余语言中的private。只能在类内部使用
}
复制代码
因为此写法还在提案阶段,本文暂不详细说明,有兴趣能够关注提案的进度
Class
上实例方法中的this
,默认指向实例自己。可是使用解构赋值后,在函数指向时,做用域指向发生了改变,就有可能引发报错。虽然说有解决的方法,可是仍是尽可能避免使用这种方式吧。
class Person6 {
constructor () {
this.name = 'zhu'
}
sayName () {
return this.name
}
}
let p1 = new Person6()
p1.sayName() // zhu
let { sayName } = p1
sayName() // Uncaught TypeError: Cannot read property 'name' of undefined
sayName.call(p1) // zhu
复制代码
最后,咱们看一下class
在babel
中如何转换成ES6的
let methodName = 'sayName'
class Person {
constructor (name) {
this.name = name
this.age = 46
}
static create (name) {
return new Person(name)
}
sayAge () {
return this.age
}
[methodName] () {
return this.name
}
}
复制代码
"use strict";
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var methodName = "sayName";
var Person = (function() {
function Person(name) {
_classCallCheck(this, Person);
this.name = name;
this.age = 46;
}
Person.create = function create(name) {
return new Person(name);
};
Person.prototype.sayAge = function sayAge() {
return this.age;
};
Person.prototype[methodName] = function() {
return this.name;
};
return Person;
})();
复制代码
_classCallCheck
方法算是new.target
的polyfill。
class继承主要就是添加了extends
关键字,相比与class
,extends
不只仅是语法糖,还实现了许多ES5
没法实现的功能。也就是说,extends
是没法彻底降级到ES5
的。好比,内置对象的继承。
class
能够经过extends关键字实现继承,这比ES5
的经过修改原型链实现继承,要清晰和方便不少。 咱们先来回顾下ES5
的实现:
function Father5 (name) {
this.name = name
this.age = 46
}
Father5.prototype.sayName = function () {
return this.name
}
Father5.prototype.sayAge = function () {
return this.age
}
Father5.create = function (name) {
return new this(name)
}
function Son5 (name) {
Father5.call(this, name)
}
Son5.prototype = Object.create(Father5.prototype, {
constructor: {
value: Son5,
enumerable: true,
writable: true,
configurable: true
}
})
Son5.__proto__ = Father5
Son5.prototype.setAge = function (age) {
this.age = age
}
var s1 = Son5.create('zhu')
s1.constructor // Son5
s1.sayName() // 'zhu'
s1.sayAge() // 46
s1.setAge(12)
s1.sayAge() // 12
复制代码
而后,咱们看下class
和 extends
如何实现:
let Father6 = class Me {
constructor (name) {
this.name = name
this.age = 46
}
static create (name) {
return new Me(name)
}
sayName () {
return this.name
}
sayAge () {
return this.age
}
}
let Son6 = class Me extends Father6 {
constructor (name) {
super(name)
}
setAge (age) {
this.age = age
}
}
let s2 = Son6.create('sang')
s2.constructor // Son6
s2.sayName() // 'sang'
s2.sayAge() // 46
s2.setAge(13)
s2.sayAge() // 13
复制代码
咱们看到extends
用super(name)
作了三件事:实例属性继承,原型对象继承,静态属性继承。接下来,咱们就来讲说super
。
在子类中,若是定义了constructor
,则必须在第一行调用super
。由于super
对子类的this
进行了封装,使之继承了父类的属性和方法。 若是在super
调用以前使用this
,会报错。
class Son extends Father {
constructor (name) {
this.name = name // Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
super(name)
this.name = name // 正常执行
}
}
复制代码
若是没有定义constructor
,则会默认添加。
class Son extends Father {}
// 等同于
class Son extends Father {
constructor (..arg) {
super(..arg)
}
}
复制代码
super
关键字必须做为一个函数或者一个对象使用,若是做为值使用会报错。
class Son extends Father{
constructor (name) {
super(name)
console.log(super) // Uncaught SyntaxError: 'super' keyword unexpected here
}
}
复制代码
做为函数调用时,只能在子类的constructor
函数中,不然也会报错。
做为对象使用时,在普通方法中,指向的是原父类的原型对象;在静态方法中,指向的是父类自己。
最后,因为对象老是继承其余对象的,因此能够在任意一个对象中,使用super关键字
var obj = {
toString() {
return "MyObject: " + super.toString();
}
};
obj.toString(); // MyObject: [object Object]
复制代码
惟一在
constructor
中能够不调用super
的状况是,constructor
显式的返回了一个对象。 不过,这种写法好像没什么意义。
在ECMAScript
中,咱们会常用字面量去「构造」一个基本类型数据。这实际上是使用new
命令构造一个实例的语法糖。这每每让咱们误觉得在ECMAScript
中,一切函数都是构造函数,而一切对象都是这些构造函数的实例,而ECMAScript
也是一门面向对象的语言。
// 引用类型
var obj = {} // var obj = new Object()
var arr = [] // var arr = new Array()
// 值类型
var str = "" // var strObj = new String();var str = strObj.valueOf()
复制代码
但ECMAScript
并非纯粹的面向对象语言,它里面也有函数式编程的东西。因此,并非每一个函数都有原型对象,都有constructor
。
好比原生构造函数的原型对象上面的方法(如Array.prototype.concat
、Number.prototype.toFixed
)都是没有prototype
属性的。还有,箭头函数也是没有prototype
属性的。因此,这些函数是不能是用new
命令的,若是用了会抛错。
new Array.prototype.concat() // Uncaught TypeError: Array.prototype.concat is not a constructor
复制代码
这些没有prototype
属性的方法,是函数式编程的实现,看起来也更纯粹。使用这些方法时,也建议使用lambda
的链式语法。
extends
后面能接受任意类型的表达式,这带来了巨大的可能性。例如,动态的决定父类。
class FatherA {}
class FatherB {}
const type = 'A'
function select (type) {
return type === 'A' ? FatherA : FatehrB
}
class Son extends select('A') {
constructor () {
super()
}
}
Object.getPrototypeOf(Son) === FatherA // true
复制代码
若是,想要一个子类同时继承多个对象的方法呢?咱们也可使用mixin
。
Mixin
指的是多个对象合成一个新的对象,新对象具备各个组成成员的接口。下面示例,mixin
的返回对象的原型对象,是传入的几个对象的原型对象的合成。
const objA = {
sayA() {
return 'A'
}
}
const objB = {
sayB() {
return 'B'
}
}
const objC = {
sayC() {
return 'C'
}
}
function mixin (...args) {
const base = function () {}
Object.assign(base.prototype, ...args)
return base
}
class Son extends mixin(objA, objB, objC) {}
let s1 = new Son()
s1.sayA() // 'A'
s1.sayB() // 'B'
s1.sayC() // 'C'
复制代码
咱们更进一步,将实例对象也合成进去。
function mix(...mixins) {
class Mix {}
for (let mixin of mixins) {
copyProperties(Mix.prototype, mixin); // 拷贝实例属性
copyProperties(Mix.prototype, Reflect.getPrototypeOf(mixin)); // 拷贝原型属性
}
return Mix;
}
function copyProperties(target, source) {
for (let key of Reflect.ownKeys(source)) {
if ( key !== "constructor"
&& key !== "prototype"
&& key !== "name"
) {
let desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}
复制代码
在ES5
及以前,没法经过继承机制来继承内置对象的某些特性。咱们以试图建立一个特殊数组为例:
// es5
// Array 的特性
var colors = []
colors[0] = 'red'
// length 跟着改变
colors.length // 1
// 改变数组的length
colors.length = 0
colors[0] // undefined
// 试图使用ES5的方式继承
function MyArray () {
Array.apply(this)
}
MyArray.prototype = Object.create(Array.prototype, {
constructor: {
value: MyArray,
writable: true,
configurable: true,
enumerable: true
}
})
var colors = new MyArray()
colors[0] = 'red'
// length 没有跟着改变
colors.length // 0
// 改变数组的length
colors.length = 0
colors[0] // 'red'
复制代码
结果并不尽如人意,咱们继续使用ES6
的继承:
class MyArray extends Array {}
let colors = new MyArray()
colors[0] = 'red'
colors.length // 1
colors.length = 0
colors[0] // undefined
复制代码
与咱们的预期彻底一致。因此,ES5
和ES6
中对内置对象的继承仍是有区别的。
在ES5
中,this
的值是被MyArray
函数建立的,也就是说this
的值实际上是MyArray
的实例,而后Array.apply(this)
被调用,this
上面又被添加了Array
上面一些附加的方法和属性,而内置的属性和方法并无被添加到this
上。
而在ES6
中,this
的值会先被Array
建立(super()
),而后才会把MyArray
的上面的附加属性和方法添加上去。
基于此,咱们能够经过继承内置对象实现更多更利于咱们本身使用的「超级内置对象」。
类的Symbol.species
属性,指向一个构造函数。建立衍生对象时,会使用该属性。
下面示例中,a
是MyArray
的实例,而b
、c
便是所谓的衍生对象。
class MyArray extends Array {
}
const a = new MyArray(1, 2, 3);
const b = a.map(x => x);
const c = a.filter(x => x > 1);
a.constructor // MyArray
b.constructor // MyArray
c.constructor // MyArray
复制代码
默认的Symbol.species
的值以下:
static get [Symbol.species]() {
return this;
}
复制代码
咱们能够试着改变它。
class MyArray extends Array {
static get [Symbol.species]() { return Array; }
}
const a = new MyArray();
const b = a.map(x => x);
a.constructor // MyArray
b.constructor // Array
复制代码
咱们看到衍生对象的构造函数执行发生了变化。
继承Object
的子类,有一个行为差别。
class NewObj extends Object{
constructor(){
super(...arguments);
}
}
var o = new NewObj({attr: true});
o.attr // undefined
复制代码
上面代码中,NewObj
继承了Object
,可是没法经过super
方法向父类Object
传参。这是由于 ES6
改变了Object
构造函数的行为,一旦发现Object
方法不是经过new Object()
这种形式调用,ES6
规定Object
构造函数会忽略参数。
咱们将如下ES6
的代码,在babel中转换为ES5
的代码。
let Father6 = class Me {
constructor (name) {
this.name = name
this.age = 46
}
static create (name) {
return new Me(name)
}
sayName () {
return this.name
}
sayAge () {
return this.age
}
}
let Son6 = class Me extends Father6 {
constructor (name) {
super(name)
}
setAge (age) {
this.age = age
}
}
复制代码
转换后的代码:
"use strict";
function _possibleConstructorReturn(self, call) {
if (!self) {
throw new ReferenceError(
"this hasn't been initialised - super() hasn't been called"
);
}
return call && (typeof call === "object" || typeof call === "function")
? call
: self;
}
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError(
"Super expression must either be null or a function, not " +
typeof superClass
);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass)
Object.setPrototypeOf
? Object.setPrototypeOf(subClass, superClass)
: (subClass.__proto__ = superClass);
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Father6 = (function() {
function Me(name) {
_classCallCheck(this, Me);
this.name = name;
this.age = 46;
}
Me.create = function create(name) {
return new Me(name);
};
Me.prototype.sayName = function sayName() {
return this.name;
};
Me.prototype.sayAge = function sayAge() {
return this.age;
};
return Me;
})();
var Son6 = (function(_Father) {
_inherits(Me, _Father);
function Me(name) {
_classCallCheck(this, Me);
return _possibleConstructorReturn(this, _Father.call(this, name));
}
Me.prototype.setAge = function setAge(age) {
this.age = age;
};
return Me;
})(Father6);
复制代码
babel定义了三个有趣的方法:
_classCallCheck
用于判断类是否被new
命令符调用,是new.target
的polyfill;_inherits
用于子类继承父类的原型对象和静态方法,_possibleConstructorReturn
用于继承实例属性。这个方法里面有个颇有意思的判断,若是构造函数的返回是object
或者function
,就把这个返回值做为子类的实例,反之,返回子类的实例。这是为了降级解决ES5
中没法继承内置对象的问题,由于内置对象默认都会返回对应的实例,而咱们自定义的构造函数通常是不会写返回值的。 这样咱们在ES5
中若是要继承内置对象,就不能给子类添加自定义的方法和属性了,由于返回的是内置对象的实例。__proto__
的指向class Father extends Function {}
class Son extends Father {}
let s1 = new Son()
复制代码
先说几个定义:
__proto__
属性指向类的原型对象。__proto__
属性指向它的父类, prototype
指向它的原型对象。__proto__
指向父类的原型对象。咱们开始验证:
s1
是 Son
的实例对象。Son
是 Father
的子类。s1.__proto__ === Son.prototype // true
Son.__proto__ === Father // ture
Son.prototype.__proto__ === Father.prototype // true
复制代码
第1,2,3条都获得了验证。
咱们继续顺着原型链往下走:
Father
是 Function
的子类Father.__proto__ === Function // true
Father.prototype.__proto__ === Function.prototype // true
复制代码
第2,3条都获得了验证。
咱们知道全部的函数或者类都是原先构造函数Function
的实例。因此:
Function.__proto__ === Function.prototype // true
typeof Function.prototype // 'function'
复制代码
第1,4条获得了印证。同时,Function.prototype
是函数,咱们也能够说Function.prototype
是全部函数的父类。
咱们知道全部对象都是原先构造函数Object
的实例,因此:
Function.prototype.__proto__ === Object.prototype // true
复制代码
全部的原型对象都继承自Object.prototype
。因此:
Object.prototype.__proto__ === null // true
复制代码
Object instanceof Function // true
Function instanceof Object // true
复制代码
咱们看一下instanceof
的定义:instanceof运算符用于测试构造函数的prototype属性是否出如今对象的原型链中的任何位置
。
Object
自己是构造函数,继承了Function.prototype
;
Object.__proto__ === Function.prototype
复制代码
Function
也是对象,继承了Object.prototype
。
Function.__proto__.__proto__ === Object.prototype
复制代码
因此谁先存在的呢?
// 肯定Object.prototype是原型链的顶端
Object.prototype.__proto__ === null // true
// 肯定Function.prototype继承自Object.prototype
Function.prototype.__proto__ === Object.prototype // true
// 肯定全部的原生构造函数继承自Function.prototype
Function.__proto__ === Function.prototype // true
Object.__proto__ === Function.prototype // true
Array.__proto__ === Function.prototype // true
String.__proto__ === Function.prototype // true
Number.__proto__ === Function.prototype // true
Boolean.__proto__ === Function.prototype // true
复制代码
Object.prototype
只是一个指针,它指向一个对象(就叫它protoObj
吧)。protoObj
是浏览器最早建立的对象,这个时候Object.prototype
尚未指向它,由于Object
尚未被建立。而后根据protoObj
建立了另外一个即便函数又是对象的funProConstructor
,也就是Function.prototype
指向的内存地址(是的,Function.prototype
也是一个指针),可是如今它们尚未创建关系,Function.prototype
尚未指向funProConstructor
。再而后,浏览器使用funProConstructor
构造函数,建立出了咱们熟悉的原生构造函数Object
、Function
等等,因此这些原生构造函数的__proto__
属性指向了它们的父类Function.prototype
,而这时候,建立出来的Object
、Function
上面的Object.prototype
和Function.prototype
也分别指向了protoObj
和funProConstructor
。自此,浏览器内部原型相关的内容初始化完毕。
咱们将上面的描述整理以下:
解开全部疑惑的关键都在这么一句话:Function.prototype
是个不一样于通常函数(对象)的函数(对象)。
getter
和 setter
由于__proto__
并非标准的一部分,因此不建议使用。若是要在ES6
中读取和修改原型,推荐使用:Object.getPrototypeOf 和 Object.setPrototypeOf
ES6
的类让JS
中的继承变得更简单,所以对于你已从其余语言学习到的类知识,你无须将其丢弃。ES6
的类起初是做为ES5
传统继承模型的语法糖,但添加了许多特性来減少错误。
ES6
的类配合原型继承来工做,在类的原型上定义了非静态的方法,而静态的方法最终则被绑定在类构造器自身上。类的全部方法初始都是不可枚举的,这更契合了内置对象的行为, 后者的方法默认状况下一般都不可枚举。此外,类构造器被调用时不能缺乏new
,确保了不能意外地将类做为函数来调用。
基于类的继承容许你从另外一个类、函数或表达式上派生新的类。这种能力意味着你能够调用一个函数来判断须要继承的正确基类,也容许你使用混入或其余不一样的组合模式来建立一个新类。新的继承方式让继承内置对象(例如数组)也变为可能,而且其工做符合预期。
你能够在类构造器内部使用new.target
,以便根据类如何被调用来作出不一样的行为。最经常使用的就是建立一个抽象基类,直接实例化它会抛出错误,但它仍然容许被其余类所继承。
总之,类是JS
的一项新特性,它提供了更简洁的语法与更好的功能,经过安全一致的方式来自定义一个对象类型。