Javascript 知识点逐步分析与解剖前端开发那些事程序员

call apply bind谁与争锋

2017-01-03  本文已影响311人  Ziksang
ECMAscript3中给Function的原型定义了两个方法,它们是Function.prototype.call和Function.prototype.apply。在实际开发中,特别是在一些函数风格的代码编写中,call和apply方法尤为重要,能熟练运用这两个方法,是们真正成为一句javascript程序员的重要一步。

----------引用javascript设计模式与开实践

call() apply()的区别

1.call()方法是第一个参数指定好this的指向和若干个参数执行函数的一个方法
2.apply()方法第一个参数是指定this的指向,第二个参数是一个数组或者类组组执行函数的一个方法
2.当调用javascript函数方法,解释器不会计较形参和实参的数量,类型以及顺序上的区别,javascript参数也是用一个数组来表示 的,从意义上说,apply比call的执行率更高一些,我们必要不关心apply里面有多少个参数,我们只有一下子推入进去就可以了。

先看看第一个this可以指向那里,写不不同的类型输出不同的this

先看看两者的语法的区别
call(argthis,arg1,arg2,arg3......)
apply(argthis,[arg1,arg2,arg3....])

1.不传或者传入null,undefined函数都指向window对象
2.传递一个函数的函数名,指向的是这个函数的引用,并不一定是这个引用函数的真正的this,因为this在函数中运行时执行的下上文,并不是在定义时执行的上下文,想理解运行时执行定义时执行请看一下我上篇的this该何去何从
3.传递的是原始值(数值型,字符串型,布尔类型)都指向其包装对象,比方说String ,Boolean,Number
4.传递的是一个对象,就指象其对象

针对上面四个定义我们做一个初级简单的例子能更加深入的理解一下

function demo(){
     console.log(this)
}     //=>定义一个demo函数,函数体内打印出this的指向,如果光执行这段代码肯定输出window
function test(){}  //=>定义一个test函数
obj = {}   //=>定义一个obj对象
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

在写严格模式下我们又要注意的一点

'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 在严格模式下null就指向null 为什么 == undefined可以和null进行类型转换
demo.call(undefined) 
//=> false false true true true 跟上面同理
demo.call()
//=>false false true true true 本质上就是执行了demo()函数在严格模式下指向undefined

执行一个函数用call apply把this指向一个对象

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() //=>其实两者执行的结果是一样的,就像我们上面说的如果我不写任何东西,只是纯粹执行了demo这个方法,
 此时函数体内的this指向的是window下的对象属性,执行出来windowZiksang是window有军魂
demo.call(obj)
demo.apply(obj)//=>还是两者执行的结果是一样的,执行函数demo这个方法的同时还把函数体内的this偷偷指向了obj这个对象,
指向了obj对象体内的name和info,所以执行出来ziksang是有军魂

在实际开发中我们也会不经意的改变this的指向,我们如何修正

 document.getElementById("div").onclick = function(){
       console.log(this.id)  //=>给id为div的dom元素绑定一个click事件,此时这个this指向的就是这个dom元素 输出div
}

如果我们在一个函数里有一个内部函数func我们看看有会发生什么
document.getElementById("div").onclick = function(){
      console.log(this.id)     //=> div  同上
      var func = function(){
             console.log(this.id)   //=>此时输出undefined 因为func函数指向的是window对向 ,在window对象上没有id这个属性,所以会报一个undefined
因为变量的提升不会报错,而在严格模式下就会报错
      }
     func()    //=>执行内部func这个函数
}

