call(),apply(),bind()
函数大家可能都有所了解,但是在平时搬砖过程中很可能或者基本没用过,学过但都淡忘了。
但是在大量第三方的框架(库),甚至js自己都在 源码中大量使用 call,apply
函数。所以今天和大家仔细讨论下它们在 开发中的应用场景 。
1 . 它们是啥意思
1.1 作用
- 他们的作用都是改变函数内部的
this
。 - 这三个函数都是
函数对象
的方法,也就是说只有函数才可以直接调用这些方法。
ps:call,apply,bing属于this显示绑定,还有好几种其他的this绑定方式,。
1.2 三者区别
- 参数: 三个函数的第一个参数都是需要绑定的
this
。
call
: 可以有n个参数,从第二个参数开始的所有参数都是原函数的参数。
`apply`:只有两个参数,并且第二个参数必须为数组,数组中的所有元素一一对应原函数的参数。`bind`: 只有一个参数,即要绑定的this。
call 语法: foo.call(this, arg1,arg2, ... ,argn ); apply 语法: foo.apply(this, [ arg1,arg2, ... ,argn ] ); bind 语法: foo.bind(this);
- 调用:
call,apply
: 调用后立即执行原函数。
`bind`: 调用后返回已经绑定好this的函数。
小例子一枚:
function foo(a,b){ console.log(a+b); } foo.call(null,'海洋','饼干'); // 海洋饼干 这里this指向不重要就写null了 foo.apply(null, ['海洋','饼干'] ); // 海洋饼干 var fun = foo.bind(null); fun('海洋','饼干'); // 海洋饼干
2 .它们能干啥事
这是我们今天讨论的主题,这三个函数如何应用?什么情况下使用?能改变this
指向又能咋滴?
2 .1 处理伪数组 (最常用)
先考虑一个问题,如果你使用var arr = document.getElementsByTagName('li')
获取了5个li
元素,你现在需要获取其中的第2,3,4三个元素,你会怎么做?
这样arr.slice(1,4);
? 啊哦,TypeError -- arr.slice is not a function(slice不是函数)
,数组操作在日常搬砖中非常常见,我见过最傻的解决这个问题的方式是使用循环,将需要的元素一个个添加到一个新数组里0.0,下面我介绍的方法完全可以在实战中使用,可以给你的代码加分哦,非常方便简洁(中高级前端程序员中,算是基本操作了)。
先要介绍一个概念( 伪数组 ),这也是为什么我们刚刚slice切割数组时出错的原因: (对新手来说算是干货了,知道的可以跳过)
什么是伪数组?( 字面的意思已经呼之欲出了 )
- 有length属性
- 能按索引存储数据
- 能像遍历数组一样来遍历
- 不能使用数组的
push()、slice()
等方法
简单来说就是可以像数组一样操作的对象,但是没有数组的方法。
js中存在大量伪数组,如 :
1. function的arguments对象。2. getElementsByName(),getElementsByTagName(),childNodes/children 等方法的返回值。3. 还有比较常见的jquery,使用它获取的元素也是伪数组。
回到原来的问题,如何截取伪数组中的元素:伪数组没有这些方法,我们'借用'Array的slice不就行了
[].slice.call(arr,1,4); // 推荐写法
不想借用你可以直接给伪数组添加一个slice函数,如
arr.slice = [].slice;arr.slice(1,4);
当然,'借用' 更方便,直接添加会导致伪数组对象'污染'。
如果可以随意改变原对象,可以 直接将其转成真正的数组对象。
[].slice.call(arr);
2 .2 继承
继承方式多种多样,我们现在讨论的这种是其中很重要的一种实现方式,用call
实现 js 构造函数继承 。
- 单继承
function person(name){ this.name = name}function man(name){ this.age = '男'; person.call(this,name); // 继承 man}var me = new man('海洋饼干');console.log(me.name,me.age); // '海洋饼干' '男'
- 多继承
function person(name){ this.name = name}function man(name){ this.age = '男';}function manProgrammer(name){ this.girlfriend = null; person.call(this,name); // 继承 person man.call(this,name); // 继承 man}var me = new manProgrammer('海洋饼干');console.log(me.name,me.age,me.girlfriend); // '海洋饼干' '男' null
2 .3 this 硬绑定 --- bind
将一个对象强制且永久性绑定到函数的this上,使用call,apply或者其他的绑定方式都无法改变(除了new绑定,当然,可以手动撸一个new都无法改变的硬绑定)
直接看例子:
var fun ;var obj = { a : 1, foo : function(){ var _this = this; //平时有没有过这种写法? 为了防止this指向问题 //将this赋值给一个变量,间接维持了this的安全性 fun = function(){ console.log(_this.a); } }}obj.foo();fun(); // 1var obj1 = { a : 2}obj.foo.call(obj1); // 直接修改_this所绑定的值,boom了fun(); // 2
但是这种方法感觉上是在逃避问题,直接不使用this了 ? 这真的不是什么好的解决问题的态度。下面使用我们的bind来优化一下:
var fun ;var obj = { a : 1, foo : function(){ // 不使用 _this, 避免无谓的变量声明 fun = function(){ console.log(this.a); }.bind(this); // 代码很简洁,很漂亮(b格) }}var obj1 = { a : 2}obj.foo();fun(); // 1fun.call(obj1); // 1 call ,apply等绑定 无法修改 // 这里和上面call的位置不同是因为this所处于不同的位置
这样替代 _this
很规(zhuang)范(b)呢
ps:call,apply,bing属于this显示绑定,还有好几种其他的this绑定方式,。
2 .4 取数组最大最小值
Math.max和min方法,接收多个参数,比较出极值,这里用到apply的一个默认功能:展开数组,传入一个数组参数就可以默认将这个数组转成一个个参数的形式赋给原函数
var num = [6,9,-3,-5];console.log(Math.max.apply(Math,num)); // 9 等价 console.log(Math.max(6,9,-3,-5));console.log(Math.min.apply(Math,num)); // -5 等价 console.log(Math.min(6,9,-3,-5));
2 .5 合并数组
合并数组常见有三种方式,1.循环 2.Array的concat() 3. 使用apply()合并
这里是使用最简便的apply
var a = [1,2,3];var b = [4,5,6];[].push.apply(a,b); // 借用数组的push方法 等价 a.push(4,5,6);console.log(a); // [1, 2, 3, 4, 5, 6]
最后
这里推荐一下我的前端学习交流裙:731771211,里面都是学习前端的,群里会不定期更新最新的教程和学习方法,有想学习web前端的,或是转行,或是大学生,还有工作中想提升自己能力的web前端党欢迎加入。