深入浅析js原型链和vue构造函数

2018-10-25 50 俗的太不一样

一、什么是原型链?

深入浅析js原型链和vue构造函数

简单回顾下构造函数,原型和实例的关系:

     每个构造函数(constructor)都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针,而实例(instance)都包含一个指向原型对象的内部指针.

然鹅,在js对象里有这么一个规则:

如果试图引用对象(实例instance)的某个属性,会首先在对象内部寻找该属性,直至找不到,然后才在该对象的原型(instance.prototype)里去找这个属性.

少废话,先来看个例子:

function Fun1 () {
  this.win = "skt"
 }
 Fun1.prototype.getVal = function () {
  return this.win
 }
 function Fun2 () {
  this.other_win = "rng"
 }
 Fun2.prototype = new Fun1 ()
 Fun2.prototype.getOtherVal = function () {
  return this.other_win
 }
 let instance = new Fun2()
 console.log(instance.getVal()) //skt

在上述例子中,有一个很有意思的操作,我们让原型对象指向了另一个类型的实例,即: constructor1.property = instance2

那么他是怎么找到instance.getVal()的?这中间又发生了什么?

1).首先会在instance1内部属性中找一遍;

2).接着会在instance1.__proto__(constructor1.prototype)中找一遍,而constructor1.prototype 实际上是instance2, 也就是说在instance2中寻找该属性;

3).如果instance2中还是没有,此时程序不会灰心,它会继续在instance2.__proto__(constructor2.prototype)中寻找...直至Object的原型对象

    搜索轨迹: instance1--> instance2 --> constructor2.prototype…-->Object.prototype

这种搜索的轨迹,形似一条长链, 又因prototype在这个游戏规则中充当链接的作用,于是我们把这种实例与原型的链条称作 原型链

二、prototype 和 __proto__ 都是个啥?

1.prototype是函数才有的属性

let fun = function () {}
 console.log(fun.prototype) // object
 console.log(fun.__proto__) // function

2.__proto__是对象具有的属性,但__proto__不是一个规范的属性,对应的标准属性是 [[Prototype]]

let obj = {}
 console.log(obj.prototype) // underfined
 console.log(obj.__proto__) // object

我们可以把__proto__理解为构造器的原型,大多数情况下 __proto__ === constructor.prototype      ( Object.create()除外 )

三、new又是个什么鬼?

我们都知道new是一个实例化的过程,那么他是怎么实例化的?下面我们来看一个简单的例子:

function Fun() {
  this.team = "rng"
 }
 let f = new Fun()
 console.log(f.team) // rng

上述代码中,我们通过new命令实例化了一个叫Fun的函数并赋值给f,这个新生成的实例对象f从构造函数Fun中得到了team属性,其实构造函数内部的this,就代表了新生成的实例对象,所以我们打印f.team的值就取到了rng这个值

这又是哪门子原理?答案如下?

1.创建一个空对象,作为将要返回的对象实例。
2.将这个空对象的原型,指向构造函数的prototype属性。
3.将这个空对象赋值给函数内部的this关键字。
4.开始执行构造函数内部的代码

也就是说,构造函数内部,this指的是一个新生成的空对象,所有针对this的操作,都会发生在这个空对象上。这也是为什么构造函数叫"构造函数"的原因,就是操作一个空对象(即this对象),将其“构造”为所需要的样子。

如果我不加new呢?

function Fun() {
  this.team = "rng"
 }
 let f = Fun()
 console.log(f) // undefined
 console.log(team) // rng

我们可以看出上面打印f为undefined,而team却有值,这又是为什么?

其实在这种情况下,构造函数就变成了普通的函数,而且不会被实例.而此时的this指向了全局,team就变成了全局变量,因此我们取到了值

四、 __proto__指向哪?

说到__proto__的指向问题,还得取决于该对象创建时的实现方式.

辣么,到底有那些实现方式?

1.字面量方式

let obj = {}
 console.log(obj.__proto__) // object
 console.log(obj.__proto__ === obj.constructor.prototype) // true 证明用字面量创建的函数,他的__proto__ 等于 该对象构造器的原型

2.构造器方式

function Func () {}
 let a = new Func()
 console.log(a.__proto__) // object
 console.log(a.__proto__ === a.constructor.prototype) // true

3.Object.create()方式

let obj1 = {name:"rng"}
 let obj2 = Object.create(obj1)
 console.log(obj2.__proto__) //{name: "rng"}
 console.log(obj2.__proto__ === obj2.constructor.prototype) // false

注: Object.create(prototype, descriptors) 创建一个具有指定原型且可选择性地包含指定属性的对象

