目录
JavaScript原型、继承和类
原型
Prototype
在 JavaScript 中,对象有一个特殊的隐藏属性 [[Prototype]]
,它要么为 null
,要么就是对另一个对象的引用。当我们从 object
中读取一个缺失的属性时,JavaScript 会自动从原型中获取该属性
let animal = {
eats: true,
sleep: true
};
let rabbit = {
jumps: true
};
rabbit.__proto__ = animal; // (*)
// 现在这两个属性我们都能在 rabbit 中找到:
alert( rabbit.eats ); // true
alert( rabbit.sleep ); // true
alert( rabbit.jumps ); // true
原型链
就近继承
__proto__
和 [[Prototype]]
的区别
__proto__
是[[Prototype]]
的 getter/setter__proto__
属性有点过时了。它的存在是出于历史的原因,现代编程语言建议我们应该使用函数Object.getPrototypeOf/Object.setPrototypeOf
来取代__proto__
去 get/set 原型
for…in与继承
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
如果需要过滤掉原型里的属性,可以使用hasOwnProperty
在条件语句体内排除
F.prototype
- 如果
F.prototype
是一个对象,那么new
操作符会使用它为新对象设置[[Prototype]]
。
// 设置 Rabbit.prototype = animal 的字面意思是:“当创建了一个 new Rabbit 时,把它的 [[Prototype]] 赋值为 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
- 默认的 F.prototype,构造器属性
每个函数都有 "prototype"
属性,即使我们没有提供它。默认的 "prototype"
是一个只有属性 constructor
的对象,属性 constructor
指向函数自身。上面的方法有一个企业宣布没把虎眼石让关于替换掉整个prototype,正确的做法是选择添加或删除prototype的属性,如Rabbit.prototype.eat
,或者手动重建construor属性,如construor: Rabbit
原生的原型
- Object.prototyped的原型是null
- 其他对象的原型都继承自Object,形成原型链,提高了存储效率
- 基本数据类型也有原型,它们并不是对象。但是如果我们试图访问它们的属性,那么临时包装器对象将会通过内建的构造器
String
、Number
和Boolean
被创建。它们提供给我们操作字符串、数字和布尔值的方法然后消失
String.prototype.show = function() {
alert(this);
};
"BOOM!".show(); // BOOM!
从原型中借用
借用是指我们从一个对象获取一个方法,并将其复制到另一个对象。
let obj = {
0: "Hello",
1: "world!",
length: 2,
};
obj.join = Array.prototype.join;
alert( obj.join(',') ); // Hello,world!
上面这段代码有效,是因为内建的方法 join
的内部算法只关心正确的索引和 length
属性。它不会检查这个对象是否是真正的数组。
原型方法,没有 proto 的对象
不推荐使用__proto__
,推荐使用以下方法来代替:
- Object.create(proto, [descriptors])—— 利用给定的
proto
作为[[Prototype]]
和可选的属性描述来创建一个空对象。 - Object.getPrototypeOf(obj)—— 返回对象
obj
的[[Prototype]]
。 - Object.setPrototypeOf(obj, proto)—— 将对象
obj
的[[Prototype]]
设置为proto
。
类
基本语法
class User {
constructor(name) {
this.name = name;
}
sayHi() {
alert(this.name);
}
}
// 用法:
let user = new User("John");
user.sayHi();
class的本质
在JavaScript中类的本质是函数。
class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
// 佐证:User 是一个函数
alert(typeof User); // function
以上代码本质是
- 创建一个名为
User
的函数,该函数成为类声明的结果。该函数的代码来自于constructor
方法(如果我们不编写这种方法,那么它就被假定为空)。 - 存储类中的方法,例如
User.prototype
中的sayHi
与函数的区别:
- 通过
class
创建的函数具有特殊的内部属性标记[[IsClassConstructor]]: true
。因此,它与手动创建并不完全相同,必须使用new
来调用它 - 类方法不可枚举。 类定义将
"prototype"
中的所有方法的enumerable
标志设置为false
- 类总是使用
use strict
。 在类构造中的所有代码都将自动进入严格模式
类表达式
类可以在另外一个表达式中被定义,被传递,被返回,被赋值,如果类表达式有名字,那么该名字仅在类内部可见
// “命名类表达式(Named Class Expression)”
// (规范中没有这样的术语,但是它和命名函数表达式类似)
let User = class MyClass {
sayHi() {
alert(MyClass); // MyClass 这个名字仅在类内部可见
}
};
new User().sayHi(); // 正常运行,显示 MyClass 中定义的内容
alert(MyClass); // error,MyClass 在外部不可见
我们甚至可以动态的按需创建类:
function makeClass(phrase) {
// 声明一个类并返回它
return class {
sayHi() {
alert(phrase);
}
};
}
// 创建一个新的类
let User = makeClass("Hello");
new User().sayHi(); // Hello
计算属性名称
使用中括号 [...]
的计算方法名称示例:
class User {
['say' + 'Hi']() { // 计算属性方法
alert("Hello");
}
}
new User().sayHi();
类字段
- 类字段使用如下:
class User {
name = "John";
}
let user = new User();
alert(user.name); // John
alert(User.prototype.name); // undefined
-
使用类字段制作绑定方法:
JavaScript 中的函数具有动态的
this
。它取决于调用上下文。因此,如果一个对象方法被传递到某处,或者在另一个上下文中被调用,则this
将不再是对其对象的引用。像下面这种情况就是出现了this
丢失:
class Button {
constructor(value) {
this.value = value;
}
click() {
alert(this.value);
}
}
let button = new Button("hello");
setTimeout(button.click, 1000); // undefined
解决方案:
- 传递一个包装函数,例如
setTimeout(() => button.click(), 1000)
- 将方法绑定到对象,例如在 constructor 中
- 使用箭头函数**(推荐)**
class Button {
constructor(value) {
this.value = value;
}
click = () => {
alert(this.value);
}
}
let button = new Button("hello");
setTimeout(button.click, 1000); // hello
// 类字段 click = () => {...} 是基于每一个对象被创建的,在这里对于每一个 Button 对象都有一个独立的方法,在内部都有一个指向此对象的 this。我们可以把 button.click 传递到任何地方,而且 this 的值总是正确的
类继承
语法Child extend Parent
- 重写方法
注意:箭头函数没有super
class Animal {
constructor(name) {
this.speed = 0;
this.name = name;
}
run(speed) {
this.speed = speed;
alert(`${this.name} runs with speed ${this.speed}.`);
}
stop() {
this.speed = 0;
alert(`${this.name} stands still.`);
}
}
class Rabbit extends Animal {
hide() {
alert(`${this.name} hides!`);
}
stop() {
super.stop(); // 调用父类的 stop
this.hide(); // 增加功能
}
}
let rabbit = new Rabbit("White Rabbit");
rabbit.run(5); // White Rabbit runs with speed 5
rabbit.stop(); // White Rabbit stands still. White rabbit hides!
- 重写constructor
- 继承类的 constructor 必须调用
super(...)
,并且 (!) 一定要在使用this
之前调用 - 父类构造器总是会使用它自己字段的值,而不是被重写的那一个
- 可以在一个
Child
方法中使用super.method()
来调用Parent
方法 - 方法在内部的
[[HomeObject]]
属性中记住了它们的类/对象。这就是super
如何解析父方法的。因此,将一个带有super
的方法从一个对象复制到另一个对象是不安全的。
静态属性与静态方法
-
静态方法:
可以把一个方法赋值给类的函数本身,而不是赋给它的
"prototype"
。这样的方法被称为 静态方法.通常,静态方法用于实现属于该类但不属于该类任何特定对象的函数。静态方法也被用于与数据库相关的公共类,可以用于搜索/保存/删除数据库中的条目
class User {
static staticMethod() {
alert(this === User);
}
}
User.staticMethod(); // true
- 静态属性
class Article {
static publisher = "Levi Ding";
}
alert( Article.publisher ); // Levi Ding
// 等价于Article.publisher = "Levi Ding";
- 继承静态属性和方法
静态方法也可以继承
私有的和受保护的属性和方法
-
内部接口和外部接口
- 内部接口是可以通过该类的其他方法访问,但不能从外部访问的方法和属性。
- 外部接口是也可以从类的外部访问的方法和属性。
-
设置受保护的属性:
- 受保护的属性通常以下划线
_
作为前缀。 - 只读:设置为访问器属性,且不设置setter
- 受保护的属性通常以下划线
类检查
- instanceof操作符
instanceof
操作符用于检查一个对象是否属于某个特定的 class。同时,它还考虑了继承。obj instanceof Class
如果 obj
隶属于 Class
类(或 Class
类的衍生类),则返回 true
- toString方法
自己创建的对象可以使用特殊的对象属性 Symbol.toStringTag
自定义对象的 toString
方法的行为。
let user = {
[Symbol.toStringTag]: "User"
};
alert( {}.toString.call(user) ); // [object User]
Mixin
在 JavaScript 中构造一个 mixin 最简单的方式就是构造一个拥有实用方法的对象,以便我们可以轻松地将这些实用的方法合并到任何类的原型中。
// mixin
let sayHiMixin = {
sayHi() {
alert(`Hello ${this.name}`);
},
sayBye() {
alert(`Bye ${this.name}`);
}
};
// 用法:
class User {
constructor(name) {
this.name = name;
}
}
// 拷贝方法
Object.assign(User.prototype, sayHiMixin);
// 现在 User 可以打招呼了
new User("Dude").sayHi(); // Hello Dude!
方式就是构造一个拥有实用方法的对象,以便我们可以轻松地将这些实用的方法合并到任何类的原型中。
// mixin
let sayHiMixin = {
sayHi() {
alert(`Hello ${this.name}`);
},
sayBye() {
alert(`Bye ${this.name}`);
}
};
// 用法:
class User {
constructor(name) {
this.name = name;
}
}
// 拷贝方法
Object.assign(User.prototype, sayHiMixin);
// 现在 User 可以打招呼了
new User("Dude").sayHi(); // Hello Dude!