使用lambda在Ruby裡模仿實現JS的物件及原型繼承
ruby沒有對原型模式的支援實在是有些遺憾,雖然Matz在書裡說可以通過object.clone實現原型,但這只是看上去像而已,實際上並不會形成一條原型鏈。
a.attr = 10
b = a.clone
a.attr = 9
b.attr # => 10
clone可以使b有和a一樣的屬性,但b擁有的是屬性的拷貝,即使a的屬性改變了,也不會對b有影響,這不符合原型鏈的定義。按原型鏈繼承的物件,如果沒有某屬性,應當沿著原型鏈向上搜尋,找原型的屬性替代。
要在ruby實現原型鏈我首先想到是使用物件的單件類,但是單件類不允許new也不允許繼承,甚至連方法都不能解綁換綁到別的物件上。接著我考慮藉助ruby的類繼承,因為類繼承和原型繼承是非常相似的,然而很快遇到了問題,js裡原型是可以隨意更換的,而ruby的類在定義的時候就要確定繼承關係,之後無法更改父類,所以這個想法也不可行。
於是考慮自己實現繼承的功能,每個物件設定一個prototype屬性,當獲取屬性時,如果屬性在當前物件不存在,則會去prototype指向的物件內查詢,一層一層遞進,直到prototype為nil。
https://github.com/CicholGricenchos/tricks/blob/master/prototype/using_ruby_object.rb
藉助ruby本身的物件系統,很容易就實現了一個。其中物件的方法是用lambda儲存的,而lambda會在建立時和周圍的上下文繫結在一起,而給物件設定屬性的時候,這些lambda都是在物件外建立的。為了讓這些lambda能操作物件內部,必須重新設定上下文,這裡我使用了instance_eval,把繫結設在例項以內。
不過觀察js物件的函式,只會設定一個this指標,並不會改變上下文,這個ruby版本雖然功能大致實現了,但實現得還不好,不夠geek。接著我想能不能不借助Object,單純用閉包實現一個呢?
https://github.com/CicholGricenchos/tricks/blob/master/prototype/using_pure_lambda.rb
這是最終實現的程式碼,思路是用一個閉包表示一個物件,閉包內有個variables hash儲存物件的屬性,然後暴露一個lambda用於提供get set操作,也就是程式碼裡的handler。
這個handler可以接受兩個引數,屬性名稱和屬性值,如果只提供名稱,則為get操作,返回這個屬性的值,如果二者都提供,則為set,將屬性設為給定的值。
PS:lambda[*arg] 以及 lambda.(*arg)是呼叫的語法糖,等價於lambda.call(*arg)。
實踐的時候很快就遇到了麻煩,還是lambda上下文的問題,在物件外建立的lambda讀取不了物件的variables。我一開始想參照前一版的做法,把lambda的binding改到物件內,但發現不使用instance_eval之類是做不到的。嘗試了諸如重新建立lambda的辦法,都失敗了。
摸索的過程記在了 https://ruby-china.org/topics/23160
後來突然想到,如果不能轉換上下文,不如把需要用到的量通過eval插入到lambda的上下文裡,相當於構造一個上下文,也能達到目的。最直觀的想法就是讓lambda的上下文有個指向物件自身的this,這和js的做法也很相似,之後就可以通過this[:attr]來讀取自身的屬性。
一開始我是用 eval "this = #{物件名稱}", lambda.binding
的形式進行插入的,因為eval只能接收字串程式碼,所以我必須知道物件的名稱才能讓this指向它。但是物件可以隨時在不同名稱的變數中轉移,要記錄變數的名稱很不現實,如果可以把handle傳進lambda的binding就好了。
想了很久,跨作用域傳遞一個量看上去是不可能的,不過有些變數是每個域都可以訪問的,就是全域性變數,通過全域性變數可以讓兩個毫不相關的上下文進行通訊。於是我把handler放到一個全域性變數裡,再eval一個this方法(至於為什麼是方法,可以參照ruby-china那個帖子),指向這個全域性變數,問題就解決了。
後面我把這個流程封裝到pack_lambda裡,這個lambda的作用是在物件方法執行前插入this指標,執行方法,然後再置空this,返回方法的返回值。還順便提供了一個建構函式的機制。
現在物件的方法lambda和js函式的行為是一致的,擁有一個指向物件自身的this指標,所有操作都通過這個this完成,而上下文環境不會改變,同時很好地實現了原型繼承功能,目標達成了。
相關文章
- 繼承的實現方式及原型概述繼承原型
- js 原型鏈實現類的繼承JS原型繼承
- 淺談JS物件的建立、原型、原型鏈繼承JS物件原型繼承
- JS的物件導向(理解物件,原型,原型鏈,繼承,類)JS物件原型繼承
- 物件-原型-繼承物件原型繼承
- 從babel實現es6類的繼承來深入理解js的原型及繼承Babel繼承JS原型
- Javascript 中實現物件原型繼承的三種方式JavaScript物件原型繼承
- 原型,繼承——原型繼承原型繼承
- JS原型繼承和類式繼承JS原型繼承
- JS原型鏈繼承JS原型繼承
- js 使用建構函式和原型鏈實現繼承操作JS函式原型繼承
- JS的原型鏈和繼承JS原型繼承
- js的繼承實現JS繼承
- 物件、原型鏈、類、繼承【上】物件原型繼承
- javascript原型鏈及繼承JavaScript原型繼承
- 原型鏈實現繼承的6種方式原型繼承
- JS中的繼承與原型鏈JS繼承原型
- JS中繼承的實現JS中繼繼承
- javascript原型鏈繼承的使用JavaScript原型繼承
- 小議JS原型鏈、繼承JS原型繼承
- javascript 筆記03(建立物件/原型模式/js 繼承/BOM)JavaScript筆記物件原型模式JS繼承
- JavaScript物件導向—繼承的實現JavaScript物件繼承
- 說清楚javascript物件導向、原型、繼承JavaScript物件原型繼承
- JavaScript物件導向 ~ 原型和繼承(1)JavaScript物件原型繼承
- 徹底弄懂JS原型與繼承JS原型繼承
- Javascript實現物件導向繼承JavaScript物件繼承
- javascript的物件導向的繼承實現JavaScript物件繼承
- js--如何實現繼承?JS繼承
- Javascript繼承4:潔淨的繼承者—-原型式繼承JavaScript繼承原型
- ECMAScript5中的物件,原型,原型鏈,原型的幾種繼承模式【一】物件原型繼承模式
- js裡的物件基本理解(原型)JS物件原型
- 原型和繼承原型繼承
- JS 繼承的 六 種實現方式JS繼承
- js實現繼承的三種方式JS繼承
- js實現繼承的幾種方式JS繼承
- JS原型鏈、prototype、__proto__、原型鏈繼承詳解JS原型繼承
- php物件導向多繼承實現PHP物件繼承
- javascript的原型和繼承JavaScript原型繼承