深入浅析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文件中

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

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

总结

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

(0)

相关推荐

  • JavaScript继承基础讲解(原型链、借用构造函数、混合模式、原型式继承、寄生式继承、寄生组合式继承)

    说好的讲解JavaScript继承,可是迟迟到现在讲解.废话不多说,直接进入正题. 既然你想了解继承,证明你对JavaScript面向对象已经有一定的了解,如还有什么不理解的可以参考<面向对象JS基础讲解,工厂模式.构造函数模式.原型模式.混合模式.动态原型模式>,接下来讲一般通过那些方法完成JavaScript的继承. 原型链 JavaScript中实现继承最简单的方式就是使用原型链,将子类型的原型指向父类型的实例即可,即"子类型.prototype = new 父类型();&qu

  • 基于JavaScript实现继承机制之构造函数+原型链混合方式的使用详解

    构造函数.原型实现继承的缺陷 首先来分析构造函数和原型链两种实现继承方式的缺陷: 构造函数(对象冒充)的主要问题是必须使用构造函数方式,且无法继承通过原型定义的方法,这不是最好的选择.不过如果使用原型链,就无法使用带参数的构造函数了.开发者如何选择呢?答案很简单,两者都用. 构造函数+原型混合方式 这种继承方式使用构造函数定义类,并非使用任何原型.创建类的最好方式是用构造函数定义属性,用原型定义方法.这种方式同样适用于继承机制,用对象冒充继承构造函数的属性,用原型链继承 prototype 对象

  • JavaScript中构造函数与原型链之间的关系详解

    在Javascript中不存在class的概念,它的class概念是通过构造函数(constructor)与原型链(prototype)来实现. 1.构造函数(constructor):创建对象时的初始化对象,总是与new 关键是一同出现. 构造函数存在以下特点: 1.构造函数内的this 指向当前实例对象. 2.使用new 关键字实例化当前对象. 3.构造函数首字母大写,区分普通函数. 4.实例对象都可以继承构造函数中的属性和方法.但是,同一个对象实例之间,无法共享属性. 2.原型(proto

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

    一.什么是原型链? 简单回顾下构造函数,原型和实例的关系: 每个构造函数(constructor)都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针,而实例(instance)都包含一个指向原型对象的内部指针. 然鹅,在js对象里有这么一个规则: 如果试图引用对象(实例instance)的某个属性,会首先在对象内部寻找该属性,直至找不到,然后才在该对象的原型(instance.prototype)里去找这个属性. 少废话,先来看个例子: function Fun1 ()

  • JS精髓原型链继承及构造函数继承问题纠正

    目录 前言 原型链继承 构造函数继承 组合继承 前言 先从面向对象讲起,本瓜认为:面向对象编程,它的最大能力就是:复用! 咱常说,面向对象三大特点,封装.继承.多态. 这三个特点,以“继承”为核心.封装成类,是为了继承,继承之后再各自发展(重写),可理解为多态.所以,根本目的是为了继承,即“复用“! 如果你用 JavaScript 面向对象的能力来编程的话,能想到的,也只供使用的就是:基于原型. 因为这门语言设计就是这样,我们之前也提过:JavaScript的语言设计主要受到了Self(一种基于

  • 浅析JS原型继承与类的继承

    我们先看JS类的继承 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>JS类的继承</title> </head> <body> /* -- 类式继承 -- */ <script type="text/javascript"> //先声明一个超类 var Animal = function

  • js原型链与继承解析(初体验)

    首先定义一个对象obj,该对象的原型为obj._proto_,我们可以用ES5中的getPrototypeOf这一方法来查询obj的原型,我们通过判断obj的原型是否与Object.prototype相等来证明是否存在obj的原型,答案返回true,所以存在.然后我们定义一个函数foo(),任何一个函数都有它的prototype对象,即函数的原型,我们可以在函数的原型上添加任意属性,之后通过new一个实例化的对象可以共享其属性(下面的两个例子会详细介绍). function foo(){} fo

  • JS原型链怎么理解

    在谈原型链之前,我们首先要了解自定义函数与 Function 之间是什么关系,而构造函数.原型和实例之间又存在什么千丝万缕的关系呢?其实,所有的函数都是 Function 的实例.在构造函数上都有一个原型属性 prototype,该属性也是一个对象:那么在原型对象上有一个 constructor 属性,该属性指向的就是构造函数:而实例对象上有一个 _proto_ 属性,该属性也指向原型对象,并且该属性不是标准属性,不可以用在编程中,该属性用于浏览器内部使用. // _proto_ 在函数里有一个

  • 五句话帮你轻松搞定js原型链

    原型链是一种机制,指的是JavaScript每个对象包括原型对象都有一个内置的[[proto]]属性指向创建它的函数对象的原型对象,即prototype属性. 作用:原型链的存在,主要是为了实现对象的继承. 一. 记住以下5句话,轻松搞定js原型链 Function 和 Object 都是构造函数 所有的构造函数都是Function new出来的(Function自己new了自己):所有的原型对象都是Object new出来的(Object new了自己的原型对象) 每一个构造函数都有一个pro

  • 小白谈谈对JS原型链的理解

    原型链理解起来有点绕了,网上资料也是很多,每次晚上睡不着的时候总喜欢在网上找点原型链和闭包的文章看,效果极好. 不要纠结于那一堆术语了,那除了让你脑筋拧成麻花,真的不能帮你什么.简单粗暴点看原型链吧,想点与代码无关的事,比如人.妖以及人妖. 1)人是人他妈生的,妖是妖他妈生的.人和妖都是对象实例,而人他妈和妖他妈就是原型.原型也是对象,叫原型对象. 2)人他妈和人他爸啪啪啪能生出一堆人宝宝.妖他妈和妖他爸啪啪啪能生出一堆妖宝宝,啪啪啪就是构造函数,俗称造人. 3)人他妈会记录啪啪啪的信息,所以可

  • javascript教程之不完整的继承(js原型链)

    Javascript的继承和标准的oop继承有很大的区别,Javascript的继承是采用原型链的技术,每个类都会将"成员变量"和"成员函数"放到 prototype 上,Js++都过superclass将其链接起来,即 C.prototype.superclass = C.superclass = P.prototype;当 var c = new C()时,c.__proto__ = C.prototype ;当 c访问"成员变量"时,如果在

  • JS原型链 详解及示例代码

    前言 在 segmentfault 上看到这样一道题目: var F = function(){}; Object.prototype.a = function(){}; Function.prototype.b = function(){}; var f = new F(); 问:f 能取到a,b吗?原理是什么? 乍一看真的有点懵,仔细研究了一下,发现还是对原型理解不透彻,所以总结一篇,填个洞~ Function和Object 在解题之前,先再说说 原型.原型链,以及 Function 和 O

随机推荐