一、了解原型设计模式以及 JavaScript 中的原型规定
设计模式
- 工厂模式
在函数内创立一个对象,给对象赋予属性及办法再将对象返回
function Person() {var People = new Object()
People.name = 'CrazyLee'
People.age = '25'
People.sex = function() {return 'boy'}
return People
}
var a = Person()
console.log(a.name) // CrazyLee
console.log(a.sex()) // boy
- 构造函数模式
无需在函数外部从新创建对象,而是用 this 指代
function Person() {
this.name = 'CrazyLee'
this.age = '25'
this.sex = function() {return 'boy'}
}
var a = new Person()
console.log(a.name) // CrazyLee
console.log(a.sex()) // boy
- 原型模式
函数中不对属性进行定义,利用 prototype 属性对属性进行定义,能够让所有对象实例共享它所蕴含的属性及办法。
function Parent() {
Parent.prototype.name = 'carzy'
Parent.prototype.age = '24'
Parent.prototype.sex = function() {
var s = '女'
console.log(s)
}
}
var x = new Parent()
console.log(x.name) // crazy
console.log(x.sex()) // 女
- 混合模式
原型模式 + 构造函数模式。这种模式中,构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性
function Parent() {
this.name = 'CrazyLee'
this.age = 24
}
Parent.prototype.sayname = function() {return this.name}
var x = new Parent()
console.log(x.sayname()) // Crazy  
- 动静原型模式
将所有信息封装在了构造函数中,而通过构造函数中初始化原型,这个能够通过判断该办法是否无效而抉择是否须要初始化原型。
function Parent() {
this.name = 'CrazyLee'
this.age = 24
if (typeof Parent._sayname == 'undefined') {Parent.prototype.sayname = function() {return this.name}
Parent._sayname = true
}
}
var x = new Parent()
console.log(x.sayname())
原型规定
- 原型规定
- 所有的援用类型(数组、对象、函数),都具备对象特色,即可自在扩大属性;
var arr = []
arr.a = 1
- 所有的援用类型,都有一个
__proto__
属性(隐式原型),属性值是一个一般对象; - 所有函数,都具备一个 prototype(显示原型),属性值也是一个一般原型;
- 所有的援用类型(数组、对象、函数),其隐式原型指向其构造函数的显式原型;
(obj.__proto__ === Object.prototype)
; - 当试图失去一个对象的某个属性时,如果这个对象自身没有这个属性,那么会去它的
__proto__
(即它的构造函数的 prototype)中去寻找;
- 原型对象:prototype 在 js 中,函数对象其中一个属性:原型对象 prototype。一般对象没有 prototype 属性,但有
__proto__
属性。原型的作用就是给这个类的每一个对象都增加一个对立的办法,在原型中定义的办法和属性都是被所以实例对象所共享。
var person = function(name){this.name = name};
person.prototype.getName=function(){ // 通过 person.prototype 设置函数对象属性
return this.name;
}
var crazy= new person(‘crazyLee’);
crazy.getName(); // crazyLee//crazy 继承上属性
- 原型链 当试图失去一个对象 f 的某个属性时,如果这个对象自身没有这个属性,那么会去它的
__proto__
(即它的构造函数的 prototype)obj.__proto__
中去寻找;当obj.__proto__
也没有时,便会在obj.__proto__.__proto__
(即 obj 的构造函数的 prototype 的构造函数的 prototype)中寻找;
[图片上传失败 …(image-6e6b90-1570253698607)]
<figcaption></figcaption>
二、instanceof 的底层实现原理,手动实现一个 instanceof
function instance_of(L, R) {
//L 示意左表达式,R 示意右表达式
var O = R.prototype // 取 R 的显示原型
L = L.__proto__ // 取 L 的隐式原型
while (true) {if (L === null) return false
if (O === L)
// 当 O 显式原型 严格等于 L 隐式原型 时,返回 true
return true
L = L.__proto__
}
}
三、实现继承的几种形式以及他们的优缺点
原型链继承
原型链继承的根本思维是利用原型让 一个援用类型继承另一个援用类型的属性和办法。
function SuperType() {
this.name = 'yanxugong'
this.colors = ['pink', 'blue', 'green']
}
SuperType.prototype.getName = function() {return this.name}
function SubType() {this.age = 22}
SubType.prototype = new SuperType()
SubType.prototype.getAge = function() {return this.age}
SubType.prototype.constructor = SubType
let instance1 = new SubType()
instance1.colors.push('yellow')
console.log(instance1.getName()) // 'yanxugong'
console.log(instance1.colors) // ["pink", "blue", "green", "yellow"]
let instance2 = new SubType()
console.log(instance2.colors) // ["pink", "blue", "green", "yellow"]
毛病:
- 通过原型来实现继承时,原型会变成另一个类型的实例,原先的实例属性变成了当初的原型属性,该原型的援用类型属性会被所有的实例共享。
- 在创立子类型的实例时,没有方法 在不影响所有对象实例的状况下 给超类型的构造函数中传递参数。
借用构造函数
借用构造函数的技术,其根本思维为: 在子类型的构造函数中调用超类型构造函数。
function SuperType(name) {
this.name = name
this.colors = ['pink', 'blue', 'green']
this.getColors = function() {return this.colors}
}
SuperType.prototype.getName = function() {return this.name}
function SubType(name) {SuperType.call(this, name)
this.age = 22
}
let instance1 = new SubType('yanxugong')
instance1.colors.push('yellow')
console.log(instancel.colors) // ['pink','blue','green','yellow']
console.log(instancel.getColors()) // ["pink", "blue", "green", "yellow"]
console.log(instancel.getName) // undefined
let instance2 = new SubType('Jack')
console.log(instance2.colors) // ['pink','blue','green']
console.log(instance2.getColors()) // ["pink", "blue", "green"]
console.log(instance2.getName) // undefined
长处:
- 能够向超类传递参数
- 解决了原型中蕴含援用类型值被所有实例共享的问题
毛病:
- 办法都在构造函数中定义,函数复用无从谈起,另外超类型原型中定义的办法对于子类型而言都是不可见的。
组合继承
组合继承指的是将原型链和借用构造函数技术组合到一块,从而施展二者之长的一种继承模式。
基本思路:
应用原型链实现对原型属性和办法的继承,通过借用构造函数来实现对实例属性的继承,既通过在原型上定义方法来实现了函数复用,又保障了每个实例都有本人的属性。
function SuperType(name) {
this.name = name
this.colors = ['pink', 'blue', 'green']
}
SuperType.prototype.getName = function() {return this.name}
function SubType(name, age) {SuperType.call(this, name)
this.age = age
}
SubType.prototype = new SuperType()
SubType.prototype.constructor = SubType
SubType.prototype.sayAge = function() {return this.age}
let instancel = new SubType('yanxugong', 20)
instancel.colors.push('yellow')
console.log(instancel.colors) // ['pink','blue','green','yellow']
console.log(instancel.sayAge()) // 20
console.log(instancel.getName()) // yanxugong
let instance2 = new SubType('Jack', 18)
console.log(instance2.colors) // ['pink','blue','green']
console.log(instance2.sayAge()) // 18
console.log(instance2.getName()) // Jack
console.log(new SuperType('po'))
毛病:
- 无论什么状况下,都会调用两次超类型构造函数:一次是在创立子类型原型的时候,另一次是在子类型构造函数外部。
长处:
- 能够向超类传递参数
- 每个实例都有本人的属性
- 实现了函数复用
原型式继承
原型继承的根本思维:
借助原型能够基于已有的对象创立新对象,同时还不用因而创立自定义类型。
function object(o) {function F() {}
F.prototype = o
return new F()}
在 object()函数外部,新建一个临时性的构造函数,而后将传入的对象作为这个构造函数的原型,最初返回了这个长期类型的一个新实例,从实质上讲,object()对传入的对象执行了一次浅拷贝。
ECMAScript5 通过新增 Object.create()办法标准了原型式继承。这个办法接管两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额定属性的对象 (能够笼罩原型对象上的同名属性),在传入一个参数的状况下,Object.create() 和 object()办法的行为雷同。
var person = {
name: 'yanxugong',
hobbies: ['reading', 'photography']
}
var personl = Object.create(person)
personl.name = 'jack'
personl.hobbies.push('coding')
var person2 = Object.create(person)
person2.name = 'Echo'
person2.hobbies.push('running')
console.log(person.hobbies) // ["reading", "photography", "coding", "running"]
console.log(person.name) // yanxugong
console.log(personl.hobbies) // ["reading", "photography", "coding", "running"]
console.log(personl.name) // jack
console.log(person2.hobbies) // ["reading", "photography", "coding", "running"]
console.log(person2.name) // Echo
在没有必要创立构造函数,仅让一个对象与另一个对象放弃类似的状况下,原型式继承是能够胜任的。
毛病:
- 同原型链实现继承一样,蕴含援用类型值的属性会被所有实例共享。
寄生式继承
寄生式继承是与原型式继承严密相干的一种思路。寄生式继承的思路与寄生构造函数和工厂模式相似,即创立一个仅用于封装继承过程的函数,该函数在外部已某种形式来加强对象,最初再像真地是它做了所有工作一样返回对象。
function object(o) {function F() {}
F.prototype = o
return new F()}
function createAnother(original) {var clone = object(original) // 通过调用函数创立一个新对象
clone.sayHi = function() {
// 以某种形式加强这个对象
console.log('hi')
}
return clone // 返回这个对象
}
var person = {
name: 'yanxugong',
hobbies: ['reading', 'photography']
}
var personl = createAnother(person)
personl.sayHi() // hi
personl.hobbies.push('coding')
console.log(personl.hobbies) // ["reading", "photography", "coding"]
console.log(person) // {hobbies:["reading", "photography", "coding"],name: "yanxugong"}
基于 person 返回了一个新对象 personl,新对象不仅具备 person 的所有属性和办法,而且还有本人的 sayHi()办法。在思考对象而不是自定义类型和构造函数的状况下,寄生式继承也是一种有用的模式。
毛病:
- 应用寄生式继承来为对象增加函数,会因为不能做到函数复用而效率低下。
- 同原型链实现继承一样,蕴含援用类型值的属性会被所有实例共享。
寄生组合式继承
所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成模式来继承办法。
基本思路:
不用为了指定子类型的原型而调用超类型的构造函数,咱们须要的仅是超类型原型的一个正本,实质上就是应用寄生式继承来继承超类型的原型,而后再将后果指定给子类型的原型。寄生组合式继承的基本模式如下所示:
function object(o) {function F() {}
F.prototype = o
return new F()}
function inheritPrototype(subType, superType) {var prototype = object(superType.prototype) // 创建对象
prototype.constructor = subType // 加强对象
subType.prototype = prototype // 指定对象
}
- 创立超类型原型的一个正本
- 为创立的正本增加 constructor 属性
- 将新创建的对象赋值给子类型的原型
至此,咱们就能够通过调用 inheritPrototype 来替换为子类型原型赋值的语句:
function object(o) {function F() {}
F.prototype = o
return new F()}
function inheritPrototype(subType, superType) {var prototype = object(superType.prototype) // 创建对象
prototype.constructor = subType // 加强对象
subType.prototype = prototype // 指定对象
}
function SuperType(name) {
this.name = name
this.colors = ['pink', 'blue', 'green']
}
SuperType.prototype.getName = function() {return this.name}
function SubType(name, age) {SuperType.call(this, name)
this.age = age
}
inheritPrototype(SubType, SuperType)
SubType.prototype.sayAge = function() {return this.age}
let instancel = new SubType('yanxugong', 20)
instancel.colors.push('yellow')
console.log(instancel.colors) // ['pink','blue','green','yellow']
console.log(instancel.sayAge()) // 20
console.log(instancel.getName()) // yanxugong
let instance2 = new SubType('Jack', 18)
console.log(instance2.colors) // ['pink','blue','green']
console.log(instance2.sayAge()) // 18
console.log(instance2.getName()) // Jack
console.log(new SuperType('po'))
长处:
- 只调用了一次超类构造函数,效率更高。防止在 SuberType.prototype 下面创立不必要的、多余的属性,与其同时,原型链还能放弃不变。
- 因而寄生组合继承是援用类型最感性的继承范式。
四、至多说出一种开源我的项目 (如 Node) 中利用原型继承的案例
Vue.extend(options)’)
-
参数:
{Object} options
- 用法:
应用根底 Vue 结构器,创立一个“子类”。参数是一个蕴含组件选项的对象。
data
选项是特例,须要留神 – 在 Vue.extend()
中它必须是函数
<div id="mount-point"></div>
// 创立结构器
var Profile = Vue.extend({template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
data: function() {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
})
// 创立 Profile 实例,并挂载到一个元素上。new Profile().$mount('#mount-point')
后果如下:
<p>Walter White aka Heisenberg</p>
为什么应用 extend
在 vue 我的项目中,咱们有了初始化的根实例后,所有页面基本上都是通过 router 来治理,组件也是通过 import
来进行部分注册,所以组件的创立咱们不须要去关注,相比 extend
要更省心一点点。然而这样做会有几个毛病:
- 组件模板都是当时定义好的,如果我要从接口动静渲染组件怎么办?
- 所有内容都是在
#app
下渲染,注册组件都是在以后地位渲染。如果我要实现一个相似于window.alert()
提醒组件要求像调用 JS 函数一样调用它,该怎么办?
这时候,Vue.extend + vm.$mount
组合就派上用场了。
五、能够形容 new 一个对象的具体过程,手动实现一个 new 操作符
先看看 new 操作符都干了什么事件,有哪些操作?通过上面的代码来进行思考:
// 新建一个类(构造函数)function Otaku(name, age) {
this.name = name
this.age = age
// 本身的属性
this.habit = 'pk'
}
// 给类的原型上增加属性和办法
Otaku.prototype.strength = 60
Otaku.prototype.sayYourName = function() {console.log('I am' + this.name)
}
// 实例化一个 person 对象
const person = new Otaku('乔峰', 5000)
person.sayYourName() // I am 乔峰
console.log(person) // 打印出结构进去的实例
解析
从控制台打印进去的后果咱们能够看出 new 操作符大略做了一下几件事件:
- 返回(产生)了一个新的对象
- 拜访到了类 Otaku 构造函数里的属性
- 拜访到 Otaku 原型上的属性和办法 并且设置了 this 的指向(指向新生成的实例对象)
通过下面的剖析展现,能够晓得 new 团伙外面肯定有 Object 的参加,不然对象的产生就有点说不清了。先来边写写:
// 须要返回一个对象 借助函数来实现 new 操作
// 传入须要的参数:类 + 属性
const person = new Otaku('乔峰', 5000)
const person1 = objectFactory(Otaku, '鸠摩智', 5000)
// 开始来实现 objectFactory 办法
function objectFactory(obj, name, age) {}
// 这种办法将本身写死了 如此他只能结构以 obj 为原型,并且只有 name 和 age 属性的 obj
// 在 js 中 函数因为 arguments 使得函数参数的写法异样灵便,在函数外部能够通过 arguments 来取得函数的参数
function objectFactory() {console.log(arguements) //{'0': [Function: Otaku], '1': '鸠摩智', '2': 5000 }
// 通过 arguments 类数组打印出的后果,咱们能够看到其中蕴含了构造函数以及咱们调用 objectfactory 时传入的其余参数
// 接下来就是要想如何失去其中这个构造函数和其余的参数
// 因为 arguments 是类数组,没有间接的办法能够供其应用,咱们能够有以下两种办法:
// 1. Array.from(arguments).shift(); // 转换成数组 应用数组的办法 shift 将第一项弹出
// 2. [].shift().call(arguments); // 通过 call() 让 arguments 可能借用 shift 办法
const Constructor = [].shift.call(arguments)
const args = arguments
// 新建一个空对象 纯净无邪
let obj = new Object()
// 接下来的想法 给 obj 这个新生对象的原型指向它的构造函数的原型
// 给构造函数传入属性,留神:构造函数的 this 属性
// 参数传进 Constructor 对 obj 的属性赋值,this 要指向 obj 对象
// 在 Coustructor 外部手动指定函数执行时的 this 应用 call、apply 实现
let result = Constructor.apply(obj, arguments)
// 确保 new 进去的是一个对象
return typeof result === 'object' ? result : obj
}
- 下面的代码正文太多,剔除正文当前的代码:
function objectFactory() {let Constructor = [].shift.call(arguments)
const obj = new Object()
obj.__proto__ = Conctructor.prototype
let result = Constructor.apply(obj, arguments)
return typeof result === 'object' ? result : obj
}
- 还有另外一种操作:
function myNew(Obj, ...args) {var obj = Object.create(Obj.prototype) // 应用指定的原型对象及其属性去创立一个新的对象
Obj.apply(obj, args) // 绑定 this 到 obj, 设置 obj 的属性
return obj // 返回实例
}
六、了解 ES6 class 结构以及继承的底层实现原理
ES6 class 应用
javascript 应用的是原型式继承,咱们能够通过原型的个性实现类的继承,
ES6 为咱们提供了像面向对象继承一样的语法糖。
class Parent {constructor(a) {this.filed1 = a}
filed2 = 2
func1 = function() {}
}
class Child extends Parent {constructor(a, b) {super(a)
this.filed3 = b
}
filed4 = 1
func2 = function() {}
}
上面咱们借助 babel
来探索 ES6 类和继承的实现原理。
class 的实现
转换前:
class Parent {constructor(a) {this.filed1 = a}
filed2 = 2
func1 = function() {}
}
转换后:
function _classCallCheck(instance, Constructor) {if (!(instance instanceof Constructor)) {throw new TypeError('Cannot call a class as a function')
}
}
var Parent = function Parent(a) {_classCallCheck(this, Parent)
this.filed2 = 2
this.func1 = function() {}
this.filed1 = a
}
可见 class 的底层仍然是构造函数:
- 调用 \_classCallCheck 办法判断以后函数调用前是否有 new 关键字。
构造函数执行前有 new 关键字,会在构造函数外部创立一个空对象,将构造函数的
proptype
指向这个空对象的__proto__
, 并将 this 指向这个空对象。如上,\_classCallCheck 中:this instanceof Parent 返回 true。若构造函数后面没有 new 则构造函数的 proptype 不会不呈现在 this 的原型链上,返回 false。
- 将 class 外部的变量和函数赋给 this。
- 执行 constuctor 外部的逻辑。
- return this (构造函数默认在最初咱们做了)。
继承实现
转换前:
class Child extends Parent {constructor(a, b) {super(a)
this.filed3 = b
}
filed4 = 1
func2 = function() {}
}
转换后:
咱们先看 Child 外部的实现,再看外部调用的函数是怎么实现的:
var Child = (function(_Parent) {_inherits(Child, _Parent)
function Child(a, b) {_classCallCheck(this, Child)
var _this = _possibleConstructorReturn(
this,
(Child.__proto__ || Object.getPrototypeOf(Child)).call(this, a)
)
_this.filed4 = 1
_this.func2 = function() {}
_this.filed3 = b
return _this
}
return Child
})(Parent)
- 调用
_inherits
函数继承父类的 proptype。
_inherits
外部实现:
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)
}
(1) 校验父构造函数。
(2) 典型的寄生继承:用父类构造函数的 proptype 创立一个空对象,并将这个对象指向子类构造函数的 proptype。
(3) 将父构造函数指向子构造函数的__proto__
(这步是做什么的不太明确,感觉没什么意义。)
- 用一个闭包保留父类援用,在闭包外部做子类结构逻辑。
- new 查看。
- 用以后 this 调用父类构造函数。
var _this = _possibleConstructorReturn(
this,
(Child.__proto__ || Object.getPrototypeOf(Child)).call(this, a)
)
这里的 Child.proto || Object.getPrototypeOf(Child)
实际上是父构造函数(\_inherits 最初的操作),而后通过 call 将其调用方改为以后 this,并传递参数。(这里感觉能够间接用参数传过来的 Parent)
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
}
校验 this 是否被初始化,super 是否调用,并返回父类曾经赋值完的 this。
- 将行子类 class 外部的变量和函数赋给 this。
- 执行子类 constuctor 外部的逻辑。
可见,ES6 实际上是为咱们提供了一个“组合寄生继承”的简略写法。
super
super 代表父类构造函数。
super.fun1()
等同于 Parent.fun1()
或 Parent.prototype.fun1()
。
super()
等同于 Parent.prototype.construtor()
当咱们没有写子类构造函数时:
var Child = (function(_Parent) {_inherits(Child, _Parent)
function Child() {_classCallCheck(this, Child)
return _possibleConstructorReturn(
this,
(Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments)
)
}
return Child
})(Parent)
可见默认的构造函数中会被动调用父类构造函数,并默认把以后 constructor
传递的参数传给了父类。
所以当咱们申明了 constructor
后必须被动调用 super()
, 否则无奈调用父构造函数,无奈实现继承。
典型的例子就是 React 的 Component 中,咱们申明 constructor
后必须调用 super(props)
,因为父类要在构造函数中对 props 做一些初始化操作。