五、如何确定原型和实例的关系?

想要确定原型和实例的关系,坦率的讲,有两种方式:  instance  和  isPrototype()

1.instanceof

我们用这个操作符来测试实例(instance)与原型链中出现过的构造函数,如果出现过则返回true,反之则为false

来来来,我们来测试一下:

function Fun1 () {
  this.laji = "uzi"
 }
 function Fun2 () {
  this.strong = "faker"
 }
 Fun2.prototype = new Fun1()

 let fun2 = new Fun2 ()

 console.log(fun2 instanceof Fun1) // true
 console.log(fun2 instanceof Fun2) // true
 console.log(fun2 instanceof Object) // true

由于原型链的关系,我们可以说fun2是一个对象Object,Fun1或是Fun2中任何一个类型的实例,所以这三个结果都返回了true

2.isPrototype()

这个方法同样,只要是原型链中出现过的原型,该方法就会返回true,用法如下

console.log(Fun1.prototype.isPrototypeOf(fun2)) // true
 console.log(Fun2.prototype.isPrototypeOf(fun2)) // true
 console.log(Object.prototype.isPrototypeOf(fun2))// true

六、原型链的问题

什么?原型链还有问题?买了佛冷,why?

原因一: 当原型链中包含引用类型值的原型时,该引用类型值会被所有实例共享;

原因二:在创建子类型时,不能向超类型的构造函数中传递参数.

七、如何解决原型链问题?

1.借用构造函数,也叫经典继承

基本思想: 在子类型构造函数的内部调用超类型构造函数

函数只是在特定环境中执行的代码的对象,因此通过使用 apply() 和 call() 方法也可以在(将来)新创建的对象上执行构造函数

看例子:

function Father () {
  this.team = ["letme","mlxg"]
 }
 function Son () {
  Father.call(this)
 }
 let son = new Son()
 son.team.push("uzi")
 console.log(son.team)  // ["letme", "mlxg", "uzi"]
 let little_son = new Son() 
 console.log(little_son.team) // ["letme", "mlxg"]

我们可以看出,借用构造函数一举解决了原型链的两大问题:

其一, 保证了原型链中引用类型值的独立,不再被所有实例共享;

其二, 子类型创建时也能够向父类型传递参数.

但是还还还有一个问题,如果仅仅借用构造函数,那么将无法避免构造函数模式存在的问题:

方法都在构造函数中定义, 因此函数复用也就不可用了.而且超类型(如Father)中定义的方法,对子类型而言也是不可见的. so,借用构造函数的技术也很少单独使用.

2.组合继承

组合继承, 有时候也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥两者优点的一种继承模式.

基本思想: 使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承.

这样,既通过在原型上定义方法实现了函数复用,又能保证每个实例都有它自己的属性.

接着看例子:

function Father (team) {
  this.team = team
  this.people = ["mlxg","letme"]
 }
 Father.prototype.sayTeam = function () {
  return console.log(this.team)
 }
 function Son (team,age) {
  this.age = age
  Father.call(this,team)
 }
 Son.prototype = new Father()
 Son.prototype.sayAge = function () {
  return console.log(this.age)
 }
 let son = new Son("faker",8)
 son.people.push("uzi")
 console.log(son.people) // ["mlxg", "letme", "uzi"]
 son.sayAge()    //8
 son.sayTeam()    // faker
 let little_son = new Son("bang",3)
 console.log(little_son.people) // ["mlxg", "letme"] 
 little_son.sayAge()    // 3
 little_son.sayTeam()   // bang

我们可以看出,组合继承既保证了引用类型不再被所有实例所共享,也能够让子类型创建时向父类型传参,同时,原型中的方法又能够被复用,可以说是避免了原型链中的两大问题以及借用构造函数的缺陷,因此他也是js中最常用的继承方式,而且

instanceof 和 isPrototypeOf( )也能用于识别基于组合继承创建的对象.

3.原型继承

基本思想: 借助原型可以基于已有的对象创建新对象, 同时还不必因此创建自定义类型

绳么意思?

比如我们在fun()函数内部, 先创建一个临时性的构造函数, 然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例.

