JavaScript原型链(重要)

1. 构造函数与原型

先了解一个概念:

类就是对象的模板,对象就是类的实例。

在构造函数内部的this指向它的实例对象。

2. new关键字

这个也是面试常考的知识点

在实例化构造函数时,做了什么?或者说在JavaScript中new

  1. 创建空对象 var zs = { }

  2. 将构造函数内部的this指向空对象 this -> zs

  3. 把属性和方法挂载到实例对象上。 zs.name zs.age zs.sing()

  4. 隐式返回新对象。

3. 实例成员与静态成员

  • 实例成员:在构造函数内部创建的对象成员称为实例成员,只能由实例化的对象来访问。
  • 静态成员:在构造函数本身上添加的成员称为静态成员,只能由构造函数本身来访问。
  var arr = new Array()
  console.dir(arr);
  console.dir(Array);

Snipaste_2022-07-23_09-44-33

4. 构造函数与静态成员

同一个函数,会在内存中保存多份,会造成内存的浪费。

<script>
  function Cup(color, size) {
      
      
    this.color = color
    this.size = size
    this.save = function () {
      
      
      console.log("储水");
    }
  }

  var mycup1 = new Cup("紫色", "3400ml")
  var mycup2 = new Cup("绿色", "400ml")


  console.log(mycup1);
  console.log(mycup2);

  mycup1.save()
  mycup2.save()
  引用数据类型,比较的是内存中的地址
  console.log(mycup1.save == mycup2.save);
  输出false
  地址不同,造成浪费
</script>

引用性数据类型比较的是地址

Snipaste_2022-07-23_10-32-24

5. 原型对象Prototype/[__proto__]

是构造函数的属性;

是Object类

构造函数的原型protoeype,作用是实现对象和属性共享,解决内存浪费问题。

每个一构造函数都有一个protoeype属性指向一个对象,这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所有。

一般情况下,我们公共属性放在构造函数里面,公共的方法我们放到原型对象身上。

原型对象里面的this指向的就是这个实例对象

作用:可以通过原型对象,对原来的内置对象进行拓展自定义的方法。

  function Cup(color, size) {
    
    
    this.color = color
    this.size = size

    Cup.prototype.save = function () {
    
    
    console.log("储水");
    }


    Cup.prototype.fill = function () {
    
    
    console.log("储水");
    }
  }

6. __proto__

对象能够访问原型对象中的方法:

原因:对象的_ _ proto_ _ 属性指向构造函数的prototype/原型对象

名称:对象原型

Snipaste_2022-07-23_10-56-40

  • __proto__对象原型和原型对象prototype是等价的
  • __proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说是一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,他只是内部指向原型对象prototype.

非标准:

不能使用,只能直接看他指向的结果,不能设置,只能直接访问

7. constructor属性

对象原型(__proto__)和构造函数(prototype)原型对象里面都有一个属性constructor属性,constructor我们称为构造函数,因此它指回构造函数本身。

  • 作用:主要用于记录该对象引用哪个构造函数,它可以让原型对象重新指向原来的构造函数。

constructor属性在那里可以访问:

  1. __proto__有constructor属性

  2. 原型对象/prototype有constructor属性

我们称constructor为构造函数。

使用:当以对象的形式修改原型对象的内容时,会发现修改后的属性把原来的属性覆盖掉了,这时候就需要重新设置一下constructor属性,并指挥回原来的构造函数。

  function Cup(color, size) {
    
    
    this.color = color
    this.size = size
  }

  Cup.prototype = {
    
    
    constructor: Cup,
    save: function () {
    
    
      console.log("储水");
    },
    warn: function () {
    
    
      console.log("保温");
    }
  }

(以对象的形式,修改了原型对象中的内容,这样修改后的原型对象constructor就不再指向当前构造函数了。此时,我们可以在修改后的原型对象中,添加一个constructor指向原来的构造函数)

8.原型链

JavaScript对象,都有__proto__属性,指向原型对象。

(mycup,Cup,Object,null)

Snipaste_2022-07-23_14-54-44

  • 作用:js中在查找对象和属性的方式时,遵循的一条链式规则。
    • 当访问一个对象的属性(包括方法)时,首先找到这个对象自身有没有该属性
    • 如果没有就查找它的原型(也就是__proto__指向的prototype原型对象)
    • 如果还没有就查找原型对象的原型(Object原型对象)
    • 以此类推一直找到Object为止(null)
    • __proto__对象原型的意义在于为对象成员查找机制提供了一种方向,或者说一条路线。

