原型与原型链
[[toc]]
原型与原型链 首先,JS是真正的“面向对象”的语言,而其他我们所熟知的例如C++、Java等,严格意义上说,是“面向类”的语言,仔细想想,还真是那么回事儿。其次,JS中,调用构造器之后,对象并不是它原型的一份拷贝,而是被链接 到原型上。
Object 我们先看一段代码:
function Person (name ) { this .name = name; } Person.prototype.sayName = function (welcome ) { console .log(welcome, this .name); } var person1 = new Person('Smiley' ); person1.sayName('Hello' );
在执行这段代码之前,有个东西是一直存在的,就是有些人所说的“原型的原型”。如下图所示,我们用圆形代表function,用方形代表object:
左边的圆形是Object构造函数,就是我们一般使用var obj = new Object()时最普普通通的的构造函数。右边的方形是Object的prototype,这个东西没有名字(虽然它很重要)。左边有一个箭头指向右边,意味着Object有一个属性叫做prototype,这个属性指向的是右边的那个方块,而向左指的箭头,意味着右边方块的constructor属性是左边的Object构造函数。
这些东西在上面程序运行之前就一直存在的。
构造函数 好的,我们开始看第一行代码,运行第一行代码之后,会生成一个叫Person的构造函数,而这个构造函数的prototype属性,指向的就是它的prototype,如下图方块所示:
Person与Person.prototype之间的关系,与Object和Object.prototype类似,不同的是,Person.prototype会通过proto 指向Object.prototype。
接下来看第5行代码:我们在Person.prototype上面添加一个sayName方法,如上图所示,Person.prototype这个方块中有sayName方法。
new调用构造器 使用new这个关键字的时候,JS编译器会做四件事情:
function realizeNew (con, ...args ) { let obj = {}; obj.__proto__ = con.prototype; let ret = con.apply(obj, args); return typeof ret === 'object' && ret !== null ? ret : obj; } function Person (name,age ) { this .name=name; this .age=age; this .run=function ( ) { alert(this .name+'在运动' ); } } realizeNew(Person,'和振峰' ,24 )
如上图所示,我们先创建一个person1的空对象,然后把person1通过proto 指向原型对象,指向构造函数中的代码,person1就获得了一个叫做name的属性,最后返回。
我们最后运行第10行代码:person1.sayName(‘Hello’);
person1上有sayName这个方法么?没有,那么就顺着person1的proto 向上找,找到Person.prototype。Person.prototype上面有sayName方法么?有的,那么执行这个方法。这个方法内部使用了this.name,那么这个this的指向是什么么?我们需要看sayName的call site,是person1调用的sayName,隐式调用,this就指向person1,而person1的name就是Smiley。
是不是觉得很神奇,最后调用时候使用的属性和方法都是我们希望使用的那个,person1.sayName(‘Hello’)看似很容易理解,JS初学者都能很容易说出最后输出结果,但是这其中的过程,恐怕只有理解了原型和原型链才能真正说明白。
明白了这些之后,我们看几个相等关系:
console .log(Person === Person.prototype.constructor);console .log(person1.__proto__ === Person.prototype);
我们再也不用死记硬背这个关系了,而是通过上面的图直接可以推到出来。
console.log(person1.constructor)是什么的呢?
es5的几种继承方式 对象冒充实现继承 function Person ( ) { this .name='张三' ; this .age=20 ; this .run=function ( ) { alert(this .name+'在运动' ); } } Person.prototype.sex="男" ; Person.prototype.work=function ( ) { alert(this .name+'在工作' ); } function Web ( ) { Person.call(this ); } var w=new Web();w.work();
原型链实现继承 function Person (name,age ) { this .name='张三' ; this .age=20 ; this .run=function ( ) { alert(this .name+'在运动' ); } } Person.prototype.sex="男" ; Person.prototype.work=function ( ) { alert(this .name+'在工作' ); } function Web (name,age ) { } Web.prototype=new Person(); var w=new Web();w.work();
原型链+对象冒充的组合继承模式 function Person (name,age ) { this .name=name; this .age=age; this .run=function ( ) { alert(this .name+'在运动' ); } } Person.prototype.sex="男" ; Person.prototype.work=function ( ) { alert(this .name+'在工作' ); } function Web (name,age ) { Person.call(this ,name,age); } Web.prototype=new Person(); var w=new Web('赵四' ,20 ); w.work();
class实现继承源码 ES6
class B { constructor (props) { this .name = props.name; } } class A extends B { constructor () { super ({ name : 'B' }); console .log(this ); } }
ES5
function __extends (child, parent ) { Object .setPrototypeOf(child, parent); function __ ( ) { this .constructor = child; } child.prototype = parent === null ? Object .create(parent) : ((__.prototype = parent.prototype), new __()); } var B = (function ( ) { function B (props ) { this .name = props.name; } return B; }()); var A = (function (_super ) { __extends(A, _super); function A ( ) { var _this = _super.call(this , { name : 'B' }) || this ; console .log(_this); return _this; } return A; }(B));
ES5/ES6 的继承除了写法以外还有什么区别?
class 声明会提升,但不会初始化赋值。Foo 进入暂时性死区,类似于 let、const 声明变量。
const bar = new Bar(); function Bar ( ) { this .bar = 42 ; } const foo = new Foo(); class Foo { constructor () { this .foo = 42 ; } }
class 声明内部会启用严格模式。
function Bar ( ) { baz = 42 ; } const bar = new Bar();class Foo { constructor () { fol = 42 ; } } const foo = new Foo();
class 的所有方法(包括静态方法和实例方法)都是不可枚举的。
function Bar ( ) { this .bar = 42 ; } Bar.answer = function ( ) { return 42 ; }; Bar.prototype.print = function ( ) { console .log(this .bar); }; const barKeys = Object .keys(Bar); const barProtoKeys = Object .keys(Bar.prototype); class Foo { constructor () { this .foo = 42 ; } static answer() { return 42 ; } print() { console .log(this .foo); } } const fooKeys = Object .keys(Foo); const fooProtoKeys = Object .keys(Foo.prototype);
class 的所有方法(包括静态方法和实例方法)都没有原型对象 prototype,所以也没有[[construct]],不能使用 new 来调用。
function Bar ( ) { this .bar = 42 ; } Bar.prototype.print = function ( ) { console .log(this .bar); }; const bar = new Bar();const barPrint = new bar.print(); class Foo { constructor () { this .foo = 42 ; } print() { console .log(this .foo); } } const foo = new Foo();const fooPrint = new foo.print();
必须使用 new 调用 class。
function Bar ( ) { this .bar = 42 ; } const bar = Bar(); class Foo { constructor () { this .foo = 42 ; } } const foo = Foo();
class 内部无法重写类名。
function Bar ( ) { Bar = 'Baz' ; this .bar = 42 ; } const bar = new Bar();class Foo { constructor () { this .foo = 42 ; Foo = 'Fol' ; } } const foo = new Foo();Foo = 'Fol' ;