那我们如何修正这个func函数,让他的this指向dom元素
document.getElementById("div").onclick = function(){
      console.log(this.id)     //=> div  同上
      var func = function(){
             console.log(this.id)   //=>此时输出 div,因为下面func.call(this)在执
行的时候把this指向了div元素,但我们还可以用其它方式
      }
     func.call(this)    //=>执行内部func这个函数并且把func函数体内的this指向向div这个dom元素
}
`````````````````````
>用匿名函数调用call方法或者apply方法,展示call和apply的用法用途

 **这个例子展示了如何让数组对象里都加一个say()方法**
````````````````````
var person = [
{name : "ziksang",age : 20},
{name : "hello",age : 21}
]   //=>声明一个数组对象,也可以叫类数组
for(var i = 0;i<person.length;i++){  //=>把数组对象进行一个循环,每个对象进行加say方法
    (function(i){
          this.say = function(){
                 console.log( i + this.name+this.age)
                  //=>0ziksang20   1hello21
          }
         this.say()
    }).call(person[i],i)   //=> 里面是一个自执行匿名函数,正常的匿名自执行函
数是这么写的(function(){})(),用(function(){}).call()是同一个意思,立即执行一
个函数,不同的是用call把其里面的this指针指向了一个对象,进行循环的话
对每个对象都进行的添加,i是传进去的参数,是循环的下标
}

如果我们再把person数组对象打印出来,发现里面每一个对象都有了say方法,然后我们都可以进行调用
for(var j = 0;j<person.length;j++){
     console.log(person[j].say())  //=>0ziksang20   1hello21
}
````````````````````
>再举个javascript设计模式中一个如何修正dom中的this指向

**其实书中怎么说是书中怎么写,如果能有前面的铺垫我再能更细的给大家
写出来我相信,肯定会对大家一定的了解**
````````````
html
    <div id="div1"><div>
script
    var getId = document.getElementById
    getId("div1")  //=>会报错
   //=>给大家解释一下为什么会报错,请先阅读下面的引用后再看我后面一句话
  //=>因为虽然document.getElementById内部的指向的document对象
但是前面我说过很多次this都是运行时执行的,getId引用了document.getElementById这个方法,此时的this就指向了window对象,所以会报错
````````````
 ----引用
     在chrome,firefox,ie10中执行过后就会发现,这优代码抛出一个异常。这是因为许多引擎的document.getElementById方法的内部实现中需要到这个this.这个this本来被期望指向docment,当getElementById如果被下载确使用的话本质指向的就是document对象

--修正
    那我们看看下面如何去修正这个this本人感觉有点绕,我也会慢慢去给大家讲明白
``````````````````
   //=>最外面一层其实本质上是我们自己定义的一个叫document.getElementById的一个方法,采用的是自执行
传了一个js document内部自带的document.getElementById的方法
    document.getElementById = (function(func){
           return function(){    //=>第二层返回一个匿名函数
               return func.apply(document,arguments)  
                  //=>返回一个修正后的document.getElementById这个方法,
那他是如何修正的呢?每次调用的时候都会把document.getElementById
这个引用再次从新指向document对象,然后加上所需要传的参数
           }
     })(document.getElementById)  //=>这个是要传的参数,也是传的方法的document对象下getElementById这个方法的引用

     var getId = document.getElementById  //=>拿到引用
     var div = getId('div1')   //=>把参数写入,此时内部就有一个机制执行了 return func.apply(document,arguments)  代码  修正this
     console.log(div.id)   //=>这里就能正确打印出div.id = div1了
``````````````````
>call apply如何带参数执行出不同的值

``````````````
看这个先看最下面执的一个方法,用call和apply改变了this的指向,指向到了那里再进行上面的执行后结果的分析,我们可以看的出指向了obj对象
var obj = {
    name : "ziksang",
    age : 22,
    say : function(){
        console.log(this.name+this.age)
    }
}
function demo(name){
    console.log(this)  //=>obj{...}  一般来说都是指向window对向,因为this被改变指向obj对象,所以这里打印出obj对象
    console.log(this.name)  //=>ziksang 因为指出obj里面的name,本质上是执行了obj.name
    console.log(name) //=>hello 这里没有加this执行了自己所传的参数 ,这里传了一个hello所以打印出来是一个hello
    this.say() //=>ziksang22   指向obj对象。say方法里的this.name和this.age都指向了obj自己内部的name和age
}
demo.call(obj,"hello")
demo.apply(obj,["hello"])  //=>call和apply都是一个意思,区别就在于两个传参的方式不一样,apply是以一个数组的方式都入,也可以是类数组
``````````````

>我们用自己的方式去实现bind方法

1.Function.prototype.bind是ECMAscript5新增加的一个方法
2.传参和call和apply类似
3.call和apply会改变this的指向后执行这个函数,而bind不会,会返回改变this指向后的引用

**接下来我给大家如何实现一个bind方法**
```````````````
//=>先看看这个
//=>我们在内部Function函数原形上加上一个bind方法,我仔细琢磨过
console.log(typeof Function.prototype) //=>Functoin 而输出不是一个对象,js中Function这种东西很特殊
Function.prototype.bind = function(){
       console.log(this)
}
function demo(){}
demo.bind() //=> function demo(){} 输出的是自己本身的函数引用,本意就是想让大家理解一下这个this的意思 因为自己理解的好久

那就继续下面如何实现一个简单的bind方法
//=>我们先在function原型内部上面加一个bind方法,其实本质上是有的,只用浏览器支持,不然我们就是从写
Function.prototype.bind = function(){
        var self = this   //=>这里的this是保存调用的函数,上面给我大家解释过了,爱不爱我
        return function(){      //=>返回一个匿名函数
              return self.apply(context,arguments)  //=>返回一个函数调用apply方法改变this指向,第二个是所要传在参数
        }
}
var obj = {
      name : "ziksang"
}
var demo = function(){
          console.log(this.name)
}.bind(obj)   //=>这样的写法上面定义过了bind的原理,不会执行函数,只是返回一个改变内部this的函数的引用
demo()  //=>ziksang  再进行执行
```````````````

>证实一下bind只是返回一个函数的引用

``````````````````
document.addEventListener('click',function(){
           console.log(this)   
},false)
//=>document 返回一个document对象
因为document对象上的addEventListener这个方法会自己动执行一个内部要调用的函数,不需手动调用

那我们试试手动调有会怎么样
function demo(){
    console.log(this)
}        //=>调用之后直接打出来window对象
document.addEventListener('click',demo(),false)  //=>这样的话直接默认你调用了demo()这个方法,不会指向document点击事件好,所指向的document对象


证实bind不会调用原函数,只是返回它的一个引用
var obj = {
    name :"ziksang"   //=>声明一个obj对象
}
function demo(first,next){
    console.log(this.name+first+next)
}
document.addEventListener('click',demo.bind(obj,"hello","world"),false)
//=> ziksang hello world    这证明了没有执行原函数,而且改变了this的指向
指向了obj对象,传了参数,很不错
``````````````````

>接下了我们要根据前面的写的一个bind方法我们要继续去加强功能,完善它

```````````````
//=>还是那句话对于难点进行一步一步的分析,先为下面实现复杂一点的方法做一个铺垫

