01.原型(prototype)和原型链(prototype chain)

1,413 阅读8分钟

[toc]

一.原型

1. 函数的prototype属性(图)

** 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)*

** 原型对象中有一个属性constructor, 它指向函数对象*

2. 给原型对象添加属性(一般都是方法)

** 作用: 函数的所有实例对象自动拥有原型中的属性(方法)*

 <script type="text/javascript">
      // 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
      console.log(Date.prototype, typeof Date.prototype)

      function Fun() {

      }
      console.log(Fun.prototype) // 默认指向一个Object空对象(没有我们的属性)

      // // 原型对象中有一个属性constructor, 它指向函数对象
      console.log(Date.prototype.constructor === Date)
      console.log(Fun.prototype.constructor === Fun)

      // //给原型对象添加属性(一般是方法) ===>实例对象可以访问
      Fun.prototype.test = function () {
        console.log('test()')
      }
      var fun = new Fun()
      fun.test()
    </script>

1.1 prototype(原型)和_ _ proto _ _(隐式)

在JavaScript中,每个函数都有一个prototype属性,这个属性指向函数的原型对象。

1.每个函数function都有一个prototype,即显式原型(属性)

2. 每个实例对象都有一个__proto__,可称为隐式原型(属性)

3. 对象的隐式原型的值为其对应构造函数的显式原型的值

 // 3. 对象的隐式原型的值为其对应构造函数的显式原型的值
console.log(Fn.prototype === fn.__proto__) // true

1.2 理解原型的内存图

<!--
    1. 每个函数function都有一个prototype,即显式原型(属性)
    2. 每个实例对象都有一个__proto__,可称为隐式原型(属性)
    3. 对象的隐式原型的值为其对应构造函数的显式原型的值
    4. 内存结构(图)
    5. 总结:
      * 函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象
      * 对象的__proto__属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
      * 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)
    -->
    <script type="text/javascript">
      //一.定义构造函数
      function Fn() { // 内部语句: this.prototype = {}

      }
      // 1. 每个函数function都有一个prototype,即显式原型属性, 默认指向一个空的Object对象
      console.log(Fn.prototype)
      // 2. 每个实例对象都有一个__proto__,可称为隐式原型
      //二.创建实例对象
      var fn = new Fn() // 内部语句: this.__proto__ = Fn.prototype
      console.log(fn.__proto__)
      // 3. 对象的隐式原型的值为其对应构造函数的显式原型的值
      console.log(Fn.prototype === fn.__proto__) // true
      //三.给原型添加方法
      Fn.prototype.test = function () {
        console.log('test()')
      }
      //四.通过实例调用原型的方法
      fn.test() //test()
    </script>

prototypeMemoryMap

1.3 原型的基本使用

通常我们在使用原型写代码的时候,我们都会这样做:

==1.在构造函数里面写属性。==

==2.在原型对象上写方法。==

这样做就避免了浪费内存的问题

1.4 构造函数和实例对象,原型对象的三角关系

threeAngle

  /*
    原型:
        1.是构造函数的一个属性,是一个对象,是在我们创建构造函数的时候 系统 自动分配的
           作用是给 构造函数的 实例对象 提供方法
        2.可以通过构造函数.prototype 得到   

     总结:
         1. 原型对象 - 作用是给实例对象提供方法
         2. 构造函数.prototype 和 实例对象.__proto__
         3. 有prorotype属性是 构造函数  ,有 __proto__ 属性 就是 实例对象    
    */
    function Person(name, age, gender) {
      this.name = name
      this.age = age
      this.gender = gender
    }

    Person.prototype.sayHi = function () {
      console.log('sayHi被调用了');
    }
    // console.dir(Person)
    console.log(Person.prototype);
    // 2.证明 原型的 constructor 属性  就是  构造函数
    console.log(Person.prototype.constructor === Person); //true

    let p1 = new Person()
    let p2 = new Person()

    // p1.sayHi()
    // p2.sayHi()
    // console.log(p1);
    // console.log(p2);

    //1.证明 实例对象的 __proto__ 就是 构造函数的prototype
    // console.log(p1.__proto__ === p2.__proto__); //true
    // console.log(p1.__proto__ === Person.prototype); //true
    // console.log(p2.__proto__ === Person.prototype); //true

