首页 > 编程语言 >深入理解之JavaScript之call, apply, bind方法

深入理解之JavaScript之call, apply, bind方法

时间:2023-05-18 22:32:44浏览次数:33  
标签:console log bind JavaScript 参数 apply call

在JavaScript中,call、apply和bind是Function对象自带的三个方法,这三个方法的主要作用是改变函数执行时的上下文,再具体一点就是改变函数运行时的this指向

Function.prototype.call()

call() 方法调用一个函数, 其具有一个指定的 this 值和多个参数(参数的列表)。

fun.call(thisArg, arg1, arg2, ...)

thisArg的取值有以下4种情况:

  1. 不传,或者传null, undefined, 函数中的this指向window对象(非严格模式下)
  2. 传递另一个函数的函数名,函数中的this指向这个函数的引用
  3. 传递字符串、数值或布尔类型等基础类型,函数中的this指向其对应的包装对象,如 String、Number、Boolean
  4. 传递一个对象,函数中的this指向这个对象.

我们可以用如下代码验证一下:

function func1() {   
    console.log(this);   
}       

function func2() {}       

var obj = { name: "jacky" };  

func1.call();   // Window
func1.call(null);   // Window
func1.call(undefined);   // Window
func1.call(1);   // Number {1}
func1.call('');   // String {""}
func1.call(true);   // Boolean {true}
func1.call(func2);   // ƒ func2() {}
func1.call(obj);   // {name: "jacky"}

我们再来看个例子,理解怎么改变this的指向:

function Animal(){
    this.name = 'animal';
    this.sayName = function(){
        console.log(this.name);
    }
}

function Cat(){
    this.name = 'cat';
}

var animal = new Animal();
var cat = new Cat();

animal.sayName.call(cat); // cat
// this 永远指向最后调用它的那个对象
// 该例子中sayName方法的this指向Cat

Function.prototype.apply()

call和apply的第一个参数都是要改变上下文的对象,而call从第二个参数开始以参数列表的形式展现,apply则是把除了改变上下文对象的参数放在一个数组里面作为它的第二个参数,他们俩之间的差别在于参数的区别。

如下代码:

function Animal(...args) {
    console.log(`${this.name} 和其他参数 ${args}`); 
};

let cat = {
    name: 'xiaomao'
};

// 1. 使用 call
Animal.call(cat, 1, 2, 3); // xiaomao 和其他参数 1,2,3

// 2. 使用 apply
Animal.apply(cat, [1, 2, 3]); // xiaomao 和其他参数 1,2,3

call、apply使用

由于两个方法实际效果是一样的,对于两者平时用该如何选择呢?

  1. 参数数量/顺序确定就用call,参数数量/顺序不确定的话就用apply。
  2. 考虑可读性:参数数量不多就用call,参数数量比较多的话,把参数整合成数组,使用apply。
  3. 参数集合已经是一个数组的情况,用apply。

call, apply 的应用

1. 数组拼接

let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];

// 用 apply方法
[].push.apply(arr1, arr2);  // 给arr1添加arr2
console.log(arr1); // [1, 2, 3, 4, 5, 6]

2. 获取数组中的最大值或最小值

var numbers = [1, 4, 6, 2, 3, 100, 98]; 
console.log( Math.max.apply(Math, numbers) );   // 100
console.log( Math.max.call(Math, 1, 4, 6, 2, 3, 100, 98) ); // 100
console.log( Math.min.call(Math, ...numbers) ); // 1

3. 判断数据类型

let arr = [1, 2, 3, 4];
let str = 'string',
    obj = { name: 'jacky' }
// 判断传入参数是否为数组
function isArray(obj) {
    return Object.prototype.toString.call(obj) === '[object Array]';
}
console.log(isArray(arr)); // true
console.log(isArray(str)); // false
console.log(Object.prototype.toString.call(arr)); // [object Array]
console.log(Object.prototype.toString.call(str)); // [object String]
console.log(Object.prototype.toString.call(obj)); // [object Object]
console.log(Object.prototype.toString.call(null)); // [object Null]

