对象的[[Prototype]] js中任何对象内都有一个隐藏属性:[[Prototype]],若访问的属性名在当前对象中没有找到,js会自动到[[Prototype]]所引用的对象中去寻找。[[prototype]]可以通过设置proto 属性来间接 设置(实际上proto 是[[Prototype]]的setter),如下例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let animal = { eats: true walk() { alert("Animal walk" ); } }; let rabbit = { jumps: true }; rabbit.__proto__ = animal; alert( rabbit.eats ); alert( rabbit.jumps ); rabbit.walk();
函数的原型属性(prototype) js中的所有函数对象,都自动会创建一个prototype属性,属性值是一个原型对象”prototype”,里面包含一个constructor属性,其属性值设置为函数对象本身。当使用new调用这个函数来创建一个对象的时候,这个对象的[[Prototype]]会指向哪个原型对象,如下例:
1 2 3 4 5 6 7 8 function Rabbit ( ) {}let rabbit = new Rabbit(); console .log(rabbit.constructor == Rabbit);
用函数原型实现面向对象 把对象的方法都放在函数原型当中,如下面两个对象:
1 2 3 4 5 6 7 8 9 function Rabbit (name ) { this .name = name; } Rabbit.prototype.jump = function ( ) { alert(`${this .name} jumps!` ); }; let rabbit = new Rabbit("My rabbit" );
1 2 3 4 5 6 7 8 9 function Animal (name ) { this .name = name; } Animal.prototype.eat = function ( ) { alert(`${this .name} eats.` ); }; let animal = new Animal("My animal" );
要实现Rabbit继承Animal,只需要让Rabbit的函数原型的[[Prototype]]指向Animal的函数原型即可。如下代码及图示:
1 2 3 4 5 6 7 8 Rabbit.prototype.__proto__ = Animal.prototype; Rabbit.prototype = Object .create(Animal.prototype) let rabbit = new Rabbit("White Rabbit" );rabbit.eat(); rabbit.jump();
创建出rabbit对象实例之后,完整的原型链还包括了Object公共原型,看起来是这样的:
class关键字 js实现面向对象的基本原理就是之前的那些,js把这些实现集合成语法糖,可以直接使用class关键字像其他语言那样直接定义类
1 2 3 4 5 6 7 8 9 10 11 12 13 class User { constructor (name) { this .name = name; } sayHi() { alert(this .name); } } let user = new User("John" );user.sayHi();
上面这段代码,其实就等价于下面这段
1 2 3 4 5 6 7 8 9 10 function User (name ) { this .name = name; } User.prototype.sayHi = function ( ) { alert(this .name); } let user = new User("John" );user.sayHi();
一个js中的类,可以写成以下这种形式
1 2 3 4 5 6 7 8 9 10 11 class MyClass { constructor (...) { } method1(...) {} method2(...) {} get something(...) {} set something(...) {} static staticMethod(..) {} }
Myclass实际上是一个变量,它的值是constructor构造函数,类里的方法如果不加static关键字,就是Myclass的原型内的方法,如果加上static关键字,那么这个函数就是Myclass函数对象内的方法,和Myclass构造函数返回的具体实例无关。
类继承的语法糖如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Rabbit extends Animal { constructor (...args) { super (...args); } hide() { alert(`${this .name} hides!` ); } stop() { super .stop(); this .hide(); } }
需要补充的是,当类的原型对象组成继承链的时候,相应的构造函数对象也同时组成了继承链,这样子类也可以调用父类的static方法,如下图所示
super关键字 super关键字用来访问当前类的父类的方法,它的原理是这样的:
类原型的函数对象里会自动创建一个隐藏的[[HomeObject]]属性,该属性是指向这个原型对象的引用,这样每个类方法都可以通过[[HomeObject]]来访问它真正的原初的this,而不会被人为乱七八糟的调用以及call或者绑定所干扰对真正this值的判断。有了真正的原初的this,就可以通过访问this.[[Prototype]]来获得父类的原型对象了,从而实现了super的效果。
下面用普通的Object写法来展示super的原理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 let animal = { name: "Animal" , eat() { alert(`${this .name} eats.` ); } }; let rabbit = { __proto__: animal, name: "Rabbit" , eat() { super .eat(); } }; let longEar = { __proto__: rabbit, name: "Long Ear" , eat() { super .eat(); } }; longEar.eat();
需要注意的是,只有写成类方法形式的函数才会生成[[HomeObject]],如果写成”Method : function()”的形式,就不会有[[HomeObject]]。