本篇為 [JS201] 進階 JavaScript:那些你一直搞不懂的地方筆記之五。
一樣先提心得:一開始完全看不懂 js 的原型鏈,經過不斷地調整讀書計畫,努力的看懂影片跟文章,慢慢的進入狀況了。影片跟文章內容應該是大同小異,但不知道為什麼,最近都是先理解文章後才看懂影片的。
我把我的思考過程留下來,希望往後學理論相關的東西,可以有一個借鏡。這可能是在 Lidemy 學程式的過程中,第一次學理論課,希望我能夠領悟出實作跟理論分別該怎麼學。
重點整理:
a. __proto__
是常見用法,但用正式用法比較好:Object.getPrototypeOf()
參考資料:Object.prototype.__proto__
b. 在 js 裡面,幾乎每個東西都是物件
c. 每個物件裡面都有 __proto__
屬性,來標示自己所繼承的原型
d. 寫好一個物件以後,通常透過外部來操控內部的方法們,而不會在外面寫一個函式,直接改變物件裡面的值。
e. 當 new 一個 instance 時,instance 放的參數,可以在 constructor 裡面收到。
f. 利用 ES5 語法實作 prototype,會更容易理解 ES6 的 class 原理
g. __proto__
其實就是「指向上一層的 prototype」
h. 了解 new
背後做的事
i. 了解繼承的概念
j. hasOwnProperty
可以知道屬性存在哪裡
k. A instanceof B
判斷 A 是不是 B 的 instance
物件導向基礎與 prototype
一、什麼是物件導向?
把物件包在函式裡面,可以隱藏、保護資訊,並且更容易模組化,更有規範;更容易對物件作操作
二、物件導向的基礎範例
1. ES6 才有的 class 語法可以用,來看一下最基本的類別雛形:
a. 名稱一定要大寫開頭
b. 就像一個設計圖,去定義裡面的內容
c. 裡面的內容就代表 class 的 method:
sayHello() 前面不用加 function,加了會出現錯誤 SyntaxError: Unexpected identifier
,所以每個 method 的名稱都是一個 identifier(?)
d. 要用 new
從設計圖把 d 實體化(做一個 instance)
e. 再用 d 用操作物件
// 裡面放函式
class Dog{
sayHello() {
console.log('hello')
}
}
var d = new Dog()
d.sayHello()
2. this 在物件導向裡面是誰?
a. 誰呼叫 this,誰就是 this;在這個例子裡,this 就是 d
b. 常見模式會有一個 setter、一個 getter: 目的是為了在「不要直接改變物件內容的情況下做操作」
class Dog {
// setter
setName(name) {
this.name = name
}
// getter
getName() {
return this.name
}
}
var d = new Dog()
d.setName('rookie')
console.log(d.getName()) // rookie
3. 如何接收 instance 裡面的參數?
class 裡面的 constructor 可以接收 instance 裡面的參數;個人理解,constructor 很像是一個存東西的倉庫,下面可以接執行其他行為的 method,接著用 instance + method 去操控物件
class Dog {
constructor (name) {
this.name = name
}
printName() {
console.log(this.name)
}
}
var d = new Dog('rookie')
d.printName() // rookie
var b = new Dog('lucky')
b.printName() // lucky
4. 物件導向最基本的概念:
有一個設計圖 -> new 一個 instance -> 用 d.methood() 的方法操作物件
三、ES5 的 class
在 ES6 以前,是沒有 class 這個語法糖的,那我們該怎麼實作呢?
下面的缺點是,無法共用同樣的資源
function Dog(name) {
var myName = name
return {
getName: function() {
return myName
},
sayHello: function() {
console.log(myName)
}
}
}
var d = Dog('abc')
d.sayHello()
var e = Dog('123')
e.sayHello()
console.log(d.sayHello === e.sayHello) // false
在 ES5 中,可以把一個函式當作 constructor 用,來看看用 prototype 改寫後:
// constructor
function Dog(name) {
this.name = name
}
Dog.prototype.getName = function() {
return this.name
}
Dog.prototype.sayHello = function() {
console.log(this.name)
}
var d = new Dog('abc')
d.sayHello()
var b = new Dog('123')
b.sayHello()
console.log(d.sayHello === b.sayHello) // true
見識到了 prototype 的威力了,接下來要來解析它
四、從 prototype 來看「原型鍊」
到底 __proto__
是什麼?
其實就是「指向上一層的 prototype」
下面範例層級由頂到底,概念是這樣:
Object -> Dog -> d
所以 d.__proto__ === Dog.prototype
會是 true
而 Dog.prototype.__proto__ === Object.prototype
也是 true
function Dog(name) {
this.name = name
}
Dog.prototype.sayHello = function() {
console.log('dog', this.name)
}
Object.prototype.sayHello = function() {
console.log('object', this.name)
}
var d = new Dog('abc')
d.sayHello()
console.log(d.__proto__ === Dog.prototype) // true
console.log(Dog.prototype.__proto__ === Object.prototype) // true
我覺得這裡難的是 __proto__
跟 prototype
之間的關係
後記:我隔了兩天又重看了一遍,其實難的不是上面那個關係,而是我在心裡沒有一個具象化的圖來表示。
因此,我把各自的 .__proto__
及 .prototype
印出來看,並畫了一個圖,好像又更懂了一點點。
五、所以,new 到底做了什麼事?
1. 預備知識:當用 .call()
呼叫函式時,第一個引數傳什麼,this 就會是什麼
function test() {
console.log(this)
}
test.call('123') // [String: '123']
2. this 的原理:this 其實就是 new 完以後產生的那一大個物件啦
模仿 new 後面的行為
a. 產生一個新的 obj
b. 去呼叫 construtor,初始化物件
c. 利用 __proto__
方式,把 obj 連接到上層的 prototype
d. 回傳設定好的 obj
// construtor
function Dog(name) {
this.name = name
}
Dog.prototype.sayHello = function() {
return this.name
}
Dog.prototype.sayHello = function() {
console.log(this.name)
}
var d = newDog('abc')
d.sayHello()
// 模仿 new 背後做的事
function newDog(name) {
var obj = {}
Dog.call(obj, name)
obj.__proto__ = Dog.prototype
return obj
}
六、物件導向的繼承:Inheritance
以下為 ES6 以後的語法:
a. extends 可以繼承一個 class 的東西
b. BlackDog 會繼承 Dog,因此可以用 test()
也可以用 sayHello()
class Dog {
constructor(name) {
this.name = name
}
sayHello() {
console.log(this.name)
}
}
class BlackDog extends Dog {
test() {
console.log('test!', this.name)
}
}
const d = new BlackDog('hello')
d.test() // test! hello
d.sayHello() // hello
c. 當黑狗被建立時,就跟我們 sayHello,寫一個 constructor 吧
d. 當黑狗裡面有 constructor 時,要先 call super()
並把參數傳進去,初始化以後,再呼叫想要的 method 才會有正確的值
呼叫 super() 其實就是在呼叫上一層的 constructor
class Dog {
constructor(name) {
this.name = name
}
sayHello() {
console.log(this.name)
}
}
class BlackDog extends Dog {
constructor(name) {
super(name) // 等於在呼叫 Dog.constructor
this.sayHello()
}
test() {
console.log('test!', this.name)
}
}
const d = new BlackDog('hello')
小結:
繼承某個東西以後,記得用 super()
初始化設定
用繼承的好處就是類似把大綱先寫好,其他有小變化再自己改
本篇為 該來理解 JavaScript 的原型鍊了 閱讀筆記
prototype 先到這裡為止,下面兩篇更深的文章留到以後慢慢懂