ECMAscript3中給Function的原型定義了兩個(gè)方法,它們是Function.prototype.cal
l和Function.prototype.apply
。在實(shí)際開發(fā)中,特別是在一些函數(shù)風(fēng)格的代碼編寫中涣狗,call和apply方法尤為重要,能熟練運(yùn)用這兩個(gè)方法,是們真正成為一句javascript程序員的重要一步太示。
----------引用javascript設(shè)計(jì)模式與開實(shí)踐
call() apply()的區(qū)別
1.call()
方法是第一個(gè)參數(shù)指定好this的指向和若干個(gè)參數(shù)執(zhí)行函數(shù)的一個(gè)方法
2.apply()
方法第一個(gè)參數(shù)是指定this的指向柠贤,第二個(gè)參數(shù)是一個(gè)數(shù)組或者類組組執(zhí)行函數(shù)的一個(gè)方法
2.當(dāng)調(diào)用javascript函數(shù)方法,解釋器不會(huì)計(jì)較形參和實(shí)參的數(shù)量类缤,類型以及順序上的區(qū)別臼勉,javascript參數(shù)也是用一個(gè)數(shù)組來(lái)表示 的,從意義上說(shuō)餐弱,apply比call的執(zhí)行率更高一些坚俗,我們必要不關(guān)心apply里面有多少個(gè)參數(shù),我們只有一下子推入進(jìn)去就可以了岸裙。
先看看第一個(gè)this可以指向那里猖败,寫不不同的類型輸出不同的this
先看看兩者的語(yǔ)法的區(qū)別
call(argthis,arg1,arg2,arg3......)
apply(argthis,[arg1,arg2,arg3....])
1.不傳或者傳入null
,undefined
函數(shù)都指向window
對(duì)象
2.傳遞一個(gè)函數(shù)的函數(shù)名,指向的是這個(gè)函數(shù)的引用降允,并不一定是這個(gè)引用函數(shù)的真正的this,因?yàn)閠his在函數(shù)中運(yùn)行時(shí)執(zhí)行的下上文恩闻,并不是在定義時(shí)執(zhí)行的上下文,想理解運(yùn)行時(shí)執(zhí)行
和定義時(shí)執(zhí)行
請(qǐng)看一下我上篇的this該何去何從
3.傳遞的是原始值(數(shù)值型,字符串型揉忘,布爾類型)都指向其包裝對(duì)象米同,比方說(shuō)String ,Boolean,Number
4.傳遞的是一個(gè)對(duì)象,就指象其對(duì)象
針對(duì)上面四個(gè)定義我們做一個(gè)初級(jí)簡(jiǎn)單的例子能更加深入的理解一下
function demo(){
console.log(this)
} //=>定義一個(gè)demo函數(shù)尉剩,函數(shù)體內(nèi)打印出this的指向,如果光執(zhí)行這段代碼肯定輸出window
function test(){} //=>定義一個(gè)test函數(shù)
obj = {} //=>定義一個(gè)obj對(duì)象
demo.call() //=>window
demo.call(null) //=>window
demo.call(undefined) //=>window
demo.call(test) //=>function test(){}
demo.call(1) //=>Number
demo.call('ziksang') //=>String
demo.call(true) //=>Boolean
demo.call(obj) //=>obj
在寫嚴(yán)格模式下我們又要注意的一點(diǎn)
'use strict'
function demo(){
console.log(this === window)
console.log(this === null)
console.log(this === undefined)
console.log(this == null)
console.log(this == undefined)
}
demo.call(null)
//=>false true false true true 在嚴(yán)格模式下null就指向null 為什么 == undefined可以和null進(jìn)行類型轉(zhuǎn)換
demo.call(undefined)
//=> false false true true true 跟上面同理
demo.call()
//=>false false true true true 本質(zhì)上就是執(zhí)行了demo()函數(shù)在嚴(yán)格模式下指向undefined
執(zhí)行一個(gè)函數(shù)用call apply把this指向一個(gè)對(duì)象
var name = "windowZiksang"
var info = "window有軍魂"
function demo(){
var and = "是"
var result = this.name+and+this.info
console.log(result)
}
var obj={
name : "ziksang",
info : "有軍魂"
}
demo.call()
demo.apply() //=>其實(shí)兩者執(zhí)行的結(jié)果是一樣的毅臊,就像我們上面說(shuō)的如果我不寫任何東西理茎,只是純粹執(zhí)行了demo這個(gè)方法,
此時(shí)函數(shù)體內(nèi)的this指向的是window下的對(duì)象屬性管嬉,執(zhí)行出來(lái)windowZiksang是window有軍魂
demo.call(obj)
demo.apply(obj)//=>還是兩者執(zhí)行的結(jié)果是一樣的皂林,執(zhí)行函數(shù)demo這個(gè)方法的同時(shí)還把函數(shù)體內(nèi)的this偷偷指向了obj這個(gè)對(duì)象,
指向了obj對(duì)象體內(nèi)的name和info蚯撩,所以執(zhí)行出來(lái)ziksang是有軍魂
在實(shí)際開發(fā)中我們也會(huì)不經(jīng)意的改變this的指向础倍,我們?nèi)绾涡拚?/p>
document.getElementById("div").onclick = function(){
console.log(this.id) //=>給id為div的dom元素綁定一個(gè)click事件,此時(shí)這個(gè)this指向的就是這個(gè)dom元素 輸出div
}
如果我們?cè)谝粋€(gè)函數(shù)里有一個(gè)內(nèi)部函數(shù)func我們看看有會(huì)發(fā)生什么
document.getElementById("div").onclick = function(){
console.log(this.id) //=> div 同上
var func = function(){
console.log(this.id) //=>此時(shí)輸出undefined 因?yàn)閒unc函數(shù)指向的是window對(duì)向 胎挎,在window對(duì)象上沒(méi)有id這個(gè)屬性沟启,所以會(huì)報(bào)一個(gè)undefined
因?yàn)樽兞康奶嵘粫?huì)報(bào)錯(cuò),而在嚴(yán)格模式下就會(huì)報(bào)錯(cuò)
}
func() //=>執(zhí)行內(nèi)部func這個(gè)函數(shù)
}
那我們?nèi)绾涡拚@個(gè)func函數(shù)犹菇,讓他的this指向dom元素
document.getElementById("div").onclick = function(){
console.log(this.id) //=> div 同上
var func = function(){
console.log(this.id) //=>此時(shí)輸出 div德迹,因?yàn)橄旅鎓unc.call(this)在執(zhí)
行的時(shí)候把this指向了div元素,但我們還可以用其它方式
}
func.call(this) //=>執(zhí)行內(nèi)部func這個(gè)函數(shù)并且把func函數(shù)體內(nèi)的this指向向div這個(gè)dom元素
}
`````````````````````
>用匿名函數(shù)調(diào)用call方法或者apply方法,展示call和apply的用法用途
**這個(gè)例子展示了如何讓數(shù)組對(duì)象里都加一個(gè)say()方法**
````````````````````
var person = [
{name : "ziksang",age : 20},
{name : "hello"项栏,age : 21}
] //=>聲明一個(gè)數(shù)組對(duì)象,也可以叫類數(shù)組
for(var i = 0;i<person.length;i++){ //=>把數(shù)組對(duì)象進(jìn)行一個(gè)循環(huán)浦辨,每個(gè)對(duì)象進(jìn)行加say方法
(function(i){
this.say = function(){
console.log( i + this.name+this.age)
//=>0ziksang20 1hello21
}
this.say()
}).call(person[i],i) //=> 里面是一個(gè)自執(zhí)行匿名函數(shù),正常的匿名自執(zhí)行函
數(shù)是這么寫的(function(){})(),用(function(){}).call()是同一個(gè)意思,立即執(zhí)行一
個(gè)函數(shù)流酬,不同的是用call把其里面的this指針指向了一個(gè)對(duì)象币厕,進(jìn)行循環(huán)的話
對(duì)每個(gè)對(duì)象都進(jìn)行的添加,i是傳進(jìn)去的參數(shù)芽腾,是循環(huán)的下標(biāo)
}
如果我們?cè)侔裵erson數(shù)組對(duì)象打印出來(lái)旦装,發(fā)現(xiàn)里面每一個(gè)對(duì)象都有了say方法,然后我們都可以進(jìn)行調(diào)用
for(var j = 0;j<person.length;j++){
console.log(person[j].say()) //=>0ziksang20 1hello21
}
````````````````````
>再舉個(gè)javascript設(shè)計(jì)模式中一個(gè)如何修正dom中的this指向
**其實(shí)書中怎么說(shuō)是書中怎么寫摊滔,如果能有前面的鋪墊我再能更細(xì)的給大家
寫出來(lái)我相信阴绢,肯定會(huì)對(duì)大家一定的了解**
````````````
html
<div id="div1"><div>
script
var getId = document.getElementById
getId("div1") //=>會(huì)報(bào)錯(cuò)
//=>給大家解釋一下為什么會(huì)報(bào)錯(cuò),請(qǐng)先閱讀下面的引用后再看我后面一句話
//=>因?yàn)殡m然document.getElementById內(nèi)部的指向的document對(duì)象
但是前面我說(shuō)過(guò)很多次this都是運(yùn)行時(shí)執(zhí)行的艰躺,getId引用了document.getElementById這個(gè)方法呻袭,此時(shí)的this就指向了window對(duì)象,所以會(huì)報(bào)錯(cuò)
````````````
----引用
在chrome,firefox,ie10中執(zhí)行過(guò)后就會(huì)發(fā)現(xiàn)腺兴,這優(yōu)代碼拋出一個(gè)異常左电。這是因?yàn)樵S多引擎的document.getElementById方法的內(nèi)部實(shí)現(xiàn)中需要到這個(gè)this.這個(gè)this本來(lái)被期望指向docment,當(dāng)getElementById如果被下載確使用的話本質(zhì)指向的就是document對(duì)象
--修正
那我們看看下面如何去修正這個(gè)this本人感覺(jué)有點(diǎn)繞页响,我也會(huì)慢慢去給大家講明白
``````````````````
//=>最外面一層其實(shí)本質(zhì)上是我們自己定義的一個(gè)叫document.getElementById的一個(gè)方法篓足,采用的是自執(zhí)行
傳了一個(gè)js document內(nèi)部自帶的document.getElementById的方法
document.getElementById = (function(func){
return function(){ //=>第二層返回一個(gè)匿名函數(shù)
return func.apply(document,arguments)
//=>返回一個(gè)修正后的document.getElementById這個(gè)方法,
那他是如何修正的呢闰蚕?每次調(diào)用的時(shí)候都會(huì)把document.getElementById
這個(gè)引用再次從新指向document對(duì)象栈拖,然后加上所需要傳的參數(shù)
}
})(document.getElementById) //=>這個(gè)是要傳的參數(shù),也是傳的方法的document對(duì)象下getElementById這個(gè)方法的引用
var getId = document.getElementById //=>拿到引用
var div = getId('div1') //=>把參數(shù)寫入没陡,此時(shí)內(nèi)部就有一個(gè)機(jī)制執(zhí)行了 return func.apply(document,arguments) 代碼 修正this
console.log(div.id) //=>這里就能正確打印出div.id = div1了
``````````````````
>call apply如何帶參數(shù)執(zhí)行出不同的值
``````````````
看這個(gè)先看最下面執(zhí)的一個(gè)方法涩哟,用call和apply改變了this的指向,指向到了那里再進(jìn)行上面的執(zhí)行后結(jié)果的分析诗鸭,我們可以看的出指向了obj對(duì)象
var obj = {
name : "ziksang",
age : 22,
say : function(){
console.log(this.name+this.age)
}
}
function demo(name){
console.log(this) //=>obj{...} 一般來(lái)說(shuō)都是指向window對(duì)向染簇,因?yàn)閠his被改變指向obj對(duì)象,所以這里打印出obj對(duì)象
console.log(this.name) //=>ziksang 因?yàn)橹赋鰋bj里面的name强岸,本質(zhì)上是執(zhí)行了obj.name
console.log(name) //=>hello 這里沒(méi)有加this執(zhí)行了自己所傳的參數(shù) ,這里傳了一個(gè)hello所以打印出來(lái)是一個(gè)hello
this.say() //=>ziksang22 指向obj對(duì)象砾赔。say方法里的this.name和this.age都指向了obj自己內(nèi)部的name和age
}
demo.call(obj,"hello")
demo.apply(obj,["hello"]) //=>call和apply都是一個(gè)意思蝌箍,區(qū)別就在于兩個(gè)傳參的方式不一樣,apply是以一個(gè)數(shù)組的方式都入暴心,也可以是類數(shù)組
``````````````
>我們用自己的方式去實(shí)現(xiàn)bind方法
1.Function.prototype.bind是ECMAscript5新增加的一個(gè)方法
2.傳參和call和apply類似
3.call和apply會(huì)改變this的指向后執(zhí)行這個(gè)函數(shù)妓盲,而bind不會(huì),會(huì)返回改變this指向后的引用
**接下來(lái)我給大家如何實(shí)現(xiàn)一個(gè)bind方法**
```````````````
//=>先看看這個(gè)
//=>我們?cè)趦?nèi)部Function函數(shù)原形上加上一個(gè)bind方法专普,我仔細(xì)琢磨過(guò)
console.log(typeof Function.prototype) //=>Functoin 而輸出不是一個(gè)對(duì)象悯衬,js中Function這種東西很特殊
Function.prototype.bind = function(){
console.log(this)
}
function demo(){}
demo.bind() //=> function demo(){} 輸出的是自己本身的函數(shù)引用,本意就是想讓大家理解一下這個(gè)this的意思 因?yàn)樽约豪斫獾暮镁?
那就繼續(xù)下面如何實(shí)現(xiàn)一個(gè)簡(jiǎn)單的bind方法
//=>我們先在function原型內(nèi)部上面加一個(gè)bind方法檀夹,其實(shí)本質(zhì)上是有的筋粗,只用瀏覽器支持策橘,不然我們就是從寫
Function.prototype.bind = function(){
var self = this //=>這里的this是保存調(diào)用的函數(shù),上面給我大家解釋過(guò)了娜亿,愛(ài)不愛(ài)我
return function(){ //=>返回一個(gè)匿名函數(shù)
return self.apply(context,arguments) //=>返回一個(gè)函數(shù)調(diào)用apply方法改變this指向丽已,第二個(gè)是所要傳在參數(shù)
}
}
var obj = {
name : "ziksang"
}
var demo = function(){
console.log(this.name)
}.bind(obj) //=>這樣的寫法上面定義過(guò)了bind的原理,不會(huì)執(zhí)行函數(shù)买决,只是返回一個(gè)改變內(nèi)部this的函數(shù)的引用
demo() //=>ziksang 再進(jìn)行執(zhí)行
```````````````
>證實(shí)一下bind只是返回一個(gè)函數(shù)的引用
``````````````````
document.addEventListener('click',function(){
console.log(this)
},false)
//=>document 返回一個(gè)document對(duì)象
因?yàn)閐ocument對(duì)象上的addEventListener這個(gè)方法會(huì)自己動(dòng)執(zhí)行一個(gè)內(nèi)部要調(diào)用的函數(shù)沛婴,不需手動(dòng)調(diào)用
那我們?cè)囋囀謩?dòng)調(diào)有會(huì)怎么樣
function demo(){
console.log(this)
} //=>調(diào)用之后直接打出來(lái)window對(duì)象
document.addEventListener('click',demo(),false) //=>這樣的話直接默認(rèn)你調(diào)用了demo()這個(gè)方法,不會(huì)指向document點(diǎn)擊事件好督赤,所指向的document對(duì)象
證實(shí)bind不會(huì)調(diào)用原函數(shù)嘁灯,只是返回它的一個(gè)引用
var obj = {
name :"ziksang" //=>聲明一個(gè)obj對(duì)象
}
function demo(first,next){
console.log(this.name+first+next)
}
document.addEventListener('click',demo.bind(obj,"hello","world"),false)
//=> ziksang hello world 這證明了沒(méi)有執(zhí)行原函數(shù),而且改變了this的指向
指向了obj對(duì)象躲舌,傳了參數(shù)丑婿,很不錯(cuò)
``````````````````
>接下了我們要根據(jù)前面的寫的一個(gè)bind方法我們要繼續(xù)去加強(qiáng)功能,完善它
```````````````
//=>還是那句話對(duì)于難點(diǎn)進(jìn)行一步一步的分析孽糖,先為下面實(shí)現(xiàn)復(fù)雜一點(diǎn)的方法做一個(gè)鋪墊
Function.prototype.bind = function(){
var self = this //=>保存原函數(shù) 這里的this指向調(diào)用這個(gè)方法的函數(shù)指針
var context= Array.prototype.shift.call(arguments)
//=>我學(xué)的覺(jué)得這是一個(gè)工具方法枯冈,下面我會(huì)給大家講一些工具方法
console.log(argthis)
//=>1 輸出1,這個(gè)this指向arguments類數(shù)組對(duì)象,借用了Array.prototype原
形上的方法办悟,也是內(nèi)置的方法來(lái)彈出數(shù)組第一個(gè)值
var arg = Array.prototype.slice.call(arguments)
//=>slice方法可以把類數(shù)組轉(zhuǎn)為真正的數(shù)組
console.log(arguments)
//=>[3尘奏,4,5病蛉,6]這里打印出剩余的數(shù)組對(duì)象
}
var demo = function(){
}.bind(1,3,4,5,6)//=>這里是進(jìn)行一個(gè)方法調(diào)用
結(jié)合上面的列子再進(jìn)行加強(qiáng)炫加,上面沒(méi)有做到bind返回一個(gè)新的函數(shù)
Function.prototype.bind = function(){
var self = this
var context = Array.prototype.shift.call(arguments)
var args = Array.prototype.slice.call(arguments)
//=>這上面的我就不用說(shuō)了,前面我已經(jīng)給大家分析過(guò)了
return function(){
return self.apply(context,[].concat.call(args,[].slice.call(arguments)))
}
//=>這里主要返回了一個(gè)函數(shù)铺然,里面又套了一層讓原函數(shù)指向傳入的context當(dāng)作函數(shù)的體內(nèi)新的this指向
//=>并且組合兩次分別傳入的參數(shù)俗孝,作為新函數(shù)的參數(shù)
}
var obj = {
name : 'ziksang'
}
var demo = function(name,age){
console.log(this.name) //=>ziksang
console.log(name+age) //=>hello20
}.bind(obj,'hello')
demo(22)
```````````````
>構(gòu)造函數(shù)里的繼承方法
比方法父親會(huì)說(shuō)自己的名子,而孩子一開始不會(huì)說(shuō)魄健,那就要讓父親去教赋铝,那我們看看是孩子如何去繼承父親的方法
`````````````````````
function Father(){ //=>聲明一個(gè)父親類
this.say = function(){ //=>里面有一個(gè)say方法,輸出自己的名字
this.name = "父親"
console.log(this.name)
}
}
function Son(name){ //=>定義了一個(gè)兒子類
this.name = name //=>從寫了名字這個(gè)屬性沽瘦,如果不從寫革骨,還是會(huì)指向this.name="父親"
Father.call(this) //=>把父親類里的所有方法屬性都拿過(guò)來(lái)用
把里面的this指向到兒子類里
}
var ziksang = new Son("ziksang")
ziksang.say() //=>ziksang
`````````````````````
>最后我講幾個(gè)借用一些對(duì)象原型上的工具方法
````````````````
1.Array.prototype.slice.call()
(function(){
console.log(arguments) //=>打印出一個(gè)具有Length屬性的類數(shù)組對(duì)象
var a = Array.prototype.slice.call(arguments)
//=>arguments類數(shù)組借用array原型上自帶的方法轉(zhuǎn)為真正的數(shù)組
//但在es6里有一個(gè)更好的方法Array.from()
console.log(a) //=>[1,2,3,4,5]
var b = Array.prototype.slice.call(arguments,0,2)
//=>轉(zhuǎn)成數(shù)組后進(jìn)行截取
console.log(b) //=>【1,2】
})(1,2,3,4,5)
2.Array.prototype.push.call()
(function(){
Array.prototype.push.call(arguments,6)
//=>借用Array原型Push的方法在下標(biāo)為5的下面添加一個(gè)值為6
console.log(arguments)
})(1,2,3,4,5)
var obj1 = {} 聲明一個(gè)空對(duì)象obj
Array.prototype.push.call(obj1,'ziksang') 在空對(duì)象下標(biāo)0下面添加一個(gè)ziksang屬性析恋,又俱有了Length屬性良哲,我稱他為偽對(duì)象
console.log(obj1)
3.借用Math對(duì)象比對(duì)數(shù)組中的大小
var array = [1,2,3,4,5]
var a = Math.max(1,2,3,4)
var b = Math.max(array)
console.log(a) //=>輸出4
console.log(b)//=>輸出NaN 因?yàn)閿?shù)組是比不了大小的
var c = Math.max.apply(Math,array)
console.log(c) //=>借用Math對(duì)象上的max方法來(lái)解析數(shù)組大小,用call的話不行助隧,只是一個(gè)一個(gè)傳筑凫,不能傳數(shù)組
4、判斷是否是一個(gè)數(shù)組
var array = [1,2,3,4]
if(Object.prototype.toString.call(array) === "[object Array]"){
console.log(true) //=>在老版本的中判斷是否為數(shù)組借用Object原形上的方法.toString來(lái)判斷是什么類形
}
var b = Array.isArray(array) //=>es5提供了判斷是否為數(shù)組類形
console.log(b) //=>兩都打印出來(lái)都為true
````````````````