prototype

原型其实就是一个在浏览器中,每当创建一个构造函数,就会自动分配好的对象。

这个对象的作用,就是给实现对象共享方法用的。

我们可以通过两个方式访问到这个对象:

1.从构造函数访问原型:构造函数.prototype
2.从实例对象访问原型:实例对象.__proto__

1.5 原型总结

  1. 原型其实就是一个对象,存在于内存中,我们看不见
  2. 原型可以给构造函数的实例对象提供方法
  3. 构造函数.prototype实例对象.__proto__ 都可以访问到原型对象
  4. prototype属性的是构造函数,有__proto__属性的是实例对象
  5. 函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象
  6. 对象的__proto__属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
  7. 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)

1.6 原型的原理

原型之所以会比单独的构造函数好,是因为它在内存这的唯一性,于是在它身上的方法也具备唯一性。

原型方法所在的位置

此时无论我们new多少个实例对象,它们都会在调用方法的时候,沿着它的__proto__属性找到原型对象,然后调用原型对象上的对应的方法,因为原型对象只会有一个,所以方法对应的函数对象也就只会有一个,就解决了浪费内存的问题。

而此时在内存中,已经在多个对象之前存在这样一个关联关系

image-20210311095926999

二.封装一个简单的jQuery

jQuery中就是使用了原型来实现了很多代码的封装,所以我们利用所学的原型的知识,模仿一下jQuery的代码封装。(这个过程比较复杂,我们可以先不看,在上课的时候再慢慢讲)

/*    简单的jQuery包含的功能:        1.获取元素  $(选择器)        2.注册事件  jq对象.on()        3.修改样式  jq对象.css()    目的:        jq对象.on(事件类型,处理程序)        jQuery.prototype.on = function(){}*/// 面向对象,为了区分对象,先 写 构造函数function jQuery(selector) {  // 得到一个伪数组  var dom = document.querySelectorAll(selector);// 这是一个 NodeList 伪数组  // 我们自己造一个  for (var i = 0; i < dom.length; i++) {    this[i] = dom[i];  }  //伪数组都要长度  this.length = dom.length;}// 封装css方法jQuery.prototype.css = function (prop, value) {  // css方法有多个用法,可以通过判断 参数个数区分  if (arguments.length === 2) {    // 把 伪数组里面的所有元素都修改    // 方法里面的this,是实例对象    // this是一个伪数组,当然要遍历    for (var i = 0; i < this.length; i++) {      this[i].style[prop] = value;    }  } else if (arguments.length === 1) {    //此时 prop 应该是一个对象,需要从prop里面得到每个键值对    for (var key in prop) {      for (var i = 0; i < this.length; i++) {        this[i].style[key] = prop[key];      }    }  }  // 为了支持链式编程,返回一个jq对象  return this;}// 封装on方法 - 实现注册事件jQuery.prototype.on = function (type, fn) {  // 判断 当前的浏览器 是否支持  addEventListener 方法  // 事件源.addEventListener(事件类型,处理程序)  for (var i = 0; i < this.length; i++) {    if (typeof this[i].addEventListener === 'function') {      this[i].addEventListener(type, fn);    } else {      // ie 的注册事件的方法 attachEvent      this[i].attachEvent('on' + type, fn);    }  }  return this}// 为了简单jQuery的使用,再包一层函数function $(selector) {  return new jQuery(selector);}

三.原型链

3.1 什么是原型链?

