JavaScript-原型、原型链

95 阅读4分钟

原型

JavaScript中的原型

prototype是一个给对象提供共享属性的对象,也就是prototype也是一个对象,保存一些共享属性

怎么让一个对象为另一个对象提供属性的访问

原型为构造函数的实例化对象提供了一个公共的空间,来存放一些共享属性,怎么让实例化对象,来访问这些公共属性

通过隐式原型的引用,每一个对象都有一个隐式原型

image-20230222164100957.png

在我们创建对象的时候,对象被挂载了另外一个 _ _ proto _ _ 这个属性,值是一个对象,隐式就是并不是开发者自己创建的属性

怎么访问

  • Object.getPrototypeOf(obj)方法,访问一个对象的原型
  • Object.setPrototypeOf(obj, anotherObj )方法,设置一个对象的隐式原型指向另一个对象prototype
  • 通过实例化对象上的_ _ proto _ _进行访问

试着将一个对象的原型设置为另一个对象

let abj1 = {
  a: 1
}, obj2 = {
  b: 2
}
// 也就是任意能提供公共属性给其他对象访问,就是prototype,现在的obj2就是obj1的原型
​
Object.setPrototypeOf(obj1, obj2)

image-20230222171601053.png

实例对象创建时,要将实例对象的隐式原型指向构造函数prototype

原型链

prototype是一个提供共享属性的对象,作为对象,就有隐式原型,指向创建它的构造函数的显示原型,直到隐式原型为null

函数

一个普通的函数在创建时,自带了一个prototype属性,这个属性的隐式原型指向Object.prototype

image-20230222172614356.png

创建一个对象

let obj = {a: 1, b: 2} 经历了什么

  • 创建一个空对象
  • 指定空对象的prototype
  • 给这个空对象(已指定prototype)初始化属性
// 假设并没有隐式指定prototype,就是一个什么都没有的空对象,每个对象
// 默认身上都有一个隐式原型,但假设此时没有指定它的prototype,通过new操作符
// 或者字面量方式创建,会一步完成上面这三步,完成的事情

let obj = {}; 

//这一步就是将obj.__proto__ = Object.prototype,所以__proto__看到的值就是Object.prototype的值了

Object.setPrototypeOf(obj, Object.prototype); 

//初始化属性

obj.a = 1; obj.b = 2; 
通过Object.create()
  • 该方法有两个参数,arg1是要给新建的这个对象指定的prototype;arg2(可选参数)是一个非undefined的对象,指定创建对象的初始化自身的可枚举属性

    let obj = Object.create(Object.prototype, { a: { value: 1, enumerable: true }, b: { value: 2, enumerable: true } })就完成了上面三步的操作,隐式的做完了这一切

image-20230223093623150.png

通过构造函数的形式

内置的一些构造函数

Array Object Number String Boolean

通过new 调用构造函数的方式创建,同时也是完成了上面的那三步操作

就像new操作符做的事

  • 创建一个空对象 (第一步,创建空对象)
  • 指定这个空对象的_ _ proto _ _为构造函数的prototype【每个对象默认携带一个 _ _ proto _ _ 属性,每个函数默认携带一个prototype】 (第二步,指定原型)
  • 调用构造函数,并指定构造函数的this,为当前的空对象,执行构造函数和的代码(第三步,初始化对象自身的可枚举属性)
  • 判断构造函数的返回值是否为函数或者对象,如果是就返回构造函数的返回值,否就返回上面创建的那个对象
通过class形式

先看一下class定义构造函数的结构

class Person {
  // constructor
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  // 【计算属性部分】
  // getter
  get personInfo() {
    return this.firstName + ' ' + this.lastName
  }
  
  // setter
  set personInfo(newValue) {
    this.firstName = newValue.split(' ')[0];
    this.lastName = newValue.split(' ')[1];
  }
  
  // 【实例化对象公共方法部分,也就是prototype】
  // method
  showMyself() {
    console.log(`大家好,我的名字叫${this.personInfo}`)
  }
  
  // 【构造函数内部属性】
  // static method
  static toUpperCaseName(thisArg) {
    return thisArg.personInfo.toUpperCase();
  }
  
  // static attr
  static constructorName = 'Person';
​
  // 【实例化对象内部属性】每个实例化对象都有
  toLowerCaseName = function(thisArg) {
    return thisArg.personInfo.toLowerCase();
  }
  constructorName1 = 'Person';
}

下面分析一下上面class语法糖的结构

constructor构造器部分:就相当于构造函数内部的代码部分,放实例化对象私有的属性,也就是每个实例化对象他们属性键值对,键相同,值不同

getter和setter计算属性部分:主要存放一些通过实例对象属性构成的一些表达式

method公共对象【prototype】部分:用在存放实例化对象的一些公共的方法

实例化对象内部属性:也就是实例化对象,都有的键值对相同的一些属性,但每个实例化对象关于这些属性键值对的存储地址不同

static构造函数内部属性:在构造函数这个对象身上存放一些属性值或方法

现在看一下实例化一个person对象

先查看一下Person构造函数

image-20230223135455604.png

在看一下实例化对象person

image-20230223135737407.png

class完成的步骤和构造函数相同,继承方法依托于prototype对象

class中的extends和super,完成两个构造函数之间的继承

class Person {
  // constructor
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
​
  // getter
  get personInfo() {
    return this.firstName + ' ' + this.lastName
  }
​
  // setter
  set personInfo(newValue) {
    this.firstName = newValue.split(' ')[0];
    this.lastName = newValue.split(' ')[1];
  }
​
  // method
  showMyself() {
    console.log(`大家好,我的名字叫${this.personInfo}`)
  }
​
  // static method
  static toUpperCaseName(thisArg) {
    return thisArg.personInfo.toUpperCase();
  }
​
  // static attr
  static constructorName = 'Person';
​
  // 实例化对象都有的属性名和值
  toLowerCaseName = function (thisArg) {
    return thisArg.personInfo.toLowerCase();
  }
  constructorName1 = 'Person';
}
​
class Student extends Person {
  constructor(firstName, lastName) {
    super(firstName, lastName);
  }
}
​
​
let person = new Person('zhang', 'san');
let student = new Student('wang', 'wu');

image-20230223142514271.png

完成继承就是修改原型,Student.prototype.constructor = Person; Object.setPrototypeOf(Student.prototype, Person.prototype)完成原型链的继承,通过call方法执行Person构造函数实现对于属性的继承,在Student构造函数中执行了Person.call(this, firstName, lastName)

如上图所示,只要是实例对象上的静态属性、静态方法、动态属性都继承了,原型也实现了绑定

问题

什么是原型

原型就是一个存放公共属性的对象,在JavaScript中每一个对象都有一个隐式引用_ _ proto _ _ ,每一个对象都可以作为另一个对象的原型用来存放一些公共的属性

原型链是什么,有什么作用

原型是一个对象,每一个对象里面都有一个 _ _ proto _ _隐式引用,指向构造函数的prototype或者null,这样就形成了原型链,用于保存一些公共属性

原型链的作用,在访问一个对象的属性的时候,会现在当前对象属性上查找,如果找不到沿着原型链查找,直到null为止,找到了就返回值,找不到就返回undefined

怎么实现原型的继承

有两种方式实现原型链的继承,一种是Object.create(prototype)这种形式 , 另外一种是Object.setPrototypeOf(实例对象, 指向的原型对象)