JS this / 原型鏈 學習筆記


Posted by m6fish on 2021-06-18

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 向上尋找

  1. nick 底下沒有 log
  2. nick.proto -> Person.prototype 有 log
  3. 如果 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

  1. 創出一個新的 object,我們叫它 O
  2. 把 O 的 proto 指向 Person 的 prototype,才能繼承原型鍊
  3. 拿 O 當作 context,呼叫 Person 這個建構函式
  4. 回傳 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


#javascript







Related Posts

[day-12]AJAX - 網路請求

[day-12]AJAX - 網路請求

[極短篇] JSONP

[極短篇] JSONP

關於 React 小書:使用 JSX 描述 UI

關於 React 小書:使用 JSX 描述 UI


Comments