Rodrick

vuePress-theme-reco Rodrick    2022
Rodrick Rodrick

Choose mode

  • dark
  • auto
  • light
Home
Category
  • CS基础
  • 数据库
  • 前端
  • 其他
Tag
About
Timeline
D&T
  • 官方文档

    • Vue
    • Vue3
    • Webpack
    • MDN
    • Node中文网
    • React
    • 小程序
    • FineReport
  • 学习面试

    • 现代JavaScript教程
    • ES6
    • 阿西河
    • LeetCode
    • 牛客网
  • 工具

    • bejson
Contact
  • Github
  • Gitee
author-avatar

Rodrick

62

Article

18

Tag

Home
Category
  • CS基础
  • 数据库
  • 前端
  • 其他
Tag
About
Timeline
D&T
  • 官方文档

    • Vue
    • Vue3
    • Webpack
    • MDN
    • Node中文网
    • React
    • 小程序
    • FineReport
  • 学习面试

    • 现代JavaScript教程
    • ES6
    • 阿西河
    • LeetCode
    • 牛客网
  • 工具

    • bejson
Contact
  • Github
  • Gitee
  • JS

    • ES6核心语法
    • 模块化&Webpack
    • Promise&异步函数async
    • WebSocket原理浅析
    • axios的使用
    • ES5的变量提升和ES6的暂时性死区
    • call、apply、bind的用法
    • 对象的原始值转换
    • 可选链"?."
    • 随机数方法
    • 深浅拷贝实现
    • Array常用处理
    • 防抖与节流
    • postMessage窗口间通讯
    • 解构赋值
    • Map&Set
    • 日期和时间
    • 对象属性
    • Toast组件简单封装
    • 原型链
    • 类 Class
    • Generator与异步迭代
    • 偏函数Partial和柯里化Currying
    • DOM操作
    • 事件处理
    • 事件循环EventLoop
    • 网络请求
    • 浏览器中存储数据
    • 正则表达式
  • CSS

  • 其他

原型链

vuePress-theme-reco Rodrick    2022

原型链

Rodrick 2020-11-29 js

# 原型继承[对象]

# 基本用法

let animal = {
  eats: true
};
let rabbit = {
  jumps: true
};

rabbit.__proto__ = animal; // 设置 rabbit.[[Prototype]] = animal

现在,如果我们从 rabbit 中读取一个它没有的属性,JavaScript 会自动从 animal 中获取

let animal = {
  eats: true
};
let rabbit = {
  jumps: true
};

rabbit.__proto__ = animal; // (*)

// 现在这两个属性我们都能在 rabbit 中找到:
alert( rabbit.eats ); // true (**)
alert( rabbit.jumps ); // true

# 继承后覆盖

假设有如下代码

let animal = {
  eats: true,
  sleep() {
    console.log("animal sleep");
  }
};
let rabbit = {
  jumps: true,
  __proto__: animal,
};

rabbit.sleep = function () {
  console.log("rabbir sleep");
}

rabbit.sleep() // rabbir sleep
animal.sleep() // animal sleep

修改继承来的属性,并不会影响到原型链上的其他对象的属性

# ❗关于 this

让我们稍微修改上面的代码

let animal = {
  eats: true,
  sound:"wuwuuu", // 增加 sound
  sleep() {
    console.log(`animal sleep ${this.sound}` );
  }
};
let rabbit = {
  jumps: true,
  __proto__: animal,
};

rabbit.sleep = function () {
  console.log(`rabbir sleep ${this.sound}`);
}

rabbit.sleep() // rabbir sleep wuwuuu
animal.sleep() // animal sleep wuwuuu

会发现, rabbit.sleep 指向的 this 看似指向 animal 里的 this ,实际上,**无论在哪里找到方法:在一个对象还是在原型中。在一个方法调用中,this 始终是点符号 . 前面的对象。**所以这里的 this 一定指向的是 rabbit 。

再来看下这个:

let hamster = {
  stomach: [],

  eat(food) {
    this.stomach.push(food);
  }
};

let speedy = {
  __proto__: hamster
};

let lazy = {
  __proto__: hamster
};

// 这只仓鼠找到了食物
speedy.eat("apple");
alert( speedy.stomach ); // apple

// 这只仓鼠也找到了食物,为什么?请修复它。
alert( lazy.stomach ); // apple

