JavaScript 原型链、instanceof、new、继承、Class

目录

1. JavaScript 创建对象的三种方式

1.1 使用 new 创建对象 VS 使用 字面量 创建对象

1.2 使用 Object.create() 创建对象(使用 现有对象 提供 新建对象的 __proto__)

2. 构造函数是什么,有什么用呢

2.1 构造函数是什么

2.2 对象、构造函数、创建函数对象方法 的关系

2.3 constructor 属性在哪里

3. prototype 是什么,有什么用呢

3.1 prototype 的作用 —— 存放实例对象共享的属性方法

3.2 constructor 属性真正的位置 —— 在 prototype 里

4. __proto__ 是什么,有什么用呢

4.1 [[Prototype]] 属性 —— 不对外提供访问

4.2 constructor 不可信

4.3 对象.__proto__ —— 访问并修改 prototype 上的属性方法

4.3.1 [[Prototype]] VS __proto__ —— 区别在于能否被外部访问

4.3.2 Object.getPrototypeOf / Object.setPrototypeOf —— ES6中用于替换 __proto__

4.4 proto、prototype、constructor 的关系

5. 原型模式的基本概念 / 原型链查找原理

5.1 原型模式的基本概念

5.2 原型链是什么?和作用域有什么区别?

5.3 JavaScript 内置函数的原型链

6. instanceof

6.1 instanceof 是什么

6.2 instanceof 与 typeof 的区别

6.3 手写 instanceof

7. new 一个对象发生了什么

7.1 new 对象的基本步骤 / 构造函数有返回值的理解

7.2 手写 new

8. 关于 继承、Class 的参考文章


何谓原型链?

当某个对象上的方法或属性不存在时,会在它的原型上去查找;

如还不存在,就会去它原型的原型上查找;

这样形成了一条链路,即原型链;

1. JavaScript 创建对象的三种方式

有人说 JavaScript 一切皆对象,是错误的!原始值就不是

1.1 使用 new 创建对象 VS 使用 字面量 创建对象

先来看看 使用 new 创建对象:

var person = new Object();
person.name = "TeaMeow";
person.sayHello = function () {
  console.log("Hello!");
};

再来看看 使用 字面量 创建对象:

var person = { } 等价于 var person = new Object()

var person = {
​    name: 'TeaMeow',
​    sayHello: function () {
​      console.log('Hello!')
​    }
  }

