JS 原型和原型链

构造函数

封装是面向对象思想中比较重要的一部分,js 面向对象可以通过构造函数实现的封装。

  • 同样的将变量和函数组合到了一起并能通过 this 实现数据的共享,所不同的是 JS 借助构造函数创建出来的实例对象之间是彼此不影响的
  • 存在浪费内存的问题,我们希望所有的对象使用同一个函数,这样就比较节省内存,那么我们要怎样做呢?

原型

1、prototype

目标:能够利用原型对象实现方法共享

  • 构造函数通过原型分配的函数是所有对象所 共享的

  • JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象

  • 这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存

  • 我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。

  • 构造函数和原型对象中的this 都指向 实例化的对象

  • 注意:这里不可以使用箭头函数,因为箭头函数的this指向上一层

应用

举例【 我们可以把那些不变的方法,直接定义在 prototype 对象上】

javascript 复制代码
 // 我们可以把那些不变的方法,直接定义在 prototype 对象上
  function Star(uname, age) {
    this.uname = uname
    this.age = age
  }
  Star.prototype.sing = function() {
    console.log(唱歌)
  }

  const ldh = new Star('刘德华',55)
  const zxy = new Star('张学友',58)
  console.log(ldh.sing === zxy.sing); //true

【构造函数和原型对象中的this 都指向 实例化的对象】

javascript 复制代码
 let that
  function Star(uname) {
    that = this
    this.uname = uname
  }
  let that2
  Star.prototype.sing = function() {
    that2 = this
    console.log('唱歌');
  }
  // 构造函数中的this指向实例化对象
  const ldh = new Star('刘德华')
  console.log(that === ldh) // true
  
  // 原型对象中的this指向实例化对象
  ldh.sing()
  console.log(that2 === ldh) // true

①:给数组扩展求最大值方法和求和方法

比如: 以前学过 const arr = [1,2,3]。arr.reverse() 结果是 [3,2,1]

扩展完毕之后:arr.sum() 返回的结果是 6

javascript 复制代码
  // 求最大值
  Array.prototype.max = function() {
    return Math.max(...this) // 展开运算符
  }
  Array.prototype.min = function() {
    return Math.min(...this) // 展开运算符
  }
  Array.prototype.sum = function() {
    return this.reduce( (prev,item)=> prev + item,0)
  }

  const arr = new Array(1,2,3) // 数组实例化
  console.log(arr);

  console.log(arr.max());
  console.log(arr.min());
  console.log(arr.sum());

2、constructor 属性

每个原型对象 prototype 里面都有个constructor 属性(constructor 构造函数)

作用: 该属性指向该原型对象的构造函数, 简单理解,就是指向我的爸爸,我是有爸爸的孩子

javascript 复制代码
  function Star() {
  }
  const ldh = new Star()
  console.log(Star.prototype.constructor === Star); // true

constructor 的具体作用讲解:

javascript 复制代码
  // 背景需求:我们在原型中添加函数的时候,有可能需要一次性加很多个。
  function Star2() {
  }

  // Star2.prototype.sing = function() {
  //   console.log('唱歌');
  // }
  // Star2.prototype.dance = function() {
  //   console.log('跳舞');
  // }
  console.log(Star2.prototype); // {constructor: ƒ}
  console.log(Star2.prototype.constructor); //ƒ Star2() {}

  // 我们想到或许可以用这种方法添加函数:
  Star2.prototype = {
    sing: function() {
      console.log('唱歌');
    },
    dance: function() {
      console.log('跳舞');
    }
  }
  console.log(Star2.prototype); //{sing: ƒ, dance: ƒ}
  console.log(Star2.prototype.constructor); // ƒ Object() { [native code] }

  // 但是这种方式出现了问题,这样子的prototype是赋值,不是追加。
  // 原型失去了原本的constructor
javascript 复制代码
// 解决方法是 加一条constructor: Star 重新指回去。这就是加一条constructor的用处
  Star2.prototype = {
    constructor: Star,
    sing: function() {
      console.log('唱歌');
    },
    dance: function() {
      console.log('跳舞');
    }
  }
  console.log(Star2.prototype); // {sing: ƒ, dance: ƒ}
  console.log(Star2.prototype.constructor); // ƒ Star2() {}

3、对象原型


对象都会有一个属性 __proto__指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 __proto__ 原型的存在。


__proto__对象原型里面也有一个 constructor属性,指向创建该实例对象的构造函数

javascript 复制代码
  function Star() {}
  const ldh = new Star()
  console.log(ldh);
  // 当前实例对象指向哪个原型对象prototype
  console.log(ldh.__proto__ === Star.prototype); //true
  
  // __proto__对象原型里面也有一个 constructor属性,指向创建该实例对象的构造函数
  console.log(ldh.__proto__.constructor === Star); //true

构造函数、实例对象、原型的关系

先有构造函数 function Star。用户通过 new Star,创建实例对象。构造函数 Star中 有属性:prototype原型(因为原型是一个对象,所以也叫作原型对象)。

实例对象 new Star()中:

实例对象有一个属性__proto__(对象原型)指向构造函数的 prototype属性(原型对象)。

实例对象的__proto__.constructor指回构造函数 Star。

原型对象prototype中:

constructor指回构造函数 Star。

  • 练习
  1. prototype是什么?哪里来的?

原型(原型对象)

构造函数都自动有原型

  1. constructor属性在哪里?作用干啥的?

prototype原型和对象原型__proto__里面都有

