函数的this、call、bind、apply

一、理解函数的参数

JS中的函数对于传递进来的参数很宽容,假设你定义的某个函数需要传递一个参数,但是你传递的时候却传递了两个,或者是没传,即使这样,解析器也不会报错。

之所以JS中的函数这么宽容,是因为函数在定义成功后,里面就生成了自动生成了一个对象:arguments。这个对象他长得有点像数组,只是像而已哦,他可以通过方括号语法访问里面的每一个元素,并用length属性判断里面到底传了几个元素。 

arguments这个对象保存的是传给这个函数的所有参数。为什么说他长得像数组呢,是因为访问这个对象内的属性(也就是传来的参数),是通过arguments[index]来访问的,方括号里面装的是index序列。所以第一个参数放在索引0处,使用arguments[0]可以引用他。

let test=function(no1,no2){
    console.log(typeof arguments[0]);
    console.log(arguments[1]);
}
var arr=[1,2,3];
var str="for-fun";
test(arr,str)//object  for-fun 

arguments的意义在于他可以模仿重载。重(zhong,四声)载的意思就是一个函数,传递不同的参数,可以执行不同的方法。而JS是没有重载的。

二、函数对象

函数实际上是对象,每个函数都是Function类型的实例。既然函数式对象,那么函数名实际上就是一个指向函数对象的指针,不会与某个函数绑定,一句话概括:“函数是对象,函数名是指针”。也正因此,一个函数,可以有多个不同的函数名。

例如:

function sum(num1,num2){
   return(num1+num2);
}
var add=sum;
console.log(add(19,2),typeof add);//21,function

一个函数可以有多个函数名,但是一个指针却不能指向多个函数,这也就是为什么JS没有重载的原因了。

 三、函数的内部属性对象this

this引用的函数执行环境的对象,当在网页全局作用域中调用函数时,this对象引用的是window。查看下面代码:

var color="red";
var o={
    color:"blue"
}

function sayColor(){
    console.log(this.color);
}

sayColor();//undefined,这个this指向的是window
o.sayColor=sayColor;//必须让这个对象绑定该方法
o.sayColor();//blue

 由于函数的名字只是一个包含指针的变量,因此即使在不同的环境中执行函数,全局的sayColor()函数与o.sayColor()函数指向的仍然是同一个函数。

  四、函数的属性

由于函数是对象,所以函数必然有它的属性与方法。

函数的属性有两个,length和prototype。length是函数希望接受到的参数的个数,prototype是在创建自定义引用类型和实现继承的时候使用的,不多赘述。

function sum(num1,num2){
   return(num1+num2);
}
console.log(sum.length);//2

五、函数的方法

每个函数都包括两个非继承而来的方法:apply()和call()。这两个方法的用途是在特定的作用域中调用函数,就相当于设置函数体内this的值。

apply():

apply()接受两个参数,一个是作用域,一个是数组。前者是在其中运行函数的作用域,后者是参数数组。

后者这个数组,可以是Array实例,也可以是arguments对象。

function sum(num1,num2){
    console.log(num1+num2);
}
function callSum(num1,num2){
    return sum.apply(this,arguments);
}
callSum(1,5,3)//6

  -

 call():

call()接受两个参数,第一个是作用域,后面是其他传递进来的参数。与apply方法不同的是,在使用call方法的时候,传递给函数的参数必须逐一列举。

function sum(num1,num2){
   return(num1+num2);
}
function callSum(num1,num2){
    return sum.call(this,num1,num2);
}
console.log(callSum(3,6));//9

  -

当然, 传递参数并不是call()和apply()的主要功能,他们最主要的作用还是扩充作用域

global.color="red";
var obj={
    color:"blue",
}
function sayColor(){
    console.log(this.color);
}
sayColor();//red
sayColor.call(obj);//blue

上面这段代码第二个sayColor,由于函数的执行对象变了,所以this就指向了obj。使用call和apply扩展作用域最好的优势,在于对象与方法不需要有任何的耦合关系。像下面这段之前的代码,先把方法传入到对象中,然后再用对象来调用方法,这就将方法与对象紧紧地耦合在了一起。

global.color="red";
var obj={
    color:"blue",
}
function sayColor(){
    console.log(this.color);
}
sayColor();//red
obj.sayColor=sayColor;
obj.sayColor();//blue

六、bind() 

ES5还定义了一个bind()方法,这个方法会创建一个函数的实例,this值会被绑定给传给bind()函数的值。

global.color="red";
var obj={
    color:"blue",
}
function sayColor(){
    console.log(this.color);
}
var objSaycolor=sayColor.bind(obj);
objSaycolor();//blue

上面这段代码中,sayColor方法调用bind()传入了对象obj,创建了objSayColor函数,因此该函数的this指向obj。

七、函数绑定

var handle={
    message:"this is handle",
    handleClick:function(){
        console.log(this.message);
    }
}

var btn=document.getElementById("my-btn");
EventUtil.addHandler(btn,"click",handle.handleClick);

上面这段代码,虽然看上去好像是点击按钮会输出“this is handle”,然而实际上,却是会输出“undefined”,这是因为this指向的是DOM按钮,而不是handle对象。

bind()方法接受一个函数和一个环境,返回一个在给定环境中调用给定函数的函数,并将所有参数原封不动传递过去。

var handle={
    message:"this is handle",
    handleClick:function(event){
        console.log(this.message);
    }
}

var btn=document.getElementById("my-btn");
EventUtil.addHandler(btn,"click",bind(handle.handleClick,handle));

 上面这段代码中,用bind()函数创建了一个保持了执行环境的函数,并将其传递给了EventUtil.addHandler(),event对象也被传递给了该函数。

七、原生bind()

ES5为所有的函数定义了一个原生bind()方法,因此可以直接在函数上调用这个方法。就像上面“六bind()”里的sayColor一样。

var handle={
    message:"this is handle",
    handleClick:function(){
        console.log(this.message);
    }
}

var btn=document.getElementById("my-btn");
EventUtil.addHandler(btn,"click",handle.handleClick.bind(handle));

如果某个函数指针是以“值”的方式进行传递,同时该函数必须在特定的环境中执行,函数绑定的作用就凸显出来了。他们主要用于事件处理程序已经setTimeout()和setInterval()。

 -

扩展:

this的指向是谁调用,就指向谁。对象里面的函数,要想拿出来使用,就必须要重新定义。

堆:先进先出。字符串、数字

栈:先进后出。数组、对象。 

函数是对象,函数名是指针。

原文地址:https://www.cnblogs.com/qingshanyici/p/10978092.html