① 隱式傳入this參數工具
function speak(line) { console.log(`The ${this.type} rabbit says '${line}'`); } let whiteRabbit = {type: "white", speak}; let hungryRabbit = {type: "hungry", speak};
② 經過call顯式傳入this參數post
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關鍵詞定義該函數而後傳入,或者把箭頭函數獨立定義在外面,上面的代碼都不會正常運行:ui
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]
對象接到調用後首先搜索自身的屬性,找不到就在原型中查找。url
js中的原型構成一個三角關係,最根部是Object.prototype,而Function.prototype和Array.prototype則是兩個分支。spa
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屬性則是殺手兔子私有的。
在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. 獨家制做↑
類實質上就是一個函數,而函數其實也是個對象。
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.
相似java,接收到調用以後,js老是搜索當前對象,找不到纔會逐個地搜索上一級原型對象,所以,若是子對象和父對象有同名的屬性,那麼父對象屬性理所固然要被子對象的屬性「覆蓋」掉。
若是用對象實現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
參考文章: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的綁定訪問那個屬性。
全部可迭代(能夠經過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
九、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)
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
封裝和多態能夠將加強代碼的獨立性,而繼承卻強化了類之間的關係,製造更多的糾紛。當採用繼承的時候,你必須知道它是如何工做的,而不只僅簡單的去用它。繼承是有用的工具,但不該看成爲解決問題的首選。
function X() { } let obj = new X(); console.log(obj instanceof Object); // → true console.log(obj instanceof X); // → true
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
- -- - - - -- - - - - -- - - - - -- - - - -- - - - - -- - - - -- - - - - -- - - - -- - - -
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
- -- - - - -- - - - - -- - - - - -- - - - -- - - - - -- - - - -- - - - - -- - - - -- - - -
實踐證實,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
- -- - - - -- - - - - -- - - - - -- - - - -- - - - - -- - - - -- - - - - -- - - - -- - - -
let map = {one: true, two: true, hasOwnProperty: true}; // Fix this call console.log(Object.hasOwnProperty.call(map, "one");); // → true