如果原型链找不到属性,undefined。如果找不到方法,会报错。

  • 关系:instance.constructor.prototype == instance.__proto__

  • 特点:JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。

小例子:

  function Cup(color, size) {
    
    
    this.color = color
    this.size = size
  }

  Cup.prototype = {
    
    
    constructor: Cup,
    save: function () {
    
    
      console.log("储水");
    },
    warn: function () {
    
    
      console.log("保温");
    }
  }
  var mycup = new Cup("粉色", "1500ml")

  console.log(mycup);
  console.log(mycup.__proto__ == Cup.prototype);
  console.log(mycup.__proto__.constructor); //指向当前的构造函数
  console.log(Cup.prototype.constructor); //指向当前的构造函数


  //验证原型链
  console.log(mycup.__proto__ == Cup.prototype);
  console.log(Cup.prototype.__proto__ == Object.prototype);
  console.log(Object.prototype.__proto__ == null);



  var arr = [1, 2, 3]
  var arr1 = new Array(1, 2, 3)
  console.log(arr, arr1);
  arr1.push(4)
  Array.prototype.sum = function () {
    
    
    var total = 0;
    for (var i = 0; i < this.length; i++) {
    
    
      total += this[i];
    }
    return total
  }


  console.log(arr.sum());
  console.log(arr1.sum());

Snipaste_2022-07-23_15-34-24

9.继承

JavaScript如何实现继承

  • 构造继承
  • 原型继承
  • 实例继承
  • 拷贝继承
  • 原型prototype机制或apply和call方法去实现较简单,建议使用构造函数与原型混合方式
function Parent(){
    
    
	this.name = 'wang';
}

function Child(){
    
    
        this.age = 28;
}
    
Child.prototype = new Parent();//继承了Parent,通过原型

var demo = new Child();
alert(demo.age);
alert(demo.name);//得到被继承的属性

ES6:利用extends实现继承

组合继承:构造函数+原型对象

es5继承:构造函数+原型对象

  //es5继承:构造函数 + 原型对象
  //1. 利用构造函数继承属性
  function Father(name, age) {
    
    
    this.name = name
    this.age = age
  }

  Father.prototype.work = function () {
    
    
    console.log("工作");
  }

  function Son(name, age, gender) {
    
    
    Father.call(this, name, age);
    this.gender = gender;
  }


  Son.prototype = new Father() //子构造函数的peototype = new 父构造函数
  Son.prototype.constructor = Son;  // 子构造函数的constructor手动设置为son


  Son.prototype.study = function () {
    
    
    console.log("学习");
  }

  var son = new Son('xh', 6, "男")
  console.log(son);

核心原理:

  • 将子类所共享的方法提取出来,让子类的prototype原型对象 = new 父类()
  • 本质:子类原型对象等于是实例化父类,因为父类实例化后另外开辟空间,就不会影响原来的父类
  • 将子类的constructoe重新指向子类的构造函数
  Son.prototype = new Father() //子构造函数的peototype = new 父构造函数
  Son.prototype.constructor = Son;  // 子构造函数的constructor手动设置为son

利用原型对象继承方法

es6:继承: extend

  //es6: 类  extends

  class Father1 {
    
    
    constructor(name, age) {
    
    
      this.name = name
      this.age = age
    }
    work() {
    
    
      console.log("父亲工作");
    }
  }
  class Son1 extends Father1 {
    
    
    constructor(name, age, gender) {
    
    
      super(name, age)
      this.gender = gender
    }
    study() {
    
    
      console.log("子学习");
    }
  }

  var son1 = new Son1("小米", 20, "男")
  console.log(son1);

es6继承的关键:extend , super

10.对象的特性

  • 封装

将对象所有的组成部分组合起来,尽可能的隐藏对象的部分细节,使其受到保护,只提供有限的接口与外部发生联系。

  • 优点:
    • 安全,使用时无法看到具体的细节,只需要直接调用
    • 便于修改操作
  • 继承

将对象拥有另外一个对象的属性和方法。

父类(基类):被继承的对象。

子类:继承的对象

  • 优点:
    • 提高代码重用性,提高代码的逻辑性和可维护性。

使用es5和es6实现类的封装

  1. es5
  //es5 构造函数+原型对象
  function Cup(color, size) {
    
    
    this.color = color
    this.size = size
  }

  Cup.prototype = {
    
    
    constructor: Cup,
    save: function () {
    
    
      console.log("储水");
    },
    warn: function () {
    
    
      console.log("保温");
    }
  }
  var mycup = new Cup("粉色", "1500ml")
  1. es6
  //es6类 语法糖
  class Cup1 {
    
    
    constructor(color, size) {
    
    
      this.color = color
      this.size = size
    }
    save() {
    
    
      console.log("储水");
    }
    warn() {
    
    
      console.log("保温");
    }
  }

  var mycup1 = new Cup("绿色", "500ml")
  var mycup2 = new Cup1("蓝色", "600ml")

