前言
我一定是一個傻子,昨天這篇文章其實我已經寫好了一半了,但是我沒有保存
這是學習ES6的過程,我沒有系統的看完阮大大的書。零零散散的,很多功能知道,但是沒有實際的用過
看了幾遍,總是看前面幾章,所以這次我要立下flag 一定從頭到尾學一遍ES6(有點諷刺 現在好像都有ES9了)
ES5與ES6 相差還是很大的
一、類
ES5 沒有類這個說法,但是是可以實現類這樣功能的,那就是構造函數
function Point (x,y){ this.x = x this.y = y } var a = new Point(1,2) console.log(a.x) //1
然後我在mdn上找到一個例子 詳細的說了構造函數的原型 原型鏈
// 讓我們從一個自身擁有屬性a和b的函數裏創建一個對象o: let f = function () { this.a = 1; this.b = 2; } /* 這麼寫也一樣 function f() { this.a = 1; this.b = 2; } */ let o = new f(); // {a: 1, b: 2} // 在f函數的原型上定義屬性 f.prototype.b = 3; f.prototype.c = 4; // 不要在 f 函數的原型上直接定義 f.prototype = {b:3,c:4};這樣會直接打破原型鏈 // o.[[Prototype]] 有屬性 b 和 c // (其實就是 o.__proto__ 或者 o.constructor.prototype) // o.[[Prototype]].[[Prototype]] 是 Object.prototype. // 最後o.[[Prototype]].[[Prototype]].[[Prototype]]是null // 這就是原型鏈的末尾,即 null, // 根據定義,null 就是沒有 [[Prototype]]。 // 綜上,整個原型鏈如下: // {a:1, b:2} ---> {b:3, c:4} ---> Object.prototype---> null console.log(o.a); // 1 // a是o的自身屬性嗎?是的,該屬性的值爲 1 console.log(o.b); // 2 // b是o的自身屬性嗎?是的,該屬性的值爲 2 // 原型上也有一個'b'屬性,但是它不會被訪問到。 // 這種情況被稱爲"屬性遮蔽 (property shadowing)" console.log(o.c); // 4 // c是o的自身屬性嗎?不是,那看看它的原型上有沒有 // c是o.[[Prototype]]的屬性嗎?是的,該屬性的值爲 4 console.log(o.d); // undefined // d 是 o 的自身屬性嗎?不是,那看看它的原型上有沒有 // d 是 o.[[Prototype]] 的屬性嗎?不是,那看看它的原型上有沒有 // o.[[Prototype]].[[Prototype]] 爲 null,停止搜索 // 找不到 d 屬性,返回 undefined
看完這個例子,就大概知道原型 原型鏈 是怎樣一層一層的找的(又有靈感 寫一篇古風版的原型鏈了)
然後ES6 是怎麼樣的呢
class Point{ constructor(x,y){ this.x = x; this.y = y; } toString(){ return "("+this.x+","+this.y+")" } }
上面代碼定義了一個“類”,可以看到裏面有一個constructor
方法,這就是構造方法
而this
關鍵字則代表實例對象。也就是說,ES5 的構造函數Point
,對應 ES6 的Point
類的構造方法。
使用的時候,也是直接對類使用new
命令,跟構造函數的用法完全一致。
class Bar { doStuff() { console.log('stuff'); } } var b = new Bar(); b.doStuff() // "stuff"
類的所有方法都定義在類的prototype
屬性上面。(繼承) 在類的實例上面調用方法,其實就是調用原型上的方法。
class B {} let b = new B(); b.constructor === B.prototype.constructor // true
prototype
對象的constructor
屬性,直接指向“類”的本身,這與 ES5 的行爲是一致的。
Point.prototype.constructor === Point // true
toString
方法是Point
類內部定義的方法,它是不可枚舉的。這一點與 ES5 的行爲不一致。
constructor
方法
constructor
方法是類的默認方法,通過new
命令生成對象實例時,自動調用該方法。一個類必須有constructor
方法,如果沒有顯式定義,一個空的constructor
方法會被默認添加。
class Point { } // 等同於 class Point { constructor() {} }
- 類必須使用
new
調用,否則會報錯。這是它跟普通構造函數的一個主要區別,後者不用new
也可以執行 constructor
函數返回一個全新的對象,結果導致實例對象不是Foo
類的實例。-
class Foo { constructor() { return Object.create(null); } } new Foo() instanceof Foo // false
- 類不存在變量提升(hoist),這一點與 ES5 完全不同。
-
{ let Foo = class {}; class Bar extends Foo { } }
上面的代碼不會報錯,因爲
Bar
繼承Foo
的時候,Foo
已經有定義了。但是,如果存在class
的提升,上面代碼就會報錯,因爲class
會被提升到代碼頭部,而let
命令是不提升的,所以導致Bar
繼承Foo
的時候,Foo
還沒有定義。 - 由於本質上,ES6 的類只是 ES5 的構造函數的一層包裝,所以函數的許多特性都被
Class
繼承,包括name
屬性name
屬性總是返回緊跟在class
關鍵字後面的類名。class Point {}
Point.name // "Point"
- 如果某個方法之前加上星號(
*
),就表示該方法是一個 Generator 函數。 -
class Foo { constructor(...args) { this.args = args; } * [Symbol.iterator]() { for (let arg of this.args) { yield arg; } } } for (let x of new Foo('hello', 'world')) { console.log(x); } // hello // world
上面代碼中,
Foo
類的Symbol.iterator
方法前有一個星號,表示該方法是一個 Generator 函數。Symbol.iterator
方法返回一個Foo
類的默認遍歷器,for...of
循環會自動調用這個遍歷器。 - 在構造方法中綁定
this
,這樣就不會找不到print
方法了。 不然就會this
會指向該方法運行時所在的環境(由於 class 內部是嚴格模式,所以 this 實際指向的是undefined
),從而導致找不到print
方法而報錯。 - 另一種解決方法是使用箭頭函數。
-
class Obj { constructor() { this.getThis = () => this; } } const myObj = new Obj(); myObj.getThis() === myObj // true
二、靜態方法
類相當於實例的原型,所有在類中定義的方法,都會被實例繼承。如果在一個方法前,加上static
關鍵字,就表示該方法不會被實例繼承,而是直接通過類來調用,這就稱爲“靜態方法”。
class Foo { static classMethod() { return 'hello'; } } Foo.classMethod() // 'hello' var foo = new Foo(); foo.classMethod() // TypeError: foo.classMethod is not a function