JavaScript学习日记 : Day14 - 原型与继承(一)

在写程序时,我们经常会想要拓展一些东西。

例如我们有一个user object,他有自己的属性跟函数,我们希望将admin与guest基於user稍作修改,重用user中的内容,并不是复制,只是在user上建构一个新的对象。

原型继承(Prototypal inheritance)这个语言特性能帮助我们实现这个需求。

1. [[prototype]]

在JavaScript中,对象有一个特殊的隐藏属性,不是null就是对另一个Object的引用,该对像称为原型

当我们从object读取一个不存在的属性时,JavaScript会自动从原型中找寻获取该属性,这就是原型继承。[[prototype]]是隐藏的,但有另一个属性可以获取到原型 :

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

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

// 现在这两个属性我们都能在 cat 中找到:
console.log( cat.eats ); // true 
console.log( cat.jumps ); // true

如果animal中有个函数,他一样可以在cat中取用 :

let animal = {
  eats: true,
  walk() {
    alert("Animal walk");
  }
};

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

// walk 方法是从原型中获得/继承的
rabbit.walk(); // Animal walk

原型链可以很长 :

let animal = {
  eats: true,
  walk() {
    alert("Animal walk");
  }
};

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

let foldEar = {
  earLength: 2,
  __proto__: cat
};

// walk 一样透过原型链取得
longEar.walk(); // Animal walk
alert(foldEar.jumps); // true (继承cat)

这里有两个限制

  1. 引用不能形成闭循环,JavaScript会报错
  2. __prototype__的值可以是object,也可以是null,其他类型都会被忽略(但还是可以设置)。

当然只能有一个[[prototype]]。

__proto__是[[prototype]]的历史原因而留下来的getter/setter
这两个是不一样的,__prototype__是[[prototype]]的getter/setter。
__proto__属性有点过时了,他的出现是因为历史原因。现代编程建议我们使用函数Object.getPrototypeof/Object.setPrototypeOf来取代__prototype__去get/set原型。

2. 原型链查找规则

原型只用於读取属性,如果要写入或删除属性直接在object中操作。

在下面的例子我们将cat分配自己的walk :

let animal = {
  eats: true,
  walk() {
    alert("Animal walk");
  }
};

let cat = {
  __proto__: animal
};

cat.walk = function() {
  console.log("cat walk!");
};

cat.walk(); // cat walk!

访问器(accessor)是一个例外,因为assignment操作是由setter函数处里的。因此,写入此类型属性时实际上跟调用函数相同。

参考以下代码 :

let user = {
  name: "John",
  surname: "Smith",

  set fullName(value) {
    [this.name, this.surname] = value.split(" ");
  },

  get fullName() {
    return `${this.name} ${this.surname}`;
  }
};

let admin = {
  __proto__: user,
  isAdmin: true
};

alert(admin.fullName); // John Smith 

// setter 作用
admin.fullName = "Alice Cooper"; // 在admin object中新增name与surname

alert(admin.fullName); // Alice Cooper,admin 的内容被修改了
alert(user.fullName);  // John Smith,user 的内容被保护了

3. for...in回圈

for..in回圈也会把继承的属性算入 :

let animal = {
  eats: true
};

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

// Object.keys 只返回自己的属性
console.log(Object.keys(cat)); // jumps

// for..in 会遍历自己以及继承的属性
for(let prop in cat) console.log(prop); // jumps,eats

如果只是要object本身的属性,并不想要继承的属性,那可以使用内建方法obj.hasOwnProperty(key) :

let animal = {
  eats: true
};

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

for(let prop in cat) {
  let isOwn = cat.hasOwnProperty(prop);

  if (isOwn) {
    console.log(`Our: ${prop}`); // Our: jumps
  } else {
    console.log(`Inherited: ${prop}`); // Inherited: eats
  }
}

cat从animal中继承,animal从Object.prototype中继承(默认继承),然後像上是null。

那cat.hasOwnProperty方法从哪来的? 图中可以看到是Object.prototype.hasOwnProperty提供的,换句话说是继承的,那为什麽for..in回圈并没有像eat和jumps那样出现在回圈中呢?

Ans: 因为它是不可枚举的。就像object.prototype的其他属性,hasOwnProperty有enumerable:false标志。并且for...in只会列出可枚举的属性。


<<:  [Day 11] 让tinyML听见你的呼唤

>>:  110/11 - 把照片储存在Pictures/应用程序名称资料夹 - 1

目前销售流程遇到什麽问题?

在探讨这个问题之前,先来分享我们目前的销售流程 搜集名单 透过脸书投放名单型广告,或是 Google...

[ Day 35 ] - Electron 应用程序 - 更新自动化 ( 说明篇 )

已经打包拿到客户那的 Electron 应用程序 , 如果有 BUG 需要更新时该怎麽办呢 ? 一般...

[Day16]程序菜鸟自学C++资料结构演算法 – 优先伫列Priority Queue和堆积Heap

前言:在第11天的时候我们有讨论到伫列,今天就是来把之前的坑给补上的,先前没有提到的就是等等要介绍的...

[Day5]-串列的相关用法

字串 函数应用方法 len() – 字串长度 min() – 最小值 max() – 最大值 使用...

全端入门Day24_後端程序撰写之多一点点的Node.js

昨天介绍了一些名词,今天继续提Node.js Node.js一点入门 今天直接贴上程序码,再去做解释...