[JS]JS中call/apply/bind方法总结

来源:互联网 发布:mysql 重启 编辑:程序博客网 时间:2024/05/20 12:24
JavaScript的一大特点是,函数存在「定义时上下文」和「运行时上下文」以及「上下文是可以改变的」这样的概念。
JavaScript中,call和apply都是为了改变某个函数运行时的上下文(context)而存在的,换句话说,就是为了改变函数体内部this的指向。bind方法用于指定函数内部的this指向(执行时所在的作用域),然后返回一个新函数。bind方法并非立即执行一个函数。

call/apply/bind常常用于继承和回调函数(指定函数的执行作用域)。

在常见的单体模式中,通常我们会使用 _this , that , self 等保存 this ,这样我们可以在改变了上下文之后继续引用到它。

call/apply/bind方法的来源

首先,在使用call,apply,bind方法时,我们有必要知道这三个方法究竟是来自哪里?为什么可以使用的到这三个方法?
call,apply,bind这三个方法其实都是继承自Function.prototype中的,属于实例方法。
console.log(Function.prototype.hasOwnProperty('call')) //trueconsole.log(Function.prototype.hasOwnProperty('apply')) //trueconsole.log(Function.prototype.hasOwnProperty('bind')) //true
上面代码中,都返回了true,表明三种方法都是继承自Function.prototype的。当然,普通的对象,函数,数组都继承了Function.prototype对象中的三个方法,所以这三个方法都可以在对象,数组,函数中使用。

Function.prototype.call()

函数实例的call方法,可以指定该函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。并且会立即执行该函数。
call方法的一个应用是调用对象的原生方法。也可以用于将类数组对象转换为数组。
var obj = {};console.log(obj.hasOwnProperty('toString')); //falseobj.hasOwnProperty = function() {    return true;}console.log(obj.hasOwnProperty('toString')); //trueconsole.log(Object.prototype.hasOwnProperty.call(obj, 'toString')); //false

上面代码中,hasOwnProperty是obj对象继承的方法,如果这个方法一旦被覆盖,就不会得到正确结果。call方法可以解决这个方法,它将hasOwnProperty方法的原始定义放到obj对象上执行,这样无论obj上有没有同名方法,都不会影响结果。要注意的是,hasOwnProperty是Object.prototype原生对象的方法,而call是继承自Function.prototype的方法。

继承

function Product(name, price) {  this.name = name;  this.price = price;}function Food(name, price) {  Product.call(this, name, price);   this.category = 'food';}//等同于function Food(name, price) {     this.name = name;    this.price = price;    this.category = 'food'; }//function Toy 同上function Toy(name, price) {  Product.call(this, name, price);  this.category = 'toy';}var feta = new Food('feta', 5);var robot = new Toy('robot', 40);console.log(feta);//Food { name: 'feta', price: 5, category: 'food' }console.log(robot);//Toy { name: 'robot', price: 40, category: 'toy' }
多重继承

var s1 = function(name){  this.name = name;}var s2 = function(sex){  this.sex = sex;}var s3 = function(age){  this.age = age;}var Student = function(name,sex,age,score){  s1.call(this,name);  s2.call(this,sex);  s3.call(this,age);  this.score = score;}Student.prototype.construction = Student;var s = new Student('jack','male','12','100');console.log(s.name); //输出:jackconsole.log(s.sex);  //输出:male console.log(s.age);  //输出:12console.log(s.score);//输出:100console.log(s instanceof Student);//输出:true

Function.prototype.apply()

apply方法的作用与call方法类似,也是改变this指向(函数执行时所在的作用域),然后在指定的作用域中,调用该函数。同时也会立即执行该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数。
对于apply、call 二者而言,作用完全一样,只是接受参数的方式不太一样
function keith(a, b) {    console.log(a + b);}keith.call(null, 2, 3); //5keith.apply(null, [2, 3]); //5
1、apply的应用有--数组之间追加
var array1 = [12 , "foo" , {name:"Joe"} , -2458];var array2 = ["Doe" , 555 , 100];Array.prototype.push.apply(array1, array2);console.log(array1) //[ 12, 'foo', { name: 'Joe' }, -2458, 'Doe', 555, 100 ]console.log(array2) //[ 'Doe', 555, 100 ]
2、apply的应用有--获取数组中的最大值和最小值
var numbers = [5, 458 , 120 , -215 ];var maxInNumbers = Math.max.apply(Math, numbers);   console.log(maxInNumbers) //458maxInNumbers = Math.max.call(Math,5, 458 , 120 , -215);console.log(maxInNumbers) //458
Javascript中是没有提供找出数组中最大值的方法的,结合使用继承自Function.prototype的apply和Math.max方法,就可以返回数组的最大值。

3.apply的应用有--将数组的空元素变为undefined
通过apply方法,利用Array构造函数将数组的空元素变成undefined。
console.log(Array.apply(null, [1, , 3])); // [1, undefined, 3]
空元素与undefined的差别在于,数组的forEach方法会跳过空元素,但是不会跳过undefined和null。因此,遍历内部元素的时候,会得到不同的结果。
var a = [1, , 3];a.forEach(function(index) {    console.log(index); //1,3 ,跳过了空元素。})Array.apply(null,a).forEach(function(index){    console.log(index);    ////1,undefined,3  ,将空元素设置为undefined})
Function.prototype.bind()
bind方法用于指定函数内部的this指向(执行时所在的作用域),然后返回一个新函数。bind方法并非立即执行一个函数。
var keith = {    a: 1,    count: function() {        console.log(this.a++);    }};var f = keith.count.bind(keith);f(); //1f(); //2var obj = {a : 5}f.call(obj);//3,说明bind是作用于执行时的上下文,而不是定义是的上下文
var bar = function(){    console.log(this.x);}var foo = {    x:3}var sed = {    x:4}var func = bar.bind(foo).bind(sed);func(); //? var fiv = {    x:5}var func = bar.bind(foo).bind(sed).bind(fiv);func(); //?

答案是,两次都仍将输出 3 ,而非期待中的 4 和 5 。原因是,在Javascript中,多次 bind() 是无效的。更深层次的原因, bind() 的实现,相当于使用函数在内部包了一个 call / apply ,第二次 bind() 相当于在包中再包了第一次 bind() ,故第二次以后的 bind 是无法生效的。

function f(a,b,c){    console.log(a,b,c);}var f_Extend = f.bind(null,"extend_A")f("A","B","C")  //--> A B Cf_Extend("A","B","C")  //--> extend_A A Bf_Extend("B","C")  //--> extend_A B Cf.call(null,"extend_A") //--> extend_A undefined undefined
这个区别不是很好理解,call是把第二个及以后的参数作为f方法的实参传进去,而bind虽说也是获取第二个及以后的参数用于之后方法的执行,但是f_Extend中传入的实参则是在bind中传入参数的基础上往后排的。
//这句代码相当于以下的操作var f_Extend = f.bind(null,"extend_A")f_Extend(4,5);//extend_A 4 5//↓↓↓var f_Extend = function(b,c){    return f.call(null,"extend_A",b,c);}f_Extend(4,5);//extend_A 4 5

总结一下call/apply/bind方法

  • 第一个参数都是指定函数内部中this的指向(函数执行时所在的作用域),然后根据指定的作用域,调用该函数。
  • 都可以在函数调用时传递参数。call,bind方法需要直接传入,而apply方法需要以数组的形式传入。
  • call,apply方法是在调用之后立即执行函数,而bind方法没有立即执行,需要将函数再执行一遍。有点闭包的味道。
0 0
原创粉丝点击