干巴爹兔的博客 干巴爹兔的博客
首页
  • 前端文章

    • JavaScript
    • HTML
    • Vue
  • 学习笔记

    • JavaScript教程
    • React学习笔记
    • Electron学习笔记
  • 开源项目

    • cloud-app-admin
    • 下班了吗Vscode插件
    • Subversion变更单插件
  • Server

    • Django
  • 学习笔记

    • MySQL学习笔记
  • 运维

    • 服务器部署
    • Linux
  • 日常学习

    • 学习方法
关于
收藏
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

干巴爹兔

卑微的前端打工人
首页
  • 前端文章

    • JavaScript
    • HTML
    • Vue
  • 学习笔记

    • JavaScript教程
    • React学习笔记
    • Electron学习笔记
  • 开源项目

    • cloud-app-admin
    • 下班了吗Vscode插件
    • Subversion变更单插件
  • Server

    • Django
  • 学习笔记

    • MySQL学习笔记
  • 运维

    • 服务器部署
    • Linux
  • 日常学习

    • 学习方法
关于
收藏
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 理解JS作用域

  • JS面向对象

    • JS面向对象编程笔记(一):原型、原型链
      • JS面向对象编程笔记(二):5种对象创建模式
      • JS面向对象编程笔记(三):5种继承方式
      • JS面向对象编程笔记(完):Object方法与模块化
    • 《JavaScript教程》笔记
    • JS面向对象
    干巴爹兔
    2020-07-08
    目录

    JS面向对象编程笔记(一):原型、原型链

    # 开头

    笔记视频内容源自B站JavaScript从入门到放弃 第十二章 面向对象编程 (opens new window),笔记为自行整理复习使用,欢迎一同学习交流,转载请通知原作者

    # 一、原型、原型链

    # 1.1、什么是面向对象?

    面向对象编程是目前主流的的一个编程的模式,它将我们真实世界各种复杂的关系抽象成了一个个的对象,然后由这些对象的分工合作完成真实世界的模拟。每一个对象都有其功能中心,即完成的任务(方法,属性)。因此面向对象编程(OOP)具有灵活、代码可复用、高度模块化等特点。

    1.对象是单个实物的抽象

    2.对象是一个容器,封装了对应的属性和方法,属性是对象的状态,方法是对象的行为(完成的任务)

    # 1.2、构造函数实例化对象

    生成一个对象通常需要一个模板(表示一类实物的共同特征),让对象生成

    类(class)就是对象的模板,但是js不是基于类的,而是基于构造函数(constructor)和原型链(prototype),我们来看一段代码:

    <body>
        <script>
          function Dog(name, age) {
            //name和age就是当前实例化对象的属性
            this.name = name;
            this.age = age;
          }
    
          var dog1 = new Dog("阿黄", 10);
        </script>
      </body>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    image-20200708194259410

    这个Dog()是构造函数,为了与普通函数区别,构造函数的名字的第一个字母通常都大写

    构造函数的特点:

    1. 函数体内使用this关键字,代表了所要生成的对象实例
    2. 生成对象必须通过new关键字实例化

    如果没有new关键字,Dog函数为普通函数没有返回值,结果就是undefined

    <body>
        <script>
          function Dog(name, age) {
            //name和age就是当前实例化对象的属性
            this.name = name;
            this.age = age;
          }
    
          //   var dog1 = new Dog("阿黄", 10);
          var d = Dog("阿黄", 10);
          console.log(d);
        </script>
      </body>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    image-20200708194733490

    使用严格模式报错会进一步扩大:

    <body>
        <script>
          function Dog(name, age) {
            'use strict'
            this.name = name;
            this.age = age;
          }
          var d = Dog("阿黄", 10);
          console.log(d);
        </script>
      </body>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    image-20200708194915097

    # 1.3、instanceof用法

    instanceof表示当前对象是否是它的类的实例,如果是返回true,不是返回false

    <body>
        <script>
        function Dog(name){
            if(!(this instanceof Dog)){
                //this指向了window,外部没有使用关键字new
                return new Dog(name)
            }
            //this指向当前实例,外部使用了关键字new
            this.name = name;
        }
        var d1 = Dog('阿黄')
        console.log(d1);
        </script>
      </body>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    image-20200708195325405

    上面的代码利用instanceof关键字来判断,如果是作为普通函数进来,this的指向为window而不是Dog,返回一个new过后的构造函数,如果是则赋值其name属性

    # 1.4、new命令原理

    当我们使用new命令时发生了什么呢?我们使用一段代码来研究一下:

    <body>
        <script>
            function Person(name){
                this.name = name
            }
            var p1 = new Person();
            console.log(p1);
            console.log(p1.__proto__ === Person.prototype) //true
            console.log(Person.prototype);      
        </script>
    </body>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    首先new关键字构造函数后:

    1. 创建一个空对象,作为将要返回的对象实例

    2. 将空对象的原型对象指向了构造函数的prototype属性对象

      image-20200708200440827

    ​ 3.将这个实例对象的值赋值给函数内部的this关键字

    ​ 4.指向构造函数体内的代码

    image-20200708201249062

    # 1.5、constructor属性

    constructor是在每个对象创建时都会自动的拥有的一个构造函数属性

    <body>
        <script>
            function Person(name){
                this.name = name
            }
            var p1 = new Person('chen');
            console.log(p1);
            console.log(p1.constructor);
            console.log(p1.constructor === Person);
            console.log(Person.prototype);
            
        </script>
    </body>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    image-20200708202012261

    constructor继承自原型对象,其中指向了构造函数的引用,所以p1.constructor === Person应该为true

    使用构造函数创建对象的好处在于,能够共享内部属性和方法:

    <body>
        <script>
          function Person(name) {
            this.name = name;
            this.sayName = function () {
              console.log(this.name);
            };
          }
          var p1 = new Person('Tom');
          var p2 = new Person('Jack');
            
          console.log(p1,p2);
        </script>
      </body>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    就像p1和p2,它们都各自用有自己的name属性。但同样的也有弊端:每个实例调用的时候方法都要重新创建,存在冗余,想要解决这类方法要使用**原型对象(prototype)**来解决

    image-20200708202512719

    # 1.6、原型对象介绍

    先看代码:

    <body>
        <script>
          function Foo() {}
          Foo.prototype.name = "chen";
          Foo.prototype.showName = function () {
            console.log("fchen");
          };
          var f1 = new Foo();
          var f2 = new Foo();
          console.log(f1.name);
          console.log(f2.name);
          f1.showName();
          f2.showName();
        </script>
      </body>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    原型对象:Foo.prototype

    实例对象:f1就是实例对象,实例对象可以构造多个,每一个原型对象中都有一个__proto__,每个实例对象都有一个constructor属性,这个constructor通过继承关系继承来的,它指向了当前的构造函数Foo

    构造函数:用来初始化新创建对象的函数,Foo就是构造函数,自动给构造函数赋予一个属性prototype,该属性指向了实例对象的原型对象(__proto__)

    image-20200708203744726

    # 1.7、原型链

    <body>
        <script>
            function Foo(){};
            var f1 = new Foo();
        </script>
    </body>
    
    1
    2
    3
    4
    5
    6

    以上面的代码结合图示来分析一下Foo构造函数的原型链:

    image-20200708204955270

    Foo的实例对象f1,它的原型对象f1.__proto__指向了Foo的prototype属性,f1的constructor属性间接继承于Foo,如果将Foo.prototype也当作一个实例化的对象,同样的他也会有__proto__与constructor属性,Foo.prototype.__proto__指向了Object.prototype,``Foo.prototype.constructor**直接继承**于Foo`,如图所示:

    image-20200708205906313

    image-20200708210034343

    控制台与图示一致,f1.__proto__=== Foo.prototype

    再深入下去,将Foo当成一个实例对象,它的__proto__又将指向哪一个呢:

    image-20200708210522666

    image-20200708210547313

    Object实例对象的__proto__为null

    image-20200708210654797

    image-20200708210708294

    把Foo和Object再当成一个实例,它们也有对应的constructor

    image-20200708210859038

    image-20200708210918034

    __proto__也同理:

    image-20200708211052074

    函数也是一个对象,任何函数我们可以看作是Function所new出来的对象,如果我们把Foo当作对象它同样的有__proto__和constructor属性

    再再深究下去Funtion实例对象也有其__proto__和constructor属性

    image-20200708211811760

    image-20200708211708875

    image-20200708211851202

    image-20200708211948189

    Foo里面的constructor是继承而来的,继承的当前构造函数的原型

    image-20200708212056056

    这就是原型链,是不是很绕(我也晕晕的),研究的就是原型对象

    # 1.8、prototype属性的作用

    js的继承就是通过prototype来进行的

    什么是js的继承机制?

    <body>
        <script>
          function Foo() {}
          var f1 = new Foo();
          Foo.prototype.name = "chen";
          console.log(f1.name);
        </script>
      </body>
    
    1
    2
    3
    4
    5
    6
    7
    8

    看结果:

    image-20200708212847067

    image-20200708212857477

    可以看到,name属性并不在f1上,而是通过继承挂载在f1的原型对象(__proto__)上

    <body>
        <script>
          function Foo() {}
          var f1 = new Foo();
          var f2 = new Foo();
          Foo.prototype.name = "chen";
          console.log(f1.name);
          console.log(f2.name);
    
          Foo.prototype.name = "chen2";
          console.log(f1.name);
          console.log(f2.name);
        </script>
      </body>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    image-20200708213133406

    可以看到两个实例都会被修改

    JS继承机制,通过原型对象(__proto__)实现继承。原型对象的作用,就是定义了所有的实例对象共享的属性和方法

    读取属性和方法的规则:js引擎会先寻找对象本身的属性和方法,如果找不到就到它的原型对象去找,如果还是找不到,就到原型的原型去找,如果直到最顶层的Object.prototype还是找不到,就会返回undefined 如果对象和它的原型,都定制了同名的属性,那么优先读取对象自身的属性,这也叫覆盖

    <body>
        <script>
          function Foo() {}
          var f1 = new Foo();
          var f2 = new Foo();
          Object.prototype.showName = function(){
              console.log(24);
          }
          f1.showName()
          var arr = [12,3,4]
          arr.showName()
        </script>
      </body>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    image-20200708213833234

    所有的对象的尽头都是Object

    接下来我们编写一段代码,让我们自己的构造函数拥有数组的部分属性和方法:

    <body>
        <script>
          function MyArray() {}
          MyArray.prototype = Array.prototype;
          var arr = new MyArray();
          console.log(arr);
          arr.push(1,2,3)
          console.log(arr);
        </script>
      </body>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    image-20200708214413344

    但是上面的代码同时也改变了他的constructor,修改成了数组的constructor,怎么样能不改变构造函数的指向呢

    要添加这一句:

    MyArray.prototype.constructor = MyArray;
    
    1

    我们打印一下arr.constructor

    image-20200708214745799

    总结:一旦我们修改构造函数的原型对象,为防止引用出现问题,同时也要修改原型对象的constructor属性

    constructor属性表示原型对象和构造函数之间的关联关系

    # 1.9、总结

    function Foo(){};
    var f1 = new Foo();
    
    1
    2

    构造函数:Foo

    实例对象:f1

    原型对象:Foo.prototype

    1.原型对象和实例对象的关系

    console.log(Foo.prototype === f1.__proto__);
    
    1

    image-20200708215321402

    2.原型对象和构造函数的关系

    console.log(Foo.prototype.constructor === Foo);
    
    1

    image-20200708215412686

    3.实例对象和构造函数

    间接关系是实例对象可以继承远程对象的constructor属性

    console.log(f1.constructor === Foo);
    
    1
    Foo.protptype={};
    console.log(Foo.prototype === f1.__proto__);
    console.log(Foo.prototype.constructor === Foo);
    
    1
    2
    3

    image-20200708215800117

    所以,代码的顺序很重要

    image-20200708215916887

    编辑 (opens new window)
    上次更新: 2022/08/26, 15:52:02
    理解JS作用域笔记(完):声明提升、作用域链
    JS面向对象编程笔记(二):5种对象创建模式

    ← 理解JS作用域笔记(完):声明提升、作用域链 JS面向对象编程笔记(二):5种对象创建模式→

    最近更新
    01
    使用Vscode开发一个小插件
    10-21
    02
    Vscode插件配置项监听
    10-18
    03
    使用has属性构造必填效果
    10-14
    更多文章>
    Theme by Vdoing | Copyright © 2020-2023 互联网ICP备案: 闽ICP备18027236号
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式