js原型链和vue构造函数
这篇文章首先会讲到原型链以及原型链的一些概念,然后会通过分析vue的源码,来看一下vue的构造函数是如何被创建的,now we go!
一、什么是原型链?
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
搜索轨迹: instance1--> instance2 --> constructor2.prototype…-->object.prototype
let fun = function () {} console.log(fun.prototype) // object console.log(fun.__proto__) // function
let obj = {} console.log(obj.prototype) // underfined console.log(obj.__proto__) // object
function fun() { this.team = "rng" } let f = new fun() console.log(f.team) // rng
上述代码中,我们通过new命令实例化了一个叫fun的函数并赋值给f,这个新生成的实例对象f从构造函数fun中得到了team属性,其实构造函数内部的this,就代表了新生成的实例对象,所以我们打印f.team的值就取到了rng这个值
这又是哪门子原理?答案如下?
- 创建一个空对象,作为将要返回的对象实例。
- 将这个空对象的原型,指向构造函数的
prototype
属性。 - 将这个空对象赋值给函数内部的
this
关键字。 - 开始执行构造函数内部的代码
也就是说,构造函数内部,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__的指向问题,还得取决于该对象创建时的实现方式.
辣么,到底有那些实现方式?
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
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) 创建一个具有指定原型且可选择性地包含指定属性的对象
五、如何确定原型和实例的关系?
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
console.log(fun1.prototype.isprototypeof(fun2)) // true console.log(fun2.prototype.isprototypeof(fun2)) // true console.log(object.prototype.isprototypeof(fun2))// true
六、原型链的问题
什么?原型链还有问题?买了佛冷,why?让我们来看一下:
问题一: 当原型链中包含引用类型值的原型时,该引用类型值会被所有实例共享;
问题二:在创建子类型时,不能向超类型的构造函数中传递参数.
七、如何解决原型链问题?
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"]
组合继承, 有时候也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥两者优点的一种继承模式.
基本思想: 使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承.
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中最常用的继承方式,而且
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]
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; }
上面的例子中,我们把obj传入createanother()函数中,返回的新对象clone不仅拥有了该属性,而且还被增强了,拥有了sayhi()方法;
等一下,这里要注意: 使用寄生式继承来为对象添加函数, 会由于不能做到函数复用而降低效率;这一点与构造函数模式类似.
5.寄生组合式继承
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的时候,经常会用new操作符去将他实例化,这说明vue也是一个构造函数,那么他是如何被创建的呢?我怀着无比激动的心情clone了vue的源码,仔细研究了一番
vue源码地址
我首先找到了src/core/instance/index.js文件,打开一看,惊了
在第八行代码中,创建了一个vue的函数,这不就是vue的构造函数么,而且在12行的警告中我更加肯定了,他说:vue是一个构造函数,应该使用“new”关键字调用
然后他在下面,他分别在
那么这五个方法是干什么的呢?我们先来看看initmixin()方法,打开./init.js文件,找到该方法
其他的代码我们先不管,我们就看该方法的前几行,他在vue的原型中注入了_init方法,这个方法有点眼熟,我们好像在哪见过,对,就是刚才的index.js文件中
这个this_init(options)看上去像是一个内部初始化的一个方法,而option应该就是初始化时的一些配置项了,在vue被实例化的时候,this._init()方法就会执行
接下来,我们来看一下./state.js文件,找到statemixin方法
我们们先看最后两行,他使用object.defineproperty方法里面传了三个参数:vue的原型,$data和$props以及datadef和propsdef,object.defineproperty顾名思义就是在对象里定义原型,所以这个方法应该就是将$data和$props注入vue的原型中.这两个属性的定义分别写在了 datadef
以及 propsdef
这两个对象里,我们来看一下这两个对象的定义,首先是 get
:
从中我们可以看出,datadef.get方法返回了this._data,也就是说,$data 属性实际上代理的是 _data
这个实例属性,而同理,$props
代理的是 _props
这个实例属性。然后有一个是否为生产环境的判断,如果不是生产环境的话,就为 $data
和 $props
这两个属性设置一下 set,给了你一个提示,
避免替换实例根$data。' + "使用嵌套数据属性",实际上就是想告诉你:别碰我.所以说,$data和$rop都是只读属性.
然后,他又向vue的原型里注入了$set啊,$delete啊还有$watch,这些你在看vue的api的时候应该见过吧
下面,我们看下一个方法eventsmixin .打开./events.js文件
不难发现,eventsmixin方法在vue的原型中分别写入了$on,$once,$off和$emit这四个方法
下一个是lifecyclemixin方法,打开./lifecycle.js文件找到该方法,这个方法在vue.prototype上添加了三个方法:_update,$forceupdate以及$distroy
最后一个就是 rendermixin
方法了,它在 render.js
文件中
这个方法的一开始以 vue.prototype
为参数调用了 installrenderhelpers
函数,我们可以从这个文件开始看到这个函数来自./render-helpers/index.js
文件,打开这个文件找到 installrenderhelpers
函数:
以上代码就是 installrenderhelpers
函数的源码,可以发现,这个函数的作用就是在 vue.prototype
上添加一系列方法,这些方法貌似render函数被创建时需要用到的一些方法.
rendermixin
方法在执行完 installrenderhelpers
函数之后,又在 vue.prototype
上添加了两个方法,分别是:$nexttick
和 _render
,至此,instance/index.js
文件中的代码就运行完毕了(所谓的运行,是指执行 npm run dev
命令时构建的运行)。
我们大致可以得出一个结论:vue官方通把vue的一些方法和属性做了分类,通过不同*mixin方法,将这些方法挂载到了vue原型中,而每个 *mixin
方法的作用其实就是包装 vue.prototype
,这就是vue构造函数被创建的过程.
以上理解可能有的不到位或者错误的地方,欢迎留言指正,3q.
上一篇: javaSE练习3——数组