都指向创建实例对象/原型的构造函数

  1. __proto__属性在哪里?指向谁?

在实例对象里面

指向原型 prototype

4、原型继承

继承是面向对象编程的另一个特征,通过继承进一步提升代码封装的程度,JavaScript 中大多是借助原型对象实现继承的特性。

说明:

有女人构造函数Women、男人构造函数Men。

javascript 复制代码
  function Women() {
    this.eyes = 2
    this.head = 1
  }
  const red = new Women()
  console.log(red);


  function Men() {
    this.eyes = 2
    this.head = 1
  }
  const black = new Men()
  console.log(black);

因为构造函数中的属性都是一样,所以可以提取一个Person

javascript 复制代码
  const Person = {
    eyes = 2
    head = 1
  }
  function Women() {}
  function Man() {}

  Women.prototype = Person // 通过原型继承
  Women.prototype.constructor = Women //  补充一个指回

  Man.prototype = Person
  Man.prototype.constructor = Man


  const red = new Women()
  console.log(red);
  const black = new Man()
  console.log(black);

通过原型继承

现在想要给女人增加一个生孩子函数 baby

javascript 复制代码
Women.prototype.baby = function { console.log('生孩子')}

console.log(red);
console.log(black);

结果发现男人也能生孩子。这是因为两者都继承了同一个对象Person

所以通过 Women.prototype = Person 和 Man.prototype = Person 这种继承是不合理的

所以可以通过构造函数 new对象,而不是const 生成对象

javascript 复制代码
function Person() {
    this.eyes = 2
    this.head = 1
}
function Women() {}
function Man() {}

Women.prototype = new Person() // 通过原型继承
Women.prototype.constructor = Women //  补充一个指回

Man.prototype = new Person()
Man.prototype.constructor = Man

//实例验证
Women.prototype.baby = function() {
  console.log('baby')
}
const red = new Women()
console.log(red);
const black = new Man()
console.log(black);

5、原型链

__proto__属性的链状结构

基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链

  • 举例说明
javascript 复制代码
  // function Object() {

  // }

  function Person() {

  }
  const ldh = new Person()

  // 构造函数Person 有prototype(原型)
  console.log(Person.prototype); 
  console.log(Person.prototype.__proto__ === Object.prototype);  // true

  //【前提】:每一个构造函数都有原型,每一个对象都有__proto_属性

  // Person是我们定义的构造函数,构造函数Person 有prototype(原型)
  // 【Person.prototype】是一个对象,每个对象里面都有一个__PROTO__,
  // Person.prototype.__PROTO__指向 "构造出【Person.prototype】这个对象的构造函数 的prototype "  
  // 有一个最大构造函数 Objct,这个构造函数构造出 Person.prototype 
  // 因此,Person.prototype.__PROTO__指向Object 的prototype

原型对象(也就是一个对象实例),指向 构造出这个对象的构造函数(function) 的prototype
原型对象
查找规则

__proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线

查找规则如下:

① 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。

② 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)

③ 如果还没有就查找原型对象的原型(Object的原型对象)

④ 依此类推一直找到 Object 为止(null)

__proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线

⑥ 可以使用 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

javascript 复制代码
const arr = [1,2,3] // arr是对象 相当于 const arr = new Array(1,2,3)
arr.map(function() {
  // 略
})

1、当访问 arr 的方法 map 时,首先查找这个对象自身有没有该属性。
发现没有
2、查找 arr 的原型(也就是 arr.__proto__指向的 prototype 原型对象)
该prototype 原型对象 属于 构造函数 Array的原型,找到了
  • instance of
javascript 复制代码
  const zxy = new Person()
  console.log(zxy instanceof Person); // zxy 属于 Person 吗 : true
  console.log(zxy instanceof Object); // zxy 属于 Object 吗 : true
  console.log(zxy instanceof Array);  // zxy 属于 Array 吗 :  false
  console.log(Array instanceof Object); // true

小结 原型和原型链

1.原型:函数都有prototype属性,称之为原型,也称为原型对象

原型可以放一些属性和方法,共享给实例对象使用

原型可以做继承

2.原型链:查找一个对象的属性和方法的时候,先在自身找,找不到则沿着__proto__向上查找,我们把__proto__形成的链条关系称原型链

__proto__属性是每一个对象以及函数都有的一个属性。__proto__属性指向的是创建他的构造函数的prototype。原型链就是通过这个属性构件的。

相关推荐
CodeClimb5 小时前
【华为OD-E卷 - 第k个排列 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
光头程序员7 小时前
grid 布局react组件可以循数据自定义渲染某个数据 ,或插入某些数据在某个索引下
javascript·react.js·ecmascript
fmdpenny8 小时前
Vue3初学之商品的增,删,改功能
开发语言·javascript·vue.js
小美的打工日记8 小时前
ES6+新特性,var、let 和 const 的区别
前端·javascript·es6
涔溪9 小时前
有哪些常见的 Vue 错误?
前端·javascript·vue.js
程序猿online9 小时前
前端jquery 实现文本框输入出现自动补全提示功能
前端·javascript·jquery
Turtle11 小时前
SPA路由的实现原理
前端·javascript
HsuYang11 小时前
Vite源码学习(九)——DEV流程中的核心类(下)
前端·javascript·架构
傻小胖11 小时前
React 中hooks之useInsertionEffect用法总结
前端·javascript·react.js
蓝冰凌13 小时前
【整理】js逆向工程
javascript·js逆向