==在JavaScript 中,每个对象都有一个指向它的原型(prototype)对象的内部链接。这个原型对象又有自己的原型,直到某个对象的原型为 null 为止(也就是不再有原型指向),组成这条链的最后一环。==这种一级一级的链结构就称为原型链(prototype chain)

事实上在内存中,存在多个原型对象,多个原型对象之前存在着一个链式关系,这个链式关系我们称为:原型链

原型链是javascript特意为了实现面向对象的继承而设计的一种对象结构,这样可以解决代码的重复利用的问题。

当我们把构造函数的原型输出,再展开查看,发现原型对象上面也有__proto__属性,也就是说其实原型对象也是一个实例对象

function Person(name,age,gender){ }console.log(Person.prototype)

image-20210309183856963

而原型对象的__proto__属性我们发现它也是一个对象,此时在内存中就至少存在这样一个链式关系

image-20210309184341705

我们就称这样的结构关系为原型链

3.2 原型链的作用

我们在上方提到,==原型链是javascript专门为了实现继承而设计的==,我们先不管继承是什么,可以先看看它这样设计的用处。

前方已经学习过,原型对象的作用就为了给实例对象提供方法的,那么也就是说 对象.__proto__ 上面的方法,对象就可以使用。同理,现在在原型对象(对象.__proto__)的上面的__proto__属性所指向的对象,它身上的方法能否被原型对象所使用呢?当然!

我们先看看在这个原型的原型上面有什么方法

console.log(Person.prototype.__proto__)

image-20210309185010758

可以看到在原型的原型身上有一个toString方法,我们尝试调用一下

console.log(Person.prototype.toString()); // 结果为: [object Object]

可见__proto__属性身上的方法确实是可以被对象所使用的。

那么这个时候我们思考一个问题: Person构造的函数的实例对象能不能调用这个toString()方法呢?毕竟Person实例的__proto__属性是 Person.prototype,Person.prototype的__proto__属性的方法也相当于是Person.prototype的方法,那么实例对象访问它的__proto__的方法,应该也可以

let p = new Person()console.log(p.toString()); // 结果: [object Object]

可以看到,toString方法确实通过多个对象之间的 __proto__ 关系被重用了。

小结:原型上,上游对象的方法可以被下游对象直接调用

3.3 原型链结构

结合之前得到的Perosn与其原型之间的关系,我们可以得到一个更加完整的关系图

70c34794aabf0c34d000e3cc2d9aab59.png

image-20210311101213624

于是我们可以思考一个问题,Object原型对象上面还有没有__proto__属性呢?

console.log(Object.prototype.__proto__) // null

也就是说原型链的关系到了Object原型这,再往上就没有了 ———— 原型链的尽头是 null

image-20210311102138980

值得注意的是,这套东西不是我们发现的,而是js作者特意为了能够实现代码复用而设计出来的特殊结构,我们只不过是以推导的方式来带领大家学习

其实原型链就是我们在js中实现继承的基本原理

小结:

  1. 原型链是本来就存在的,人为故意设计的
  2. 原型链是指对象之间通过__proto__属性关联起来的关系
  3. 原型链末端的对象可以访问上游的对象的方法

3.4 原型链成员访问规则

对象成员 == 对象属性+对象方法

原型链末端的对象可以访问上游的对象的方法,其实也是有一定的访问规则的。和作用域类似的,原型链上的成员也遵循**"就近原则"**

  • 读取对象的属性值时:
  1. 当访问一个对象的成员时,如果这个对象有自己的对应成员,优先使用自己的
  2. 当自己没有对应的成员时,会沿着原型链查找,找到一个最近的
  3. 如果直到Object.prototype身上也没有,返回undefined
  • 设置对象的属性值时:==不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值==

  • ==方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上==

       <!--
          1. 读取对象的属性值时: 会自动到原型链中查找
          2. 设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值
          3. 方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上
        -->
        <script type="text/javascript">
          //例子一:
          function Fn() {
    
          }
          Fn.prototype.a = 'xxx'
          var fn1 = new Fn()
          console.log(fn1.a, fn1) //xxx Fn{}
    
          var fn2 = new Fn()
          fn2.a = 'yyy'
          console.log(fn1.a, fn2.a, fn2) //xxx yyy Fn{a:'yyy'}
    
    
          //例子二:
          function Person(name, age) {
            this.name = name
            this.age = age
          }
          Person.prototype.setName = function (name) {
            this.name = name
          }
          var p1 = new Person('Tom', 12)
          p1.setName('Bob')
          console.log(p1) //{'Bob',12}
    
          var p2 = new Person('Jack', 12)
          p2.setName('Cat')
          console.log(p2) //{'Cat',12}
          console.log(p1.__proto__ === p2.__proto__) // true
    