❓为什么 speedy 和 lazy 的 stomach 中都有食物呢?首先方法执行的时候做了这些事:

  1. speedy.eat("apple"); 的时候, speedy 本身没有 eat

  2. 那么会在原型链上找到 hamster  ,然后执行 eat ,接着回到 this(=speedy)中查找 stomach

  3. 但是 this 中并没有 stomach ,然后它对 stomach 调用 push 显然会报错,那么就会去找原型链上的 stomach , 将食物添加到原型 hamster  的 stomach 中

对于 lazy.stomach.push(...) 和 speedy.stomach.push() 而言,属性 stomach 被在原型中找到(不是在对象自身),然后向其中 push 了新数据。

在简单的赋值 this.stomach= 的情况下不会出现这种情况,因为直接赋值的时候,如果没有 stomach ,则会自动在 this 中创建一个 stomach:

let hamster = {
  stomach: [],

  eat(food) {
    // 分配给 this.stomach 而不是 this.stomach.push
    this.stomach = [food];
  }
};

let speedy = {
   __proto__: hamster
};

let lazy = {
  __proto__: hamster
};

// 仓鼠 Speedy 找到了食物
speedy.eat("apple");
alert( speedy.stomach ); // apple

// 仓鼠 Lazy 的胃是空的
alert( lazy.stomach ); // <nothing>

this.stomach= 不会执行对 stomach 的查找。该值会被直接写入 this 对象。

所以我们的结论是:第一个例子 的  animal 代表“方法存储”,rabbit 在使用其中的方法。所以,方法是共享的,但对象状态不是。

直接执行的父级方法里
是直接对属性赋值**,不会改变父级 key 的 value,但是如果方法里**做其他操作会触发原型链上的查找,那么会针对改变的对象就是父级,但是this始终是子级!

# 循环中会怎样

let animal = {
  eats: true
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

// Object.keys 只返回自己的 key
alert(Object.keys(rabbit)); // jumps

// for..in 会遍历自己以及继承的键
for(let prop in rabbit) alert(prop); // jumps,然后是 eats

rabbit.hasOwnProperty("eats")  // false
rabbit.hasOwnProperty("jumps") // true

Object.keys 只会返回自己的 keys ,不会返回继承来的 key,但是 for...in 不管这些,都遍历。
几乎所有其他键/值获取方法,例如 Object.keys 和 Object.values 等,都会忽略继承的属性。
它们只会对对象自身进行操作。不考虑 继承自原型的属性。
如果我们想判断一个属性是继承来的还是自身有的,用 obj.hasOwnProperty(key) 来判断就好了。

# 现代应该使用的__proto__定义方法

__proto__ 被认为是过时且不推荐使用的(deprecated),这里的不推荐使用是指 JavaScript 规范中规定,proto 必须仅在浏览器环境下才能得到支持。

现代的方法有:

  • Object.create(proto, [descriptors]) —— 利用给定的 proto 作为 [[Prototype]] 和可选的属性描述来创建一个空对象。
  • Object.getPrototypeOf(obj) —— 返回对象 obj 的 [[Prototype]]。
  • Object.setPrototypeOf(obj, proto) —— 将对象 obj 的 [[Prototype]] 设置为 proto。
let animal = {
  eats: true
};

// 创建一个以 animal 为原型的新对象
let rabbit = Object.create(animal);
//== let rabbit ={__proto__:animal}

alert(rabbit.eats); // true

alert(Object.getPrototypeOf(rabbit) === animal); // true
//== rabbit.__proto__ == animal

Object.setPrototypeOf(rabbit, {}); // 将 rabbit 的原型修改为 {}
//== rabbit.__proto__ = {}

Object.create 有一个可选的第二参数:属性描述器。我们可以在此处为新对象提供额外的属性,就像这样:

let animal = {
  eats: true
};

let rabbit = Object.create(animal, {
  jumps: {
    value: true //除了 value 以外其他的属性参见【对象属性】文章
  }
});

//== let rabbit = {jumps:true,__proto__:animal}

alert(rabbit.jumps); // true

描述器的格式与 属性标志和属性描述符 一章中所讲的一样。
我们可以使用 Object.create 来实现比复制 for..in 循环中的属性更强大的对象克隆方式:

let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));