4. 调用父类构造函数实现继承

function Animal(name){      
    this.name = name;      
    this.showName = function(){      
        console.log(this.name);      
    }      
}      

function Cat(name){    
    Animal.call(this, name);    
}      

var cat = new Cat("xiaomao");     
cat.showName();   // xiaomao

缺点:

  1. 只能继承父类的实例属性和方法,不能继承原型属性/方法
  2. 每次子类实例化都要执行父类函数,重新声明父类this里所定义的方法,因此父类方法无法复用。

5. 类数组对象转数组

function func() {
    var args = [].slice.call(arguments);
    console.log(args);
}
func(1, 2, 3); // [1, 2, 3]

还有像调用 getElementsByTagName , document.childNodes 之类的,它们返回NodeList对象都属于伪数组。

6. 代理console.log方法

注意这里传入多少个参数是不确定的,所以使用apply是最好的。

function log(){
    console.log.apply(console, arguments);
};
log(1);    // 1
log(1, 2, 3);    // 1 2 3

Function.prototype.bind()

MDN的解释是:

bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。

说直白一点,bind方法是创建一个函数,然后可以在需要调用的时候再执行函数,并非是立即执行函数;而call,apply是在改变了上下文中的this指向后并立即执行函数。

bind 接收的参数类型与 call 是一样的,给它传递的是一组用逗号分隔的参数列表。

var bar = function(){   
    console.log(this.x);   
}
var foo = { 
    x: 2   
}   
bar(); // undefined

var func = bar.bind(foo);   
func(); // 2

bind的应用

bind绑定回调函数的this指向

class PageA {
    constructor(callBack) {
        this.className = 'PageA'
        this.MessageCallBack = callBack 
        this.MessageCallBack('执行了')
    }
}
class PageB {
    constructor() {
        this.className = 'PageB'
        this.pageClass = new PageA(this.handleMessage)
    }
    // 回调函数
    handleMessage(msg) {
        console.log('处理' + this.className + '的回调 ' + msg) // 处理PageA的回调 执行了
    }
}
new PageB();

运行上面的代码,我们发现回调函数this丢失了?问题出在这行代码

this.pageClass = new PageA(this.handleMessage)

传递过去的this.handleMessage是一个函数内存地址,没有上下文对象,也就是说该函数没有绑定它的this指向。

解决方案:用bind绑定this的指向

this.pageClass = new PageA(this.handleMessage.bind(this))

这也是为什么react的render函数在绑定回调函数的时候,也要使用bind绑定一下this的指向,也是因为同样的问题以及原理。

  • 多个bind的情况
var bar = function() {
    console.log(this.x);
}

var obj1 = {
    x: 2
}
var obj2 = {
    x: 3
}
var obj3 = {
    x: 4
}

var func1 = bar.bind(obj1).bind(obj2);
var func2 = bar.bind(obj1).bind(obj2).bind(obj3);
func1(); // 2
func2(); // 2

输出结果都为第一个绑定的obj对象的x值。原因是,在Javascript中,bind()方法返回的外来的绑定函数对象仅在创建的时候记忆上下文(如果提供了参数),多次 bind() 是无效的。更深层次的原因, bind() 的实现,相当于使用函数在内部包了一个 call / apply ,第二次 bind() 相当于再包住第一次 bind() ,故第二次以后的 bind 是无法生效的。

总结

call和apply的第一个参数都是要改变上下文的对象,而call从第二个参数开始以参数列表的形式展现; apply则是把除了改变上下文对象的参数放在一个数组里面作为它的第二个参数,他们俩之间的差别在于参数的区别。

call和apply改变了函数的this上下文后便执行该函数,而bind则是返回改变了上下文后的一个函数,其内的this指向为创建它时传入bind的第一个参数,而传入bind的第二个及以后的参数作为原函数的参数来调用原函数。bind的参数可以在函数执行的时候再次添加。

