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

[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

<span style="color:red;font-size:30px;background:blue;">值得注意的是,这套东西不是我们发现的,而是js作者特意为了能够实现代码复用而设计出来的特殊结构,我们只不过是以推导的方式来带领大家学习</span>

其实原型链就是我们在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={ };

<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/07379864d6024241975362d68ac1d42c~tplv-k3u1fbpfcp-zoom-1.image" alt="image-20210619160307233" style="zoom: 200%;" />

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

function Foo( ){ }

<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/da6b5a591c5a4b859292248ac7173bad~tplv-k3u1fbpfcp-zoom-1.image" alt="img" style="zoom: 67%;" />

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) //2 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()

<span style="background-color:blue;font-size:30px">这道题的关键在于要理解下面的终极原型链图</span>

image-20210620085852692
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,612评论 5 471
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,345评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,625评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,022评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,974评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,227评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,688评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,358评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,490评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,402评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,446评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,721评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,802评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,013评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,504评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,080评论 2 341

推荐阅读更多精彩内容