Photo by Bruno Kelzer on Unsplash
前言
如果有個JS觀念對新人不友善排行榜,this 絕對榜上有名! 對於其他的OOP程式語言(ex:JAVA),this 就很明確是指向到該實體,但JS卻不完全是這樣,就會造成很多的黑人問號 XD
而這篇筆記是上面兩篇文章的學習紀錄,研究之後以自己的方式記錄下來,會比較精簡,適合已經學會之後用來複習。建議可以先閱讀完上面的篇章,再來看這篇文章。
主題
What's this?
對於其他物件導向語言來說,this 指的是當前物件的實體
而在 js 中即使脫離物件,this也可以使用,但沒什麼意義,會指向預設值
- 嚴格模式底下就都是undefined
- 非嚴格模式,瀏覽器底下是window
- 非嚴格模式,node.js 底下是global
綁定this 的方式
- THAT / SELF
- call, apply, bind
- arrow function
已經 bind 過的this, 就不會再被變動 (e.g. apply, 跟 bind 一起使用)
'use strict';
function hello() {
console.log(this)
}
const myHello = hello.bind('my')
myHello.call('call') // my
this 的值
this 的值跟作用域跟程式碼的位置在哪裡完全無關,只跟 如何呼叫 有關
可以使用替換成call的方式,得知當前的this是誰
(適用於大部分的狀況,但仍有例外!)
function hello() {
console.log(this)
}
var a = { value: 1, hello }
var b = { value: 2, hello }
hello()
a.hello()
b.hello.apply(a)
hello() // hello.call() => undefinded or window
a.hello() // a.hello.call(a) => a
b.hello.apply(a) => a
var x = 10
var obj = {
x: 20,
fn: function() {
var test = function() {
console.log(this.x)
}
test()
}
}
obj.fn()
test() -> test.call() -> 全域 -> 10
Arrow Function
本身沒有 this
在宣告它的地方的 this 是什麼,它的 this 就是什麼
const obj = {
x: 1,
hello: function(){
// 這個 this 看 function hello 被呼叫的時候
console.log(this)
// 這裡是宣告test的地方, 就是上一行的 this
const test = () => {
console.log(this.x)
}
test()
}
}
obj.hello() // obj, 1
const hello = obj.hello
hello() // window, undefined
原型鏈 prototype
什麼是原型鏈
"講到繼承,JavaScript 就只有一個建構子:物件。每個物件都有一個連著其他原型(prototype)的私有屬性(private property)物件。原型物件也有著自己的原型,於是原型物件就這樣鏈結,直到撞見 null 為止:null 在定義裡沒有原型、也是原型鏈(prototype chain)的最後一個鏈結。" - MDN-繼承與原型鏈
原型鏈可以說是JS實作"繼承"的一種方式,環環相扣,最終找到位於最頂端的 Object 物件
很多相關的名詞: prototype, proto, constructor, Object.prototype, Function.prototype, new
注: __proto__ 已被標記廢棄,要改用 Object.getPrototypeOf
MDN Object.getPrototypeOf()
Class 與 建構子
// constructor
function Person(name, age) {
this.name = name;
this.age = age;
this.log = function () {
console.log(this.name + ', age:' + this.age);
}
}
var nick = new Person('nick', 18);
var peter = new Person('peter', 18);
借鏡其他語言,使用 new 關鍵字產生一個實體,接著執行 Person (constructor)
nick & peter 都有相同的方法 log, 但實際上仍然是兩個funciton (占用記憶體)
真正共通的方式就是綁到 prototype 上,讓所有產生的實體都能使用
Person.prototype.log = function () {
console.log(this.name + ', age:' + this.age);
}
順序與尋找規則
Person.prototype.log = function () {
console.log(this.name + ', age:' + this.age);
}
var nick = new Person('nick', 18);
console.log(nick.__proto__ === Person.prototype) // true
會往上沿著 proto 向上尋找
- nick 底下沒有 log
- nick.proto -> Person.prototype 有 log
- 如果 2. 沒找到會在往上,直到某個 proto 為 null
nick.proto -> Person.prototype.proto -> Object.prototype.proto -> null
(最上層就是 Object.prototype)
Constructor
每個 prototype 裡有 constructor 屬性
A.prototype.constructor === A
person.prototype.constructor -> person function
console.log(nick.constructor === Person); // true
console.log(Person.prototype.constructor === Person); // true
New
- 創出一個新的 object,我們叫它 O
- 把 O 的 proto 指向 Person 的 prototype,才能繼承原型鍊
- 拿 O 當作 context,呼叫 Person 這個建構函式
- 回傳 O
function newObj(Constructor, arguments) {
var o = new Object();
// 讓 o 繼承原型鍊
o.__proto__ = Constructor.prototype;
// 執行建構函式
Constructor.apply(o, arguments);
// 回傳建立好的物件
return o;
}
結尾
因為研究ES6的class,發現自己有些基礎知識不夠紮實,回頭來看就產生了這篇筆記。補完 this 跟 原型鏈的知識之後,總算對js的繼承模型有一點認知了。很有趣的,有派說法是這樣的模式是缺陷;另派說法是比起傳統繼承而有優勢。
Master the JavaScript Interview: What’s the Difference Between Class & Prototypal Inheritance?
宗教戰爭就交給大大們吧,平凡工程師就是各種都學 XD