javascript中的this
和new
javascript是一門很靈活的語言,尤其是function
。他即可以以面向過程的方式來用,比如:
function getName() {return '張三'
}
getName()
也可以以面向對象的方式來用,比如:
function User() {this.name = '張三'
}var user = new User()
javascript
是如何實現面向對象編程的呢?他提供了new
這個關健字,有了new
就可以把對象進行實例化,比如:
function User(name, age){this.name = namethis.age = age
}
var zs = new User('zs', 20)
var ls = new User('ls', 30)
new
出來的兩個實例,會開辟兩塊新的內存區域,來保存這些數據,同時有指針指向對象User
。所以就有instanceof
這個運算符,這個運算符的意思就是:a是不是A的實例。比如上例:zs instanceof User
的返回值是true
。
即然是面向對象的編程語言,那this
也是不可或缺的。在javascript
中,this
永遠指向的是他的調用者。要理解這句話,我們舉幾個例子:
例子1
function test(){this.name = 'zs'
}
test()
當執行完成之后,這個name
會直接掛載到window
下面,因為是這樣執行的:winow.test()
。
例子2
var game = document.getElementById('game')
game.addEventListener('click', function () {setTimeout(function () {this.innerText = 'Clicked'}, 1000)
})
這個例子很簡單,點擊某個元素的時候,1秒后,讓他里面的html改成Clicked,可是你發現這樣不好使,就是因為this
指向的問題,因為這里面的this
也指向window
了,所以你執行window.innerText
會返回Clicked
。
例子3
function User(name) {this.name = namethis.getName = function () {console.log(this.name)}
}
var u = new User('zs')
u.getName()
這里面的this
的指向沒有問題,因為按照之前的原則,調用者是u
,也就是User
的實例,所以在方法getName
中,this.name
相當于u.name
,所以打印出zs
。
prototype和__proto__
prototype
javascript
是面向對象的語言,這個上面已經提過了,其他面向對象語言有一個必備我就是繼承,很顯然在ES6之前,沒有extends
這個關鍵字,那么,javascript
就是利用prototype
的原型鏈來實現繼承。先記住這句話,我們一會會說到繼承。prototype
其實只是對象的一個屬性,在Chrome控制臺下,可以直接看出來,但是這個屬性很特殊,這個屬性下可以掛載任何的對象、方法、屬性,而且掛載的東西可以反映到實例下對象上。說的比較繞,我們看個例子:
function User(name) {this.name = name
}
User.prototype.getName = function () {console.log(this.name)
}
var u = new User('zs')
u.getName()
我們在User.prototype
上面掛載了getName
的方法,在下面實例化User
之后的u
,就可以訪問這個方法。
看到這,你可以有個疑問,既然是給實例化對象用的,那下面這種方式豈不是更好、更直觀?
function User(name) {this.name = namethis.getName = function () {console.log(this.name)}
}
var u = new User('zs')
u.getName()
如果我們和Java語言進行對應,User
相當是Class
,name
相當于屬性,getName
相當于里面的方法,完美映射!可以這樣有一個問題啊,就是太費內存了。因為每new
一個對象,都會占用一塊內存區域,這樣User
里面方法屬性越多,那么每個實例化的對象都會對這些進行 深復制
,那么占用的內存空間就越大。那么javascript
是如何通過prototype
來解決內存占用的問題的呢?這就需要引用__proto__
。
__proto__
定義:__proto__
是存在于實例化后對象
的一個屬性,并且指向原對象的prototype
屬性。
比如上例中的u.__proto__ === User.prototype
返回的是true
。可以在Chrome
控制臺下查看u.__proto__
。
你會發現,不對吧,User
對象下也有__proto__
啊。那是因為User
也是Function
的實例,不信你可以試一下User.__proto__ === Function.prototype
的返回值。其實我們這樣定義函數:function test(){}
是一個語法糖的寫法,全拼應該是這樣:var test = new Function('alert(1)')
。
現在我們來解釋為什么使用prototype
能節省內存。不知道你有沒有注意到上面一句代碼u.__proto__ === User.prototype
,我為什么要使用三等?因為三等號除了值、類型外,內存地址也必須是相等的,也就是說User
不管實例化多少對象,他們的prototype
只有一份,放在User
里。客戶端的瀏覽器環境不像服務器,內存還是比較緊張的,所以javascript
通過這種方式,來解決內存占用問題。
繼承
方式一:直接繼承
先舉個例子:
var Animal = function (name) {this.name = name
}
Animal.prototype.walk = function () {console.log('I can walk!')
}
Animal.prototype.getName = function () {console.log('My name is ' + this.name + '!')
}var Dog = function (name) {this.name = name
}
Dog.prototype = Animal.prototypevar d = new Dog('Tom')
d.getName()
d.walk()
我們建立一個父類Animal
對象,建立一個子類Dog
,我們想讓Dog
也有walk方法和getName方法,通過上面對prototype
的了解,我們最先想到的是Dog.prototype = Animal.prototype
,這樣子類和父類的prototype
相等,那子類就有父類所有方法嘍,繼承鏈條是這樣的:d.__proto__ === Dog.prototype === Animal.prototype
。
這樣很直觀,但是也有一個比較嚴重的問題。我們在擴展Dog
的時候,同時父類也會有對應的方法,這很顯然是一個很嚴重的問題。
方式二:實例化繼承
為了解決上面的問題,我們需要引入一個空函數,這個空函數做為橋梁,把子類和父類之間的連接切斷。實現如下:
var F = function () {}
F.prototype = Animal.prototype
Dog.prototype = new F()Dog.prototype.say = function () {console.log('Say')
}
- 為什么是
Dog.prototype = new F()
呢?因為這樣即可以繼承Animal的所有方法,他的原型鏈是這樣的:
d.__proto__ --> Dog.prototype --> new F().__proto__
執行walk
方法,F已經有了,所以就不會再找Animal
了
- 新增加的方法又不影響父類,這句怎么講?因實例化的對象沒有
prototype
屬性!所以不會影響