Eloquent JavaScript - The Secret Life of Object

207 阅读2分钟

Prototypes

You can use Object.create() to create an object with a specific prototype.

let protoRabbit = {
    speak(line) {
        console.log(`The ${this.type} rabbit says '${line}'`);
    },
};

console.log(typeof protoRabbit);            // -> object

let killerRabbit = Object.create(protoRabbit);
killerRabbit.type = 'killer';
console.log(Object.keys(killerRabbit));     // -> ["type"]
console.log(Object.getPrototypeOf(killerRabbit)); 
console.log(protoRabbit.isPrototypeOf(killerRabbit));   // -> true
killerRabbit.speak('SKREEEE!');
  1. The "proto" rabbit acts as a container for the properties that are shared by all rabbits.
  2. An individual rabbit object, like the killer rabbit, contains properties that apply only to itself and derives shared properties from its prototype.

Object.keys()输出的properties不包含对象从原型那里继承来的,所以console.log(Object.keys(killerRabbit))输出的结果是["type"]
另外,上面涉及到Object.create()这个知识点。

The Object.create() method creates a new object, using an existing object as the prototype of the newly created object.

  • Object.create()的参数是一个object
  • 新创建的对象的prototype是Object.create()的参数 所以,上面代码里面,console.log(Object.getPrototypeOf(killerRabbit))输出的答案是:

1617261361479.jpg 也就是说,killerRabbit的prototype是protoRabbit这个object。

Classes

So JavaScript classes are constructor functions with a prototype property. 这个定义很重要。
2015年之前,class的写法就是直接声明一个constructor function,大概像下面这样子:

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

Rabbit.prototype.speak = function (line) {
    console.log(`The ${this.type} rabbit says '${line}'`);
};

let weirdRabbit = new Rabbit('weird');

2015年之后,可以用Class Notation来写:

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"); 

The Iterator Interface

The object given to a for / of loop is expected to be iterable. 具体实例:搭建一个matrix类别

class Matrix {
    // 初始化,创建一个matrix[width][height]
    constructor(width, height, element = (x, y) => undefined) {
        this.width = width;
        this.height = height;
        this.content = []; // single array representation of a two-dimensional array

        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;
    }
}

第二步:创建一个MatrixIterator class

class MatrixIterator {
    constructor(matrix) {
        this.x = 0;
        this.y = 0;
        this.matrix = matrix;
    }
    // 注意next()的写法
    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方法

Matrix.prototype[Symbol.iterator] = function () {
    return new MatrixIterator(this); // 返回的是一个MatrixIterator的object
};

第四步:创建一个Matrix类对象

let matrix = new Matrix(2, 2, (x, y) => `value ${x}, ${y}`); 
console.log('constructor' in matrix);      // -> true
console.log('get' in matrix);              // -> true
console.log('set' in matrix);              // -> true
console.log(Symbol.iterator in matrix);    // -> true

可以看到,上面代码里面后面四行代码的输出都是true,也就是说matrix这个对象从它的原型那里继承了constructor\get\set\Symbol.iterator。另外,

console.log(Object.keys(matrix));

上面代码输出的结果是:["width", "height", "content"]

第五步:对matrix对象使用for / of

for (let { x, y, value } of matrix) {
    console.log(x, y, value);
}

因为matrix是iterable,所以for / of就能顺利输出结果:

0 0 "value 0, 0"
1 0 "value 1, 0"
0 1 "value 0, 1"
1 1 "value 1, 1"