1.2 使用 Object.create() 创建对象(使用 现有对象 提供 新建对象的 __proto__

Object.create() —— 使用 现有对象 提供 新建对象的 __proto__

假设 me 是新建对象,那么 me.__proto__=== person

// 现有对象 person
var person = {
​    name: 'TeaMeow',
​    sayHello: function () {
​      console.log('Hello!')
​    }
 }

// 将 现有对象person 作为 新建对象me 的 __proto__
 var me = Object.create(person);
 me.name = 'miaomiao';

2. 构造函数是什么,有什么用呢

2.1 构造函数是什么

任何函数都可以作为构造函数,之所以有构造函数与普通函数之分,是因为功能不同

构造函数通过 new 实例化对象,它可以为 初始化对象 添加 属性 和 方法

function Person() {}
var person1 = new Person();
var person2 = new Person();

person1.constructor === Person; // true
person2.constructor === Person; // true

上面的 person1 和 person2 都是通过 Person 函数实例化出来的

Person 函数就是当前 person1 和 person2 的构造函数

对象上的 constructor 属性,可以指明对象的构造函数是啥

2.2 对象、构造函数、创建函数对象方法 的关系

上面的 Person 函数,本身也是一个对象,那么这个对象是如何实例化出来的呢? 它的 constructor 又指向谁呢?这就涉及到 创建函数对象 的方法 Function

对象、构造函数、函数对象的关系示例:

  • person1 对象,由 Person 函数实例化得到;person1.constructor === Person 函数 
  • Person 函数,由 JavaScript 内置函数 Function 函数实例化得到;Person.constructor === Function 函数
  • 而 Function 本身是构造函数,它的 constructor 是本身,Function.constructor === Function

2.3 constructor 属性在哪里

打印上面的例子中的 person1,查看 constructor 属性,打印如下: 

会发现看不见 constructor 属性,为什么呢?

明明可以访问到啊(person1.constructor === Person),这个问题的答案解释在 3.2 

3. prototype 是什么,有什么用呢

3.1 prototype 的作用 —— 存放实例对象共享的属性方法

给构造函数设置一个 prototype 属性【它其实是个对象】,用于存放 所有实例对象 共享 的属性和方法,可以有效解决 内存浪费 问题

function Person() {}

Person.prototype.hairColor = "back";
Person.prototype.sayHello = function () {
  console.log("Hello!");
};

var person1 = new Person();
var person2 = new Person();

所有函数都可以是构造函数,因此,所有函数都具有 prototype 属性,包括 Function() 函数

3.2 constructor 属性真正的位置 —— 在 prototype 里

  • 如果实例打印时,每个实例都给赋值了一个 constructor 属性,会导致内存浪费
  • 因为他们的 constructor 可能是一样的,不如放在 prototype 里,以节约内存

4. __proto__ 是什么,有什么用呢

4.1 [[Prototype]] 属性 —— 不对外提供访问

person1.[[Prototype]]  === Person.prototype

person1.[[Prototype]].hairColor === Person.prototype.hairColor

[[Prototype]] 属性,是内部隐藏属性,不对外提供访问

因此,通过 对象.[[Prototype]] 无法查看和修改原型 prototype 上的属性和方法

4.2 constructor 不可信

举个栗子~~

function Person() {}

Person.prototype.hairColor = "black";
Person.prototype.sayHello = function () {
  console.log("Hello!");
};

var person1 = new Person();
var person2 = new Person();

====================================================

function Dog() {}

person1.constructor = Dog;

Dog.prototype.hairColor = "red"; // 在 Dog.prototype 上定义 hairColor 属性

console.log(person1.constructor); // Dog
console.log(person2.constructor); // Person
console.log(person1.hairColor); // black

hairColor 是 black 而不是 red,所以通过 constructor 获取实例的构造函数,然后获取共享属性(hairColor)的方法不可取

4.3 对象.__proto__ —— 访问并修改 prototype 上的属性方法

后来许多浏览器厂商实现了 __proto__属性 ,(最开始是火狐浏览器提供的)

__proto__指向了 [[Prototype]],可以通过 对象.__proto__ 得到对象 原型对象 prototype 上的属性和方法,同样,也可以去修改

4.3.1 [[Prototype]] VS __proto__ —— 区别在于能否被外部访问

对象.[[Prototype]] = 创建自己的构造函数内部的 prototype(原型对象),无法外部访问

对象.__proto__ = 创建自己的构造函数内部的 prototype(原型对象),可以外部访问

对象.__proto__ = 对象.[[Prototype]]

Function.__proto__ === Function.prototype

4.3.2 Object.getPrototypeOf / Object.setPrototypeOf —— ES6中用于替换 __proto__

__proto__在 ES6 以前不是 JavaScript 标准,是浏览器提供的

由于越来越流行,运用广泛,在 ES6 规范中,被标准化为传统功能,以确保 Web 浏览器的兼容性

现在已经不推荐使用 __proto__ 了,推荐使用 Object.getPrototypeOf / Object.setPrototypeOf

4.4 proto、prototype、constructor 的关系

JavaScript 中,对象分为两种:

  • 普通对象
  • 函数对象

__proto__(指向构造函数的 prototype)是对象独有的;函数创建的对象.__proto__ === 该函数.prototype

constructor(指向对象的构造函数) 是对象独有的; 构造函数.prototype.constructor === 该构造函数本身

prototype(包含所有实例提供共享的属性和方法)是函数独有的;

在 JavaScript 中,函数也是对象,所以函数也拥有 __proto__ 和 constructor 属性

console.log(Function.prototype === Function.__proto__); // true
console.log(Object.__proto__ === Function.prototype); // true
console.log(Function.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true

5. 原型模式的基本概念 / 原型链查找原理

5.1 原型模式的基本概念

JavaScript 的数据类型包括:基础类型 + 引用类型

由于没有类(ES6 引入了 class,但只是语法糖)的概念,如何将所有对象联系起来,是个问题,于是有了原型和原型链的概念:

  1. 所有的引用类型(数组、对象、函数)都有一个 __proto__属性(隐式原型属性),本质是个对象
  2. 所有的函数,都有一个 prototype(显式原型属性),存放了其实例可共享的属性和方法。
  3. 对象的 __proto__ 等于实例这个对象的构造函数的 prototype

所谓的原型链上去查找,其实就是通过对象的__proto__去查找(这个链可以理解为用__proto__去连接)

  1. 先查看实例上是否具有该属性,即 对象.属性 是否有值
  2. 如果 1 中没找到,就去实例的原型对象(__proto__)找有没有该属性,即 对象.__proto__ 是否有值
  3. 如果 2 中没找到,就取 对象.__proto__.__proto__ 找有没有该属性,一直通过 __proto__ 链接下去,直到终点 null

5.2 原型链是什么?和作用域有什么区别?

访问一个对象上的属性时,如果该对象内部不存在这个属性,那么就会去它__proto__属性所指向的对象(原型对象)上查找。如果原型对象依旧不存在这个属性,那么就会去其原型的__proto__属性所指向的原型对象上去查找。以此类推,直到找到 null,而这个查找的线路,也就构成了我们常说的原型链

原型链和作用域的区别: 

  • 原型链是查找对象上的属性
  • 作用域链是查找上下文中的变量

5.3 JavaScript 内置函数的原型链

Array.prototype // []

Array.__proto__ === Function.prototype

Array.__proto__.__proto__  === Object.prototype

Array.__proto__.__proto__.__proto__ === null

Objecy.prototype // {}

Objecy.__proto__ // Function.prototype

Function.prototype // ƒ ()

Function.__proto__ // Function.prototype

String.prototype // 空字符串

String.__proto__ // Function.prototype

6. instanceof

6.1 instanceof 是什么

instanceof 可以判断:一个对象的原型链上,是否包含该构造函数的原型(也就是说 —— 对象是否为该构造函数的实例)

console.log(Object instanceof Object); // true
console.log(Function instanceof Function); // true
console.log(Function instanceof Object); // true
console.log(function() {} instanceof Function); // true
。

6.2 instanceof 与 typeof 的区别

instanceof —— 判断对象是否为某个构造函数的实例

typeof —— 判断一个变量的数据类型

typeof 可以用来判断 number、undefined、symbol、string、function、boolean、object 这七种数据类型【特殊情况:typeof null === 'object'】

6.3 手写 instanceof

function instanceOf(obj, fn) {
  // 获取对象的 __proto__
  let proto = obj.__proto__;

  if (proto) {
    // 如果对象的__proto__ 和 构造函数的 prototype 一致
    if (proto === fn.prototype) {
      // 证明 当前对象 是 构造函数 的实例
      return true;
    } else {
      // 递归调用,顺着原型链往上找
      return instanceOf(proto, fn);
    }
  // 如果没有 __proto__ 证明走到了原型链的尽头 null
  } else {
    return false;
  }
}

// 测试
function Dog() {}
let dog = new Dog();
console.log(instanceOf(dog, Dog), instanceOf(dog, Object)); // true true

7. new 一个对象发生了什么

7.1 new 对象的基本步骤 / 构造函数有返回值的理解

基本步骤:

  • 修改原型 —— 创建一个对象,该对象的原型,指向构造函数的原型
  • 修改 this —— 调用该构造函数,构造函数的 this,指向新生成的对象
  • 确定返回值 —— 判断构造函数是否有返回值,如果有返回值,且返回值是一个对象或一个方法,则返回该值;否则返回新生成的对象

关于构造函数有返回值的理解:

function Dog(name) {
  this.name = name;
  return { test: 1 };
}
let obj = new Dog("aaa");
console.log(obj); // { test: 1 } 

7.2 手写 new

利用 Object.create() 生成一个对象,新生成对象的原型,是构造函数的原型

function selfNew(fn, ...args) {
  // 创建一个 instance 对象,该对象的原型是 fn.prototype
  let instance = Object.create(fn.prototype);

  // 调用构造函数,使用 apply,将 this 指向新生成的对象
  let res = fn.apply(instance, args);

  // 如果fn函数有返回值,并且返回值是一个对象或方法,则返回该对象,否则返回新生成的 instance 对象
  return typeof res === "object" || typeof res === "function" ? res : instance;
}

8. 关于 继承、Class 的参考文章

【THE LAST TIME】一文吃透所有JS原型相关知识点 - 掘金【THE LAST TIME】一直是我想写的一个系列,旨在厚积薄发,重温前端。 也是给自己的查缺补漏和技术分享。 欢迎大家多多评论指点吐槽。 首先我想说,【THE LAST TIME】系列的的内容,向来都是包括但不限于标题的范围。 再回来说原型,老生常谈的问题了。但是着实 现在…https://juejin.cn/post/6844903984335945736

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lyrelion

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值