上面的方法中,如果我们创建了一个对象 obj ,并且希望他有一个属性就叫做 __proto__ ,那么我们需要使用 Object.create(null) ,这样他的 __proto__ 就不会指向特定的对象

# F.prototype [函数作类继承]

# 基本使用

每个函数都有 prototype 属性,即使我们没有提供它。
默认的 "prototype" 是一个只有属性 constructor 的对象,属性 constructor 指向函数自身。

function Rabbit() {}

/* default prototype
Rabbit.prototype = { constructor: Rabbit };
*/

在这里,Rabbit.prototype 设置为 animal ,那么 rabbit 的 __proto__ 就会被设置为 animal

let animal = {
  eats: true
};

function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype = animal;

let rabbit = new Rabbit("White Rabbit"); //  rabbit.__proto__ == animal

alert( rabbit.eats ); // true


我们在 new 一个新的对象时,可以使用继承的 constructor  ,下面写法效果是一样的:

function Rabbit(name) {
  this.name = name;
  alert(name);
}

let rabbit = new Rabbit("White Rabbit");

let rabbit2 = new rabbit.constructor("Black Rabbit");

# 修改 prototype 会发生什么

  1. 这里直接把 Rabbit 的 prototype 直接整个换掉,这样的情况下,rabbit 实例的 __proto__ **并不会变化,而是仍然指向原来的对象 **{eat:xx} ,但是 Rabbit 的 prototype 指向完全变了

image.png

function Rabbit() {}
Rabbit.prototype = {
  eats: true
};

let rabbit = new Rabbit();

Rabbit.prototype = {};

alert( rabbit.eats ); // true

console.log(rabbit.__proto__); // {eats: true}
  1. 这里我们结合上面的图,可以知道这里 Rabbit.prototype 的指向没变化,所以 rabbit.__proto__ 的 eats 自然就是 false 了
function Rabbit() {}
Rabbit.prototype = {
  eats: true
};

let rabbit = new Rabbit();

Rabbit.prototype.eats = false;

alert( rabbit.eats ); // false

console.log(rabbit.__proto__); // {eats: false}
  1. 我 rabbit 又没有 eats 属性,你删我的有啥用?当然还是 true 了
function Rabbit() {}
Rabbit.prototype = {
  eats: true
};

let rabbit = new Rabbit();

delete rabbit.eats;

alert( rabbit.eats ); // true
  1. 这样删就对了嘛,你要删就去删我继承的人啊
function Rabbit() {}
Rabbit.prototype = {
  eats: true
};

let rabbit = new Rabbit();

delete Rabbit.prototype.eats;

alert( rabbit.eats ); // undefined
  1. constructor 是在 默认的 prototype 里的,你把我的 prototype 指向改了,我还怎么拿我的构造器,但是他会追根溯源找到 constructor 为止,那么他一路就找到了 Object ,最后其实是 let user2 = new Object('Pete') ,这不是我们想要的结果。
function User(name) {
  this.name = name;
}
User.prototype = {}; // (*)

let user = new User('John');
let user2 = new user.constructor('Pete');

alert( user2.name ); // undefined

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

image

function Person() { }
// Person.prototype 构造函数 -> 原型
Person.prototype.name = "p"
// 构造函数 -> 实例
let person = new Person()
// 原型 -> 构造函数
console.log(Person.prototype.constructor === Person); // true
// 实例 -> 原型
console.log(person.__proto__ === Person.prototype); //true
// 实例 -> 构造函数
console.log(person.__proto__.constructor===Person); //true

在读取一个实例的属性的过程中,如果属性在该实例中没有找到,那么就会循着 __proto__ 指定的原型上去寻找,如果还找不到,则尝试寻找原型的原型

再看一个这个图: 我们用 let d = new Date() 来做例子

image

红色线的才是 d 的原型链!

Date 的原型是 Object 的实例,所以 Date.prototype.__proto__===Object.prototype

在构造函数层面:

// Object 是构造函数,所有构造函数都是 Function 的实例
Object.__proto__===Function.prototype
// Function 的原型又是 Object 的实例
Function.prototype.__proto__===Object.prototype
// Object 的原型显然不是 Object 的实例,他不继承于任何
Object.prototype.__proto__===null
欢迎来到 Rodrick
看板娘