function fun(o){
   function F(){}
   F.prototype = o;
   return new F();
  }
  let obj = {arr:[11,22]
  fun(obj).arr.push(33)
  console.log(fun(obj).arr) // [11,22,33]

在这个例子中,可以作为另一个对象基础的是obj对象,于是我们把它传入到fun()函数中,然后该函数就会返回一个新对象. 这个新对象将arr作为原型,因此它的原型中就包含引用类型值属性. 然后我们向该属性中又增加了一个元素,所以我们能够将它打印出来

       *在原型继承中, 包含引用类型值的属性始终都会共享相应的值, 就像使用原型模式一样.

4.寄生式继承

基本思想:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象

function fun(o){
  function F(){}
  F.prototype = o;
  return new F();
 }
 let obj = {a:[11,22]}
 function createAnother(z) {
  // 通过调用函数创建一个新对象
  var clone = fun(z);
  clone.sayHi = function () {
   alert("hi");
  }
  return clone;
 }

createAnother(obj)

上面的例子中,我们把obj传入createAnother()函数中,返回的新对象clone不仅拥有了该属性,而且还被增强了,拥有了sayHi()方法;

等一下,这里要注意: 使用寄生式继承来为对象添加函数, 会由于不能做到函数复用而降低效率;这一点与构造函数模式类似.

5.寄生组合式继承

前面讲过,组合继承是 JavaScript 最常用的继承模式; 不过, 它也有自己的不足. 组合继承最大的问题就是无论什么情况下,都会调用两次父类构造函数: 一次是在创建子类型原型的时候, 另一次是在子类型构造函数内部. 寄生组合式继承就是为了降低调用父类构造函数的开销而诞生的

基本思想:不必为了指定子类型的原型而调用超类型的构造函数

function inheritPrototype(subType, superType) {
  var protoType = Object.create(superType.prototype); //创建对象
  protoType.constructor = subType;      //增强对象
  subType.prototype = protoType;       //指定对象
 }
 function Father(name) {
  this.name = name;
  this.colors = ["red", "blue", "green"];
 }
 Father.prototype.sayName = function () {
  console.log(this.name);
 }
 function Son(name, age) {
  Father.call(this, name);
  this.age = age;
 }
 inheritPrototype(Son, Father)
 Son.prototype.sayAge = function () {
  console.log(this.age);
 }
 var instance = new Son("uzi", 3);
 instance.sayName(); //uzi
 instance.sayAge(); //3

 

inheritPrototype函数接收两个参数:子类型构造函数和超类型构造函数。

1. 创建超类型原型的副本。

2. 为创建的副本添加constructor属性,弥补因重写原型而失去的默认的constructor属性

3. 将新创建的对象(即副本)赋值给子类型的原型

inheritPrototype的高效率体现在它没有调用superClass构造函数,因此避免了在subClass.prototype上面创建不必要多余的属性. 同时,原型链还能保持不变,可以说是相当奈斯

由于寄生组合式继承,集寄生式继承和组合继承的优点于一身,是实现基于类型继承的最有效方法.

八.vue构造函数

我们在使用的vue的时候,经常会用new操作符去将他实例化,这说明vue也是一个构造函数,那么他是如何被创建的呢?我怀着无比激动的心情clone了vue的源码,仔细研究了一番

vue源码地址

我首先找到了src/core/instance/index.js文件,打开一看,惊了


在第八行代码中,创建了一个Vue的函数,这不就是Vue的构造函数么,而且在12行的警告中我更加肯定了,他说:Vue是一个构造函数,应该使用“new”关键字调用
然后他在下面,他分别在

initMixin()

stateMixin()

eventsMixin()

lifecycleMixin()

renderMixin()

这五个方法中讲Vue作为形参传入,最后将Vue导出.

那么这五个方法是干什么的呢?我们先来看看initMixin()方法,打开./init.js文件,找到该方法


其他的代码我们先不管,我们就看该方法的前几行,他在Vue的原型中注入了_init方法,这个方法有点眼熟,我们好像在哪见过,对,就是刚才的index.js文件中

深入浅析js原型链和vue构造函数

这个this_init(options)看上去像是一个内部初始化的一个方法,而option应该就是初始化时的一些配置项了,在Vue被实例化的时候,this._init()方法就会执行

接下来,我们来看一下./state.js文件,找到stateMixin方法

深入浅析js原型链和vue构造函数

总结

以上所述是小编给大家介绍的js原型链和vue构造函数,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

展开阅读全文

更多Javascript文章

基于jquery1.4.2的仿flash超炫焦点图播放效果
Apr 20 37
JavaScript设置获取和设置属性的方法
Mar 04 43
javascript获取重复次数最多的字符
Jul 08 33
JavaScript实现添加及删除事件的方法小结
Aug 04 47
深入理解JS addLoadEvent函数
May 20 117
jQuery实现的简单歌词滚动功能示例
Jan 07 37
vue实现图片懒加载的方法分析
Feb 05 52
手机访问当前页面