理解原型设计模式以及 JavaScript 中的原型规则
原型链:每一个实例对象都有一个__proto__
属性(隐式原型),在js内部用来查找原型链;每一个构造函数都有prototype
属性(显示原型),用来显示修改对象的原型,实例.__proto__
=构造函数.prototype
=原型。原型链的特点就是:通过实例.__proto__
查找原型上的属性,从子类一直向上查找对象原型的属性,继而形成一个查找链即原型链。
instanceof 的底层实现原理,手动实现一个 instanceof
简单说就是判断实例对象的__proto__
是不是强等于对象的prototype
属性,如果不是继续往原型链上找,直到 __proto__
为 null
为止。
1 |
|
理解 JavaScript 的执行上下文栈,可以应用堆栈信息快速定位问题
执行上下文 就是当前 JavaScript
代码被解析和执行时所在环境的抽象概念, JavaScript
中运行任何的代码都是在执行上下文中运行。
执行上下文总共有三种类型:全局执行上下文, 函数执行上下文, Eval
函数执行上下文
执行栈,在其他编程语言中也被叫做调用栈,具有 LIFO(后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。
实现继承的几种方式以及他们的优缺点
1.原型链继承
1 |
|
缺点:构造函数原型上的属性在所有该构造函数构造的实例上是共享的,即属性没有私有化,原型上属性的改变会作用到所有的实例上。
2.构造函数继承
在构造子类构造函数时内部使用call或apply来调用父类的构造函数
1 |
|
优缺点:实现了属性的私有化,但是子类无法访问父类原型上的属性。
3.组合继承
利用构造函数和原型链的方法,可以比较完美的实现继承
1 |
|
这里还有个小问题,
Sub.prototype = new Super
; 会导致Sub.prototype
的constructor
指向Super
;然而constructor
的定义是要指向原型属性对应的构造函数的,Sub.prototype
是Sub
构造函数的原型,所以应该添加一句纠正:Sub.prototype.constructor = Sub
;
4.寄生继承
即将Sub.prototype=new Super
改为Sub.prototype=Object.create(Supper.prototype)
,避免了组合继承中构造函数调用了两次的弊端。
可以描述 new 一个对象的详细过程,手动实现一个 new 操作符
过程
当我们new一个构造函数,得到的实例继承了构造器的构造属性以及原型上的属性。
在《JavaScript模式》这本书中,new的过程说的比较直白,当我们new一个构造器,主要有三步:
1.以构造器的prototype
属性为原型,创建新对象;
2.将this
(也就是上一句中的新对象)和调用参数传给构造器,执行;
3.如果构造器没有手动返回对象,则返回第一步创建的对象
实现
1 | function myNew(Obj,...args){ |
理解 es6 class 构造以及继承的底层实现原理
ES6中通过class关键字,定义类
1 | class Parent { |
经过babel转码之后
1 | var _createClass = function () { |
可以看到ES6类的底层还是通过构造函数去创建的。
通过ES6
创建的类,是不允许你直接调用的。在ES5
中,构造函数是可以直接运行的,比如Parent()
。但是在ES6
就不行。我们可以看到转码的构造函数中有_classCallCheck(this, Parent)
语句,这句话是防止你通过构造函数直接运行的。你直接在ES6运行Parent(),这是不允许的,ES6中抛出Class constructor Parent cannot be invoked without 'new'
错误。转码后的会抛出Cannot call a class as a function
.我觉得这样的规范挺好的,能够规范化类的使用方式。
转码中_createClass
方法,它调用Object.defineProperty
方法去给新创建的Parent添加各种属性。defineProperties(Constructor.prototype, protoProps)
是给原型添加属性。如果你有静态属性,会直接添加到构造函数上defineProperties(Constructor, staticProps)
。但是貌似并没有用到,下面可以证明.
这两个流程走下来,其实就创建了一个类。
上面讲的是创建一个类的过程,那ES6如何实现继承的呢?还是上面的例子,这次我们给Parent添加静态属性,原型属性,内部属性
1 | class Parent { |
转码之后的代码变成了这样
1 | var _createClass = function () { |
我们可以看到,构造类的方法都没变,只是添加了_inherits
核心方法来实现继承,下面我们就看下这个方法做了什么?
首先是判断父类的类型,然后
1 | subClass.prototype = Object.create(superClass && superClass.prototype, { |
这段代码翻译下来就是
1 | function F(){} |
接下来subClass.proto = superClass
_inherits核心思想就是下面两句
1 | subClass.prototype.__proto__ = superClass.prototype |
一图胜千言
那为什么这样一倒腾,它就实现了继承了呢?
首先 subClass.prototype.__proto__ = superClass.prototype
保证了c instanceof Parent
是true
,Child的实例可以访问到父类的属性,包括内部属性,以及原型属性。其次,subClass.__proto__ = superClass
,保证了Child.height
也能访问到,也就是静态方法。
subClass.__proto__ = superClass
不是很好理解,可以通过下面的方式理解
1 | function A(){} |
a是一个实例,A.prototype
是构造方法的原型。通过这种方式,那么a就可以访问A.prototype
上面的方法。
那把 subClass类比成 a,superClass类比成A.prototype,那是不是subClass可以直接访问 superClass的静态属性,静态方法了。
参考资料: ES6类以及继承的实现原理