编程技术文章分享与教程

网站首页 > 技术文章 正文

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

hmc789 2024-11-10 10:33:53 技术文章 2 ℃

开头

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

一、原型、原型链

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>

这个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>

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

<body>
    <script>
      function Dog(name, age) {
        'use strict'
        this.name = name;
        this.age = age;
      }
      var d = Dog("阿黄", 10);
      console.log(d);
    </script>
  </body>

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>

上面的代码利用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>

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

  1. 创建一个空对象,作为将要返回的对象实例
  2. 将空对象的原型对象指向了构造函数的prototype属性对象

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

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

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>

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>

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

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>

原型对象:Foo.prototype

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

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

1.7、原型链

<body>
    <script>
        function Foo(){};
        var f1 = new Foo();
    </script>
</body>

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

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

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

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

Object实例对象的__proto__为null

FooObject再当成一个实例,它们也有对应的constructor

__proto__也同理:

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

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

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

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

1.8、prototype属性的作用

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

什么是js的继承机制?

<body>
    <script>
      function Foo() {}
      var f1 = new Foo();
      Foo.prototype.name = "chen";
      console.log(f1.name);
    </script>
  </body>

看结果:

可以看到,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>

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

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>

所有的对象的尽头都是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>

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

要添加这一句:

MyArray.prototype.constructor = MyArray;

我们打印一下arr.constructor

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

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

1.9、总结

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

构造函数:Foo

实例对象:f1

原型对象:Foo.prototype

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

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

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

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

3.实例对象和构造函数

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

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

所以,代码的顺序很重要

标签列表
最新留言