簡單的this,麻煩的this
周末的Hello World咖啡館總是熱鬧非凡。
Java , Python, Ruby, JavaScript圍坐在一起,一邊喝咖啡,一邊海闊天空。
C老頭兒則待在一旁,冷眼旁觀。
聊著聊著,這話題不知怎么著轉移到了“this”上來了。
Java 說: “唉!你們不知道吧,對于一個初學Java的人來說,this 是非常難于理解的。”
Python說:“this 在你那里已經夠簡單了啊。還難于理解?”
“我們都是支持面向對象編程的,在我這里,this 可以用到實例方法或者構造器中,表示對當前對象實例的引用。”
- public class Point {
- private double x = 0.0;
- private double y = 0.0;
- public Point(int x, int y) {
- this.x = x;
- this.y = y;
- }
- public double distanceTo(Point that) {
- double dx = this.x - that.x;
- double dy = this.y - that.y;
- return Math.sqrt(dx*dx + dy*dy);
- }
- }
“這不很容易理解嗎? ” Ruby 問道。
“對于***次接觸面向對象編程的人來說,他分不清這個當前對象this到底是哪個對象。” Java說,“我必須得再寫一段代碼給他掰扯一下。”
- Point p1 = new Point(1,1);
- Point p2 = new Point(2,2);
- // this 指向的是p1
- p1.distanceTo(p2);
- // this 指向的是p2
- p2.distanceTo(p1);
“對啊,this必須得有個上下文才能準確理解。” Python說,“還有,你那個this吧,是個隱式的,像我是顯式的:”
- class Point:
- def __init__(this, x, y):
- this.x = x
- this.y = y
- def distanceTo(this,point):
- dx = this.x - point.x
- dy = this.y - point.y
- return math.sqrt(dx**2+dy**2)
Java 說:“你不是一直用self嗎,怎么現在是this?”
Python笑道:“我這不是為了和你Java老弟保持一致嘛,反正只是個變量名,你想用this就用this,想用that就用that,只不過我們習慣于用self 。”
Ruby說: “Python兄,你把this放到方法中作為一個參數,實在是太丑陋了,一點美感都沒有。”
“那是我們的哲學,我們信奉 Explicit is better than implicit。”
“可是在調用的時候,怎么不把一個對象傳給那個方法?你的self去哪里了?”
- p1 = Point(1,1)
- p2 = Point(2,2)
- p1.distanceTo(p2)
- p2.distanceTo(p1)
“你怎么不寫成: distanceTo(p1,p2) ?”
“那不行,” Python說,“如果那樣的話我們就不是面向對象了,而面向過程了。 ”
“哼哼,” C老頭兒在一旁冷笑一聲,“說來說去,還不是披了一層面向對象的外衣,內部實現依然是面向過程的?!”
“此話怎講?” Java一直以正統的面向對象自居,不像Python, Ruby ,Java即使是想輸出一個Hello World也得定義一個類不可。
“就說你吧,Java小子,你的Java 源文件被編譯以后變成了.class文件, 這個class文件被裝載到了Java虛擬機的一個區域,這個區域叫什么?” C 老頭兒出手不凡。
“當然是Method Area, 方法區了,這我會不知道?!”
“對啊,它為什么叫方法區? 為什么不叫Class Area, 類區?” C 老頭兒真是別出心裁。
“這…… ” Java 語噻了,他從來就沒有想過這個問題。
“你的方法區是被各個線程所共享的,存儲虛擬機加載類的信息,常量池,其中最主要的就是類中定義的方法相關的代碼。 而你創建的對象呢,卻是放在‘堆中’,虛擬機在執行的時候,要從方法區找到‘方法’,這些方法的字節碼在運行的過程中,會操作位于堆中的對象。 ”
“所以你看,你的數據和方法是分離的,一個地方是方法(所以叫方法區),一個地方是數據,和我們C寫出的程序是一樣的,都是面向過程的!” C老頭兒經過一系列證明后做了最終陳述。
Python也沉默了,他知道,自己在運行時也和這種方式差不多。
過了一會兒,Java 醒悟了過來:“不對,老頭兒你這是混淆概念,我們是站在程序員的角度在談論語言是不是面向對象的,而你則把我們拉到了實現層面,這是不對的。”
Python也附和道:“對對,我們是面向對象的語言,抽象程度比你的面向過程要高!”
“抽象? 哼哼,” C 老頭兒又冷笑一聲,“Linus 用C 寫了Linux,用C 寫了Git, 你覺得他沒有做抽象? 笑話! 依我看來,抽象就是要在變化的東西中找到不變的東西,和具體的編程語言關系不大啊。” C老頭說了一句至理名言。
Java 悄悄對Python說: “老頭兒主要做操作系統內核,操作系統中的那些虛擬內存,進程,線程,文件系統概念都很清晰, 并且很穩定,估計他沒有接觸到應用層變態的,不講道理的業務邏輯。 ”
C 老頭兒說:“別以為你們面向對象有多么了不起,我告訴你,有很多程序員,用著面向對象的語言,寫著面向過程的程序!關鍵是人!”
Ruby 說:“兩位兄臺,算了,不和老頭兒爭論了,來看看我的this吧, 奧不, 是self, 我這里必須用self。 我的self 和你們的都不一樣,在不同的位置表示不同的含義。比如說:”
- class Point
- # 此處的Self 就是Point這個類
- puts "Self is :#{self}"
- # 定義一個Class級別(靜態)的方法,self還是Point這個類
- def self.name
- puts "Self inside class method is: #{self}"
- end
- # 定義一個實例方法, 此處的self 就是對象實例了
- def name
- puts "Self inside instance method is: #{self}"
- end
- end
Java 說:“你這搞得太麻煩了,定義一個靜態方法,用static 不就結了?”
半天都沒有說話的JavaScript突然說道:“這也叫麻煩,來看看我是怎么處理this的!”
- function add(y){
- return this.x + y
- }
熟悉面向對象的Java, Python看到這么古怪的代碼,大為吃驚, 這是什么鬼? add函數中的這個this 到底指向誰?
JavaScript說:“不要大驚小怪! 我的this和你們的this ,self都不一樣,它是動態的,在定義時確定不了到底指向誰,只有等到函數調用的時候才能確定,this 指向的是最終調用它的那個對象,比如:”
- function add(y){
- //此時的this 指向的是全局的對象,在瀏覽器運行就是window
- return this.x + y
- }
- x = 10
- console.log(add(20))
在這里調用add函數的是全局上下文, 所以this指向的是全局對象,輸出的值是30 。
JavaScript說:“我還可以給add函數傳遞一個對象當作this。”
- function add(y){
- //此時的this 指向的是對象obj,this.x 是20 ,不是10
- return this.x + y
- }
- x = 10
- var obj = {x: 20};
- //傳遞一個對象給add函數
- add.call(obj,20) // 40
大家更加吃驚了。
JavaScript又展示了一個例子:
- var obj = {
- x:100,
- print:function(){
- console.log(this.x);
- }
- }
- obj.print() //100
Python說: “這個很容易理解,這個this應該是指向obj這個對象實例, 所以print函數輸出的x是100,對吧。”
“對的,再來看一個:”
- var obj = {
- x:100,
- y:{
- x : 200,
- print:function(){
- console.log(this.x);
- }
- }
- }
- obj.y.print() //200
Java 說道:“按照你的規則,這個this 指向的應該是最終調用它的對象,那就是y , 在y中,x是200,所以應該輸出200 !”
JavaScript說:“如果我把對象y中的x:200 給去掉,輸出是什么? ”
“難道是100 ? 不, 它不會向上一級去找,只會在y中尋找x 的值,如果沒有,就是undefined, 唉!你這this規則實在是太麻煩。”
JavaScript笑了笑:“再來看個更古怪的例子:”
- var obj = {
- x:100,
- y:{
- x : 200,
- print:function(){
- console.log(this.x);
- }
- }
- }
- var point ={x:15,y:15, f: obj.y.print}
- point.f() //輸出什么?
- var x = 10
- g = obj.y.print
- g() //輸出什么?
Python說:“這不還是一樣嘛, 都應該輸出200。”
JavaScript說: “不,point.f() 應該輸出15, 注意此時f 是對象point的一個函數,最終調用f 的是point對象,此時x = 15 了! ”
Java接口說:“我明白了,調用函數g()的是全局對象,x = 10 ,所以應該輸出10 。”
Python說:“你小子號稱前端之王,就這么用this來折磨程序員?”
JavaScript笑道:“其實吧,普通程序員直接操作this的機會也不太多,都被框架、類庫封裝好了!”
這時候就聽到C老頭兒在那里搖頭晃腦: “簡單就是美,簡單就是美啊。你們這幫小子,把世界搞得這么復雜,讓程序員們學習這么多不必要的復雜性,真是浪費生命啊。”
“浪費生命? 沒有我們這些語言,怎么可能創建出這么多Web應用程序出來? 你行嗎?”
“我是不行,我只知道你Java 虛擬機是用我C語言寫的, 你Python解釋器,Ruby解釋器也是C語言寫的, 就連JS的V8引擎也是用我的兄弟C++語言寫的。”
C 老頭兒把手中的咖啡往桌子上狠狠一摔,轉身就離開了咖啡館。
【本文為51CTO專欄作者“劉欣”的原創稿件,轉載請通過作者微信公眾號coderising獲取授權】