上面这两种完全等同,但es6的更简单,封装更容易

11.函数

函数的三种定义方式

  1. 利用funcation关键字定义函数,也叫命名函数
  //定义,利用funcation 命名函数
  function fn() {
    
    
    console.log(1);
  }
  fn()
  1. 利用字面量定义函数,也叫匿名函数
  //2. 字面量定义  匿名函数
  var fn1 = function () {
    
    
    console.log(2);
  }
  fn1()

由于声明函数方式的不同以及预解析的因素,导致1,2两种方式略微不同。1,这种定义方式,把函数调用提前是完全没有问题的,但2,这种方式就会报错。(预解析知识点)

  1. 实例化Funcation
  //3.实例化Funcation
  var fn2 = new Function('num', 'concole.log(num)')
  fn2(3)

这种方式也是可以传参的,但由于写法复杂,用的并不多。

函数的调用

  1. 普通函数,this指向window
  function fn() {
    
    
    console.log("普通函数");
  }
  fn();
  1. 构造函数,this指向实例化的对象
  function Cup(size) {
    
    
    this.size = size
  }

  var cup = new Cup("500ml")
  1. 原型对象的方法,this指向实例化的对象
  Cup.prototype = {
    
    
    constructor: Cup,
    save: function () {
    
    
      console.log("储水");
    }
  }
  var cup = new Cup("500ml")
  cup.save();
  1. 事件处理函数,this指向事件源 < button >< /button >
  var btn = document.querySelector("button")
  btn.onclick = function () {
    
    
    console.log("事件处理函数");
    console.log(this);
  }
  1. 定时器函数,this 指向window
  setTimeout(function () {
    
    
    console.log("定时器中的函数");
  }, 1000)
  1. 立即执行函数,this 指向window
    (function () {
    
    
      console.log("立即执行函数");
    })()

改变this指向的三种方式

call方法

  1. 调用函数
  2. 改变this指向
fn.call(thisAry,arg1,arg2,...)
  • thisAry:当前调用函数this的指向对象
  • arg1,arg2:传递的其他参数

返回值就是函数的返回值

  var obj = {
    
     name: "zs", age: 20 }
  function fn(num) {
    
    
    console.log(1);   20
    console.log(this);  21
    console.log(num);   22
  }

  fn(0);  25

  fn.call(obj, 'call')   28

Snipaste_2022-07-23_18-47-15

很明显可以看到又调用了 一次函数。而且this指向也变成了obj,不再是window了

apply方法

  1. 调用函数
  2. 改变this指向
fun.apply(thisAry,[arg1,arg2])
  • thisAry:在fun函数运行时指定的this值
  • arg1,arg2 :传递的其他参数

返回值就是函数的返回值

  var obj = {
    
     name: "zs", age: 20 }
  function fn(num) {
    
    
    console.log(1);   20
    console.log(this);  21
    console.log(num);   22
  }

  fn(0);  25

  fn.apply(obj, ['apply'])  31

Snipaste_2022-07-23_18-53-27

和call方法一样,也会调用函数

也可以不改变参数只传值,不常用。

  fn.apply(null, ['apply'])  

bind方法

用到比较多

  1. 改变this指向
fun.bind(thisArg,arg1,arg2,...)
  • thisArg:在fun函数运行时指向的this值
  • arg1,arg2:传递的参数

返回由原来函数改变this之后的新函数

  var obj = {
    
     name: "zs", age: 20 }
  function fn(num) {
    
    
    console.log(1);   20
    console.log(this);  21
    console.log(num);   22
  }

  fn(0);  25


  var f = fn.bind(obj, "bind") 33
  fn()  34

Snipaste_2022-07-23_19-01-23

总结

是否可以改变this指向 是否可以调用函数 返回值 使用场景
call 就是函数返回值 用来继承
apply 就是函数返回值 数组相关
bind 返回由原函数改变this之后的新函数 不希望函数立即执行(比如定时器)

原型,构造函数,实例,以及原型链

Snipaste_2022-07-24_11-28-11

原型,构造函数,实例三者之间的关系

猜你喜欢

转载自blog.csdn.net/liyuchenii/article/details/125957625