标签:console,log,bind,JavaScript,参数,apply,call
From: https://blog.51cto.com/u_16056437/6307414

相关文章

  • javascript小技巧(六)
    操作EXECL<scriptlanguage="javascript">functionjStartExcel(){varxls=newActiveXObject("Excel.Application");xls.visible=true;varnewBook=xls.Workbooks.Add;newBook.Worksheets.Add;newBook.Worksheets(1).Activa......
  • Javascript 常见的循环方式总结
    本文地址:https://www.cnblogs.com/zichliang/p/17412968.html在Javascript中有很多种循环方式。有多种循环方式可以用来遍历数组、对象、以及执行一些重复性任务但是有很多方式甚至从未用过,所以简单来总结一下JS中的循环for循环for循环是在您希望创建循环时经常使用的工具。......
  • JavaScript全解析——Ajax是什么(上)
    AJAX是AsynchronousJavaScriptAndXML的缩写。它不是一种编程语言。它是一种基于HTML、CSS、JavaScript和XML,让开发更好、更快和更有互动的Web应用的技术。什么是ajax认识前后端交互前后端交互就是前端与后端的一种通讯方式,主要使用的技术栈就是ajax(asyncjavascript......
  • Custom elements in iteration require 'v-bind:key' directives.
    Customelementsiniterationrequire'v-bind:key'directives.这个错误提示"Customelementsiniterationrequire'v-bind:key'directives"的意思是在循环中使用自定义元素时,需要为每个元素添加v-bind:key指令。......
  • javascript创建数组
    javascript数组:vararray=[]等于创建一个数组array[0]代表给数组的第一个位置上赋值,值为32array[5]代表给数组的第六位置上赋值。值为3在位置0,1,2,5位置上都赋值,维度3,4位置上未赋值,但有一栏占位,所以会导致array.lenth获取数组的长度为6dor(vari=0;<array.lenth;i++){}代表的......
  • JavaScript中变量类型间的转化
    转到数值字符串布尔nullundefined数值Number()parsenInt()-0,/1,*1Number(true)→1Number(false)→0Number(null)→0Number(undefind)→NaN字符串String()toString()+""String(true)→trueString(false)→falseError:null.toString()Error:undefined.......
  • 深入理解JavaScript之作用域链与闭包
    作用域作用域是指程序源代码中定义变量的区域。实际上描述的就是查找变量的范围,作用域必须有的两个功能就是存储变量以及查找变量,作用域就是发挥这两个作用以及更多作用的规则。作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。词法作用域和动态作用域词法作用域......
  • 第13章 使用Bind提供域名解析服务。 dns 正向反向解析 主从 dns加密传
    章节简述: 本章讲解了DNS域名解析服务的原理以及作用,介绍了域名查询功能中正向解析与反向解析的作用,并通过实验的方式演示了如何在DNS主服务器上部署正、反解析工作模式,以便让大家深刻体会到DNS域名查询的便利以及强大。本章还介绍了如何部署DNS从服务器以及DNS缓存服务器来提......
  • JavaScript 使用一个数组对另一个对象数组进行过滤
    JavaScript使用一个数组对另一个对象数组进行过滤假设我们有一个对象数组objs,其中每个对象都有一个name属性,我们希望使用一个数组names对objs数组进行过滤,只保留那些name属性在names数组中的对象。我们可以使用filter()方法来实现这个功能。constobjs=[{id......
  • 学习日记——初识JavaScript
    1.JS的组成和基本结构①JavaScript定义:(1)脚本语言(2)有一定的安全性(3)一种基于对象的一种语言(4)可以定义一堆的事件(方法/函数)并进行调用②组成部分: (1)EcmaScript:核心语法。Js前身(2)Dom(文档对象模型)(3)Bom(浏览器对象模型)③JavaScript基本结构<scripttype="text/javascript">alert();......