在函数内建立一个对象,给对象赋予属性及方法再将对象返回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 指代vue
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 属性对属性进行定义,可让全部对象实例共享它所包含的属性及方法。java
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()) // 女
原型模式+构造函数模式。这种模式中,构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性express
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__
属性(隐式原型),属性值是一个普通对象;(obj.__proto__ === Object.prototype)
;__proto__
(即它的构造函数的 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继承上属性
__proto__
(即它的构造函数的 prototype)obj.__proto__
中去寻找;当 obj.__proto__
也没有时,便会在 obj.__proto__.__proto__
(即 obj 的构造函数的 prototype 的构造函数的 prototype)中寻找;[图片上传失败...(image-6e6b90-1570253698607)]api
<figcaption></figcaption>数组
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__ } }
原型链继承的基本思想是利用原型让一个引用类型继承另外一个引用类型的属性和方法。babel
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"]
缺点:闭包
借用构造函数的技术,其基本思想为:在子类型的构造函数中调用超类型构造函数。app
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 // 指定对象 }
至此,咱们就能够经过调用 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'))
优势:
参数:
{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>
在 vue 项目中,咱们有了初始化的根实例后,全部页面基本上都是经过 router 来管理,组件也是经过 import
来进行局部注册,因此组件的建立咱们不须要去关注,相比 extend
要更省心一点点。可是这样作会有几个缺点:
#app
下渲染,注册组件都是在当前位置渲染。若是我要实现一个相似于 window.alert()
提示组件要求像调用 JS 函数同样调用它,该怎么办?Vue.extend + vm.$mount
组合就派上用场了。先看看 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 操做符大概作了一下几件事情:
经过上面的分析展现,能够知道 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 // 返回实例 }
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 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 的底层依然是构造函数:
构造函数执行前有 new 关键字,会在构造函数内部建立一个空对象,将构造函数的proptype
指向这个空对象的__proto__
,并将 this 指向这个空对象。如上,\_classCallCheck 中:this instanceof Parent 返回 true。若构造函数前面没有 new 则构造函数的 proptype 不会不出如今 this 的原型链上,返回 false。
转换前:
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__
(这步是作什么的不太明确,感受没什么意义。)
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。
可见,ES6 其实是为咱们提供了一个“组合寄生继承”的简单写法。
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 作一些初始化操做。