Function.prototype.bind = function(){
            var self = this //=>保存原函数 这里的this指向调用这个方法的函数指针
            var context= Array.prototype.shift.call(arguments)
//=>我学的觉得这是一个工具方法,下面我会给大家讲一些工具方法
            console.log(argthis)
//=>1  输出1,这个this指向arguments类数组对象,借用了Array.prototype原
形上的方法,也是内置的方法来弹出数组第一个值
                        var arg = Array.prototype.slice.call(arguments)
//=>slice方法可以把类数组转为真正的数组
            console.log(arguments)
//=>[3,4,5,6]这里打印出剩余的数组对象
        }
        var demo  = function(){

}.bind(1,3,4,5,6)//=>这里是进行一个方法调用

结合上面的列子再进行加强,上面没有做到bind返回一个新的函数
Function.prototype.bind = function(){
            var self = this
            var context = Array.prototype.shift.call(arguments)
            var args = Array.prototype.slice.call(arguments)
//=>这上面的我就不用说了,前面我已经给大家分析过了
            return function(){
                return self.apply(context,[].concat.call(args,[].slice.call(arguments)))
            }
//=>这里主要返回了一个函数,里面又套了一层让原函数指向传入的context当作函数的体内新的this指向
//=>并且组合两次分别传入的参数,作为新函数的参数
}
var obj = {
    name : 'ziksang'
}
var demo  = function(name,age){
    console.log(this.name)  //=>ziksang
    console.log(name+age) //=>hello20

}.bind(obj,'hello')
 demo(22)
```````````````
>构造函数里的继承方法


比方法父亲会说自己的名子,而孩子一开始不会说,那就要让父亲去教,那我们看看是孩子如何去继承父亲的方法

`````````````````````
  function Father(){                 //=>声明一个父亲类
       this.say = function(){   //=>里面有一个say方法,输出自己的名字
                    this.name = "父亲"
             console.log(this.name)   
       }
          
  }
  function Son(name){    //=>定义了一个儿子类
       this.name = name   //=>从写了名字这个属性,如果不从写,还是会指向this.name="父亲"
       Father.call(this)     //=>把父亲类里的所有方法属性都拿过来用
    把里面的this指向到儿子类里
  }
  var ziksang = new Son("ziksang")
  ziksang.say()   //=>ziksang
`````````````````````

>最后我讲几个借用一些对象原型上的工具方法

````````````````
1.Array.prototype.slice.call()
(function(){
    console.log(arguments) //=>打印出一个具有Length属性的类数组对象
     var a = Array.prototype.slice.call(arguments)
     //=>arguments类数组借用array原型上自带的方法转为真正的数组
    //但在es6里有一个更好的方法Array.from()
     console.log(a) //=>[1,2,3,4,5]
     var b = Array.prototype.slice.call(arguments,0,2)
     //=>转成数组后进行截取
     console.log(b) //=>【1,2】
})(1,2,3,4,5)

2.Array.prototype.push.call()
(function(){
    
     Array.prototype.push.call(arguments,6) 
//=>借用Array原型Push的方法在下标为5的下面添加一个值为6
     console.log(arguments)
     
})(1,2,3,4,5)

var obj1 = {}  声明一个空对象obj
Array.prototype.push.call(obj1,'ziksang')  在空对象下标0下面添加一个ziksang属性,又俱有了Length属性,我称他为伪对象
console.log(obj1)

3.借用Math对象比对数组中的大小
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  因为数组是比不了大小的
var c = Math.max.apply(Math,array)
console.log(c) //=>借用Math对象上的max方法来解析数组大小,用call的话不行,只是一个一个传,不能传数组


4、判断是否是一个数组
     var array = [1,2,3,4]
     if(Object.prototype.toString.call(array) === "[object Array]"){

         console.log(true)  //=>在老版本的中判断是否为数组借用Object原形上的方法.toString来判断是什么类形
     }
     var b = Array.isArray(array) //=>es5提供了判断是否为数组类形
     console.log(b)   //=>两都打印出来都为true
````````````````
上一篇下一篇

猜你喜欢

热点阅读