Eloquent JavaScript #06# class
索引
Notes
1、this
① 隐式传入this参数
function speak(line) {
console.log(`The ${this.type} rabbit says '${line}'`);
}
let whiteRabbit = {type: "white", speak};
let hungryRabbit = {type: "hungry", speak};
② 通过call显式传入this参数
speak.call(hungryRabbit, "Burp!");
③ 对于箭头函数而言this指的是外围函数的this
function normalize() {
console.log(this.coords.map(n => n / this.length));
}
normalize.call({coords: [0, 2, 3], length: 5});
// → [0, 0.4, 0.6]
用function关键词定义该函数然后传入,或者把箭头函数独立定义在外面,上面的代码都不会正常运行:
const f = n => n / this.length;
function normalize() {
console.log(this.coords.map(f));
}
normalize.call({coords: [0, 2, 3], length: 5});
// → [NaN, Infinity, Infinity]
2、原型Prototype
对象接到调用后首先搜索自身的属性,找不到就在原型中查找。
js中的原型构成一个三角关系,最根部是Object.prototype,而Function.prototype和Array.prototype则是两个分支。
console.log(Object.getPrototypeOf({}) ==
Object.prototype);
// → true
console.log(Object.getPrototypeOf(Object.prototype));
// → null
console.log(Object.getPrototypeOf(Math.max) ==
Function.prototype);
// → true
console.log(Object.getPrototypeOf([]) ==
Array.prototype);
// → true
可以用Object.create创捷具有特定原型的对象:
let protoRabbit = {
speak(line) {
console.log(`The ${this.type} rabbit says '${line}'`);
}
};
let killerRabbit = Object.create(protoRabbit);
killerRabbit.type = "killer";
killerRabbit.speak("SKREEEE!");
// → The killer rabbit says 'SKREEEE!'
在上面的例子中,speak方法是所有兔子共有的,而type属性则是杀手兔子私有的。
3、类
在java中兔子类的构造方法应该是这样的:
public class Rabbit {
public String type;
public void speak() {}
// 构造器
public Rabbit(String type) {
this.type = type;
}
}
然而在js里却要这样实现:
function makeRabbit(type) {
let rabbit = Object.create(protoRabbit);
rabbit.type = type;
return rabbit;
}
正常的兔子都应该通过该函数实例化,这样才可以保证兔子们共享speak,又具备自己独立的type。
js提供了一种方式让定义这类函数更加简单:
function Rabbit(type) {
this.type = type;
}
Rabbit.prototype.speak = function(line) {
console.log(`The ${this.type} rabbit says '${line}'`);
};
let weirdRabbit = new Rabbit("weird");
事实上,js中的所有函数都具有一个prototype属性,它是一个派生自Object.prototyp的空对象。你可以用其它对象覆盖它,或者像上面那样改造它。构造函数、构造函数的prototype属性指向的对象、通过构造函数创建的对象之间的关系如下:

PS. 独家制作↑
4、class符号(since 2015)
类实质上就是一个函数,而函数其实也是个对象。
class Rabbit {
constructor(type) {
this.type = type;
}
speak(line) {
console.log(`The ${this.type} rabbit says '${line}'`);
}
}
let killerRabbit = new Rabbit("killer");
let blackRabbit = new Rabbit("black");
相对于3只是换了一种书写方式,且在class定义中只允许添加函数到原型。
匿名类:
let obj = new class {
constructor(word) {
this.word = word;
}
speak() {
console.log('speak.');
}
} ("hello");
obj.word;
// → "hello"
obj.speak();
// → speak.
5、覆盖派生属性
类似java,接收到调用之后,js总是搜索当前对象,找不到才会逐个地搜索上一级原型对象,因此,如果子对象和父对象有同名的属性,那么父对象属性理所当然要被子对象的属性“覆盖”掉。
6、Maps
如果用对象实现map:
let obj = {
a: "abc",
b: "bca",
c: "cab"
};
然而它存在一些问题:
let obj = {
a: "abc",
b: "bca",
c: "cab"
};
console.log("a" in obj);
// -> true
console.log("toString" in obj);
// -> true
原型对象的属性也会被作为键。针对这个问题,有两个比较容易的解决方案——创建没原型的对象:
let obj = Object.create(null);
obj.a = "abc"; console.log("a" in obj);
// -> true
console.log("toString" in obj);
// -> false
或者用Object.keys或者hasOwnProperty代替in,它们都不会把原型属性包含在范畴内。
不过还存在一个问题,对象的属性只能是字符串,这就意味着没有办法用对象作为键,因此最佳的方案还是采用js内建的Map类:
let myMap = new Map();
let obj1 = {};
let obj2 = {};
let obj3 = null;
myMap.set(obj1, "a");
myMap.set(obj2, "b");
myMap.set(obj3, "c");
console.log(myMap.get(obj1));
// → a
console.log(myMap.has(obj2));
// → true
console.log(myMap.get(obj3));
// → c
console.log(myMap.get(null));
// → c
7、Symbols
参考文章:js-ES6学习笔记-Symbol
let sym = Symbol("name");
console.log(sym == Symbol("name"));
// → false
Rabbit.prototype[sym] = 55;
console.log(blackRabbit[sym]);
// → 55
/ 示例2
const toStringSymbol = Symbol("toString");
Array.prototype[toStringSymbol] = function() {
return `${this.length} cm of blue yarn`;
};
console.log([1, 2].toString());
// → 1,2
console.log([1, 2][toStringSymbol]());
// → 2 cm of blue yarn
/ 示例3
let stringObject = {
[toStringSymbol]() { return "a jute rope"; }
};
console.log(stringObject[toStringSymbol]());
// → a jute rope
/ 示例4
let sy1 = Symbol("just a description");
let sy2 = Symbol("just a description");
console.log(sy1 == sy2);
// → false
let obj = {
sy1: "a",
[sy1]: "b",
[sy2]: "c",
sy3: Symbol("just a description")
};
console.log(obj.sy1);
// → a
console.log(obj["sy1"]);
// → a
console.log(obj[sy1]);
// → b
console.log(Object.keys(obj));
// → ["sy1", "sy3"]
在你仅仅只是需要一个独一无二的符号而不论它是什么的时候可以用它。
优点:降低代码(由于语义产生的)耦合性。
注意点:symbol作为属性,必须像上面那样用方括号包含对symbol的绑定进行声明(方括号会导致计算),并且只能用方括号+对symbol的绑定访问那个属性。
8、iterator接口
所有可迭代(可以通过for/of遍历)对象其实都包含一个由Symbol.iterator定义的方法。该方法返回一个实现了next方法的(iterator)对象。
可以直接调用这个方法拿到这种对象:
let okIterator = "OK"[Symbol.iterator]();
console.log(okIterator.next());
// → {value: "O", done: false}
console.log(okIterator.next());
// → {value: "K", done: false}
console.log(okIterator.next());
// → {value: undefined, done: true}
自定义实现该接口的矩阵对象(课本实例):
class Matrix {
constructor(width, height, element = (x, y) => undefined) {
this.width = width;
this.height = height;
this.content = [];
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
this.content[y * width + x] = element(x, y);
}
}
}
get(x, y) {
return this.content[y * this.width + x];
}
set(x, y, value) {
this.content[y * this.width + x] = value;
}
}
class MatrixIterator {
constructor(matrix) {
this.x = 0;
this.y = 0;
this.matrix = matrix;
}
next() {
if (this.y == this.matrix.height) return {done: true};
let value = {x: this.x,
y: this.y,
value: this.matrix.get(this.x, this.y)};
this.x++;
if (this.x == this.matrix.width) {
this.x = 0;
this.y++;
}
return {value, done: false};
}
}
Matrix.prototype[Symbol.iterator] = function() {
return new MatrixIterator(this);
};
let matrix = new Matrix(2, 2, (x, y) => `value ${x},${y}`);
for (let {x, y, value} of matrix) {
console.log(x, y, value);
}
// → 0 0 value 0,0
// → 1 0 value 1,0
// → 0 1 value 0,1
// → 1 1 value 1,1
9、Getters, setters, and statics
class Temperature {
constructor(celsius) {
this.celsius = celsius;
}
get fahrenheit() {
return this.celsius * 1.8 + 32;
}
set fahrenheit(value) {
this.celsius = (value - 32) / 1.8;
}
static fromFahrenheit(value) {
return new Temperature((value - 32) / 1.8);
}
}
let temp = new Temperature(22);
console.log(temp.fahrenheit);
// → 71.6
temp.fahrenheit = 86;
console.log(temp.celsius);
// → 30
Temperature.fromFahrenheit(100)
10、继承
class SymmetricMatrix extends Matrix {
constructor(size, element = (x, y) => undefined) {
super(size, size, (x, y) => {
if (x < y) return element(y, x);
else return element(x, y);
});
}
set(x, y, value) {
super.set(x, y, value);
if (x != y) {
super.set(y, x, value);
}
}
}
let matrix = new SymmetricMatrix(5, (x, y) => `${x},${y}`);
console.log(matrix.get(2, 3));
// → 3,2
封装和多态可以将增强代码的独立性,而继承却强化了类之间的关系,制造更多的纠纷。当采用继承的时候,你必须知道它是如何工作的,而不仅仅简单的去用它。继承是有用的工具,但不应当作为解决问题的首选。
11、instanceof
function X() {
}
let obj = new X();
console.log(obj instanceof Object);
// → true
console.log(obj instanceof X);
// → true
Exercises
① A vector type
class Vec {
constructor(x, y) {
this.x = x;
this.y = y;
}
plus(v) {
return new Vec(v.x + this.x, v.y + this.y);
}
minus(v) {
return new Vec(this.x - v.x, this.y - v.y);
}
get length() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
}
console.log(new Vec(1, 2).plus(new Vec(2, 3)));
// → Vec{x: 3, y: 5}
console.log(new Vec(1, 2).minus(new Vec(2, 3)));
// → Vec{x: -1, y: -1}
console.log(new Vec(3, 4).length);
// → 5
- -- - - - -- - - - - -- - - - - -- - - - -- - - - - -- - - - -- - - - - -- - - - -- - - -
② Groups
class Group {
// Your code here.
constructor() {
this.container = [];
}
static from(iterable) {
let group = new Group();
for (let x of iterable) {
group.add(x);
}
return group;
}
add(x) {
if (!this.container.includes(x)) {
this.container.push(x);
}
}
delete(x) {
this.container = this.container.filter(a => !(a === x));
}
has(x) {
return this.container.includes(x);
}
}
let group = Group.from([10, 20]);
console.log(group.has(10));
// → true
console.log(group.has(30));
// → false
group.add(10);
group.delete(10);
console.log(group.has(10));
// → false
- -- - - - -- - - - - -- - - - - -- - - - -- - - - - -- - - - -- - - - - -- - - - -- - - -
③ Iterable groups
实践证明,class和function一样可以无视在文本中定义的位置。
class Group {
constructor() {
this.container = [];
}
static from(iterable) {
let group = new Group();
for (let x of iterable) {
group.add(x);
}
return group;
}
add(x) {
if (!this.container.includes(x)) {
this.container.push(x);
}
}
delete(x) {
this.container = this.container.filter(a => !(a === x));
}
has(x) {
return this.container.includes(x);
}
[Symbol.iterator]() {
return new GroupIterator(this);
}
}
class GroupIterator {
constructor(x) {
this.index = 0;
this.container = x.container;
}
next() {
if (this.index === this.container.length) return {done: true};
let value = this.container[this.index++];
return {value, done: false};
}
}
for (let value of Group.from(["a", "b", "c"])) {
console.log(value);
}
// → a
// → b
// → c
- -- - - - -- - - - - -- - - - - -- - - - -- - - - - -- - - - -- - - - - -- - - - -- - - -
④ Borrowing a method
let map = {one: true, two: true, hasOwnProperty: true};
// Fix this call
console.log(Object.hasOwnProperty.call(map, "one"););
// → true
Eloquent JavaScript #06# class的更多相关文章
- Eloquent JavaScript #13# HTTP and Forms
索引 Notes fetch form focus Disabled fields form’s elements property 阻止提交 快速插入单词 实时统计字数 监听checkbox和rad ...
- Eloquent JavaScript #11# The Document Object Model
索引 Notes js与html DOM 在DOM树中移动 在DOM中寻找元素 改变Document 创建节点 html元素属性 布局 style CSS选择器 动画 Exercises Build ...
- Eloquent JavaScript #10# Modules
索引 Notes 背景问题 模块Modules 软件包Packages 简易模块 Evaluating data as code CommonJS modules ECMAScript modules ...
- Eloquent JavaScript #04# Objects and Arrays
要点索引: JSON More ... 练习 1.补:js字符串的表达方式有三种: "" 和 '' 没什么区别,唯一区别在于 "" 中写 "要转义字符 ...
- Eloquent JavaScript #03# functions
索引: let VS. var 定义函数的几种方式 more... 1.作者反复用的side effect side effect就是对世界造成的改变,例如说打印某些东西到屏幕,或者以某种方式改变机器 ...
- Eloquent JavaScript #02# program_structure
第一章中作者介绍了各种值,但是这些独立的值是没有意义的,只有当值放在更大的框架的时候才会彰显它们的价值.所以第二章开始介绍程序结构. 1.var VS. let 以及 const 作者推荐用 let ...
- Eloquent JavaScript #01# values
When action grows unprofitable, gather information; when information grows unprofitable, sleep. ...
- Eloquent JavaScript #12# Handling Events
索引 Notes onclick removeEventListener Event objects stopPropagation event.target Default actions Key ...
- Eloquent JavaScript #09# Regular Expressions
索引 Notes js创建正则表达式的两种方式 js正则匹配方式(1) 字符集合 重复匹配 分组(子表达式) js正则匹配方式(2) The Date class 匹配整个字符串 Choice pat ...
随机推荐
- (转)Thread中yield方法
先上一段代码 public class YieldExcemple { public static void main(String[] args) { Thread threada = new Th ...
- jmeter SMTP Sampler取样器发送测试结果邮件
原理: 先用结果类监听器(用表格察看结果.聚合报告)将测试结果以csv文件保存到本地. 然后再用SMTP Sampler取样器把本地的测试结果文件发送到指定邮箱 具体步骤如下: 1.下载javamai ...
- cocos2dx C++为Sprite添加触摸事件监听器
1.首先头文件定义事件处理的函数原型 private: bool onTouchBegan(Touch* tTouch,Event* eEvent);//手指按下事件 void onTouchMove ...
- opencv之模糊处理
初学OpenCV的开发者很容易被OpenCV中各种滤波方法所困扰,不知道到底该用哪里一个来做滤波.表面原因看起来是因为OpenCV中各种滤波方式实在是太多太杂, 其背后原因是对各种滤波方法的应用场景认 ...
- webpack打包二进制文件报错
错误示例,如下图所示: 修改webpack的module部分的rules,在其中添加一下代码: { test: /\.woff[0-9]{0,}$/, loader: "url-loader ...
- sql语句,加引号和不加引号的区别
今天碰到个问题,查询数据的时候,显示表不存在,在可视化窗口确实能看见.试着给表名加个引号,发现能成功查询数据了.上网查询原因如下: 1.oracle表和字段是有大小写的区别.oracle默认是大写,如 ...
- python对缩进的严格要求
一般的编辑器都会默认Tab键为四格缩进,但是在python脚本中,Tab与直接4个tablespace空格是有区别的. 一般表现为报错信息如下: IndentationError: unindent ...
- armv8 memory system
在armv8中,由于processor的预取,流水线, 以及多线程并行的执行方式,而且armv8-a中,使用的是一种weakly-ordered memory model, 不保证program or ...
- Yii2 Restful api创建
- 多线程实现Thread.Start()与ThreadPool.QueueUserWorkItem两种方式对比
Thread.Start(),ThreadPool.QueueUserWorkItem都是在实现多线程并行编程时常用的方法.两种方式有何异同点,而又该如何取舍? 写一个Demo,分别用两种方式实现.观 ...