3.5 原型链案例分析(==构造函数/原型/实例对象的关系(图解)==)

  <!--
      1. 原型链(图解)
        * 访问一个对象的属性时,
          * 先在自身属性中查找,找到返回
          * 如果没有, 再沿着__proto__这条链向上查找, 找到返回
          * 如果最终没找到, 返回undefined
        * 别名: 隐式原型链
        * 作用: 查找对象的属性(方法)
      2. 构造函数/原型/实体对象的关系(图解)
      3. 构造函数/原型/实体对象的关系2(图解)
-->
    <script type="text/javascript">
      // console.log(Object)
      //console.log(Object.prototype)
      //console.log(Object.prototype.__proto__)

      function Fn() {
        this.test1 = function () {
          console.log('test1()')
        }
      }
      //console.log(Fn.prototype)
      Fn.prototype.test2 = function () {
        console.log('test2()')
      }

      var fn = new Fn()

      fn.test1() //test1()
      fn.test2() //test2()
      console.log(fn.toString()) //[object object]
      console.log(fn.test3) //undefined
      //fn.test3()


      /*
      1. 函数的显示原型指向的对象默认是空Object实例对象(但Object不满足)
       */
      console.log(Fn.prototype instanceof Object) // true
      console.log(Object.prototype instanceof Object) // false
      console.log(Function.prototype instanceof Object) // true
      /*
      2. 所有函数都是Function的实例(包含Function)
      */
      console.log(Function.__proto__ === Function.prototype)
      /*
      3. Object的原型对象是原型链尽头
       */
      console.log(Object.prototype.__proto__) // null

prototypeChain

3.6 ==构造函数/原型/实例对象的关系(图解)==

var o1=new Object();
var o2={ };
image-20210619160307233

3.7 ==构造函数/原型/实例对象的关系2(图解)==

function Foo( ){ }
img

prototypeMap

四.instanceOf关键字

1.instanceof是如何判断的?

表达式: A instanceof B

instance关键字规则: ==如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false==

2.Function是通过new自己产生的实例

4.1 案例一:

function Foo() {}
var f1 = new Foo()
console.log(f1 instanceof Foo) // true
console.log(f1 instanceof Object) // true

image-20210620084342153

4.2 案例二:

 console.log(Object instanceof Function) // true
 console.log(Object instanceof Object) // true
 console.log(Function instanceof Function) // true
 console.log(Function instanceof Object) // true

 function Foo() {}
 console.log(Object instanceof Foo) // false

image-20210620085852692

五.面试题

5.1 面试题一:熟悉以后,就不需要画图,要一眼看懂

 function A() {

      }
      A.prototype.n = 1

      var b = new A()

      A.prototype = {
        n: 2,
        m: 3
      }

      var c = new A()
      console.log(b.n, b.m, c.n, c.m) //1 undefined 2 3

image-20210620095908102

5.2 面试题二:

function F() {}
Object.prototype.a = function () {
      console.log('a()')
}
Function.prototype.b = function () {
      console.log('b()')
}

var f = new F()
f.a()
f.b()//报错
F.a()
F.b()

这道题的关键在于要理解下面的终极原型链图

image-20210620085852692