JavaScript中的call,apply,bind区别及应用(包含手写call/apply/bind)
目录
一、使用目的
二、三者分别是如何定义的及区别(摘自MDN)
三、在程序中收获
四、三者的具体应用
四、手写bind,apply,call
今天在读程序题的时候,遇到call,apply,bind的使用。
const person = { name: 'Lydia' }function sayHi(age) {console.log(`${this.name} is ${age}`)
}sayHi.call(person, 21)
sayHi.bind(person, 21)
一、使用目的
三者都是用来改变this的指向。
二、三者分别是如何定义的及区别(摘自MDN)
apply()
方法调用一个具有给定this
值的函数,以及作为一个数组(或类似数组对象)提供的参数。
MDN关于apply详细介绍:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply
注意:call()方法的作用和 apply() 方法类似,区别就是call()
方法接受的是参数列表,而apply()
方法接受的是一个参数数组。
bind()
方法创建一个新的函数,在调用时设置this
关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。
MDN关于bind详细介绍:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
call()
方法使用一个指定的 this
值和单独给出的一个或多个参数来调用一个函数。
MDN关于call详细介绍:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call
注意:该方法的语法和作用与 apply()
方法类似,只有一个区别,就是 call()
方法接受的是一个参数列表,而 apply()
方法接受的是一个包含多个参数的数组。
call()改过this的指向后,会再执行函数,bind()改过this后,不执行函数,会返回一个绑定新this的函数function f(){
console.log("看我怎么被调用");
console.log(this) //指向this
}
var obj = {};
f.call(obj) //直接调用函数
var g = f.bind(obj); //bind()不能调用函数
g(); //此时才调用函数
三、在程序中收获
<script>var name = 'Lily',age = 13var obj = {name: 'Sam',objAge: this.age,myFunc: function (param1, param2) {console.log(this.name + ' ' + this.objAge + ' ' + this.age + ' ' + param1 + ' ' + param2)// Sam undefined undefined 1 2}}obj.myFunc(1, 2)
</script>
非常明确,在obj.myFunc()中,通过this.age是获取不到定义在window中的age值,因为在该方法中,this指向的是obj对象。因此值为undefined,这个时候,可以通过call,apply,bind来更改this的指向,从而获取值。
<script>var name = 'Lily',age = 13var obj = {name: 'Sam',objAge: this.age,myFunc: function (param1, param2) {console.log(this.name + ' ' + this.objAge + ' ' + this.age + ' ' + param1 + ' ' + param2)// Amy 20 21 1 2}}var db = {name: 'Amy',age: 21,objAge: 20}obj.myFunc.call(db, '1', '2') // db为this要指向的对象,后面传入的是参数列表,参数可以是任意类型,当第一个参数为null、undefined的时候,默认指向window;obj.myFunc.apply(db, [1, 2]) // db为this要指向的对象,参数必须放在一个数组里面;obj.myFunc.bind(db, '1', '2')() // db为this要指向的对象,返回的是一个新函数,必须调用才会去执行。
</script>
/*定义一个人类*/ function Person(name,age) { this.name=name; this.age=age; } /*定义一个学生类*/ function Student(name,age,grade) { Person.apply(this,arguments); this.grade=grade; } //创建一个学生类 var student=new Student("zhangsan",21,"一年级"); //测试 alert("name:"+student.name+"\n"+"age:"+student.age+"\n"+"grade:"+student.grade); //大家可以看到测试结果name:zhangsan age:21 grade:一年级 //学生类里面我没有给name和age属性赋值啊,为什么又存在这两个属性的值呢,这个就是apply的神奇之处.
Person.apply(this,arguments);
this:在创建对象在这个时候代表的是student
arguments:是一个数组,也就是[“zhangsan”,”21”,”一年级”];
就是通俗一点讲就是:用student去执行Person这个类里面的内容,在Person这个类里面存在this.name等之类的语句,这样就将属性创建到了student对象里面
call示例
在Studen函数里面可以将apply中修改成如下:
Person.call(this,name,age);
四、三者的具体应用
转自:https://blog.csdn.net/wyyandyou_6/article/details/81488103
(1)利用call()判断数据类型
在判断数据类形式使用typeof,一般不是太准确的,我们可以使用Object.prototype.toString.call()方法来判断一个数据的数据类型console.log(Object.prototype.toString.call("qq")) // [Object String] 返回值都是字符串类型
console.log(Object.prototype.toString.call(12)) // [object Number]
console.log(Object.prototype.toString.call(false)) // [object Boolean]
console.log(Object.prototype.toString.call(undefined)) // [object Undefined]
console.log(Object.prototype.toString.call(null)) // [object Null]
console.log(Object.prototype.toString.call(function(){})) // [object Function]
console.log(Object.prototype.toString.call([])) // [object Array]
console.log(Object.prototype.toString.call({})) // [object Object]
(2)利用call()翻转字符串
var str = "abcdefg";
var rs = Array.prototype.reverse.call(str.split("")).join("");
console.log(rs) // gfedcba
(3) 利用apply()求最大值
var arr =[2,6,8,3,4,9,7,23,56,889];
console.log(Math.max.apply(arr,arr)) //第一个arr表示让arr借用max这个方法,第二个arr表示传给max的数据//apply()所执行的操作:1.执行Math.max(1,2,3,5,4) 2.把内部的this改成arr
(4)用 apply
将数组添加到另一个数组
我们可以使用push
将元素追加到数组中。并且,因为push接受可变数量的参数,我们也可以一次推送多个元素。但是,如果我们传递一个数组来推送,它实际上会将该数组作为单个元素添加,而不是单独添加元素,因此我们最终得到一个数组内的数组。如果那不是我们想要的怎么办?在这种情况下,concat
确实具有我们想要的行为,但它实际上并不附加到现有数组,而是创建并返回一个新数组。 但是我们想要附加到我们现有的阵列......那么现在呢? 写一个循环?当然不是吗?
var array = ['a', 'b'];
var elements = [0, 1, 2];
array.push.apply(array, elements);
console.info(array); // ["a", "b", 0, 1, 2]
最后回归最初的读程序,结果是
Lydia is 21
function
undefined
使用这两种方法,我们都可以传递我们希望 this
关键字引用的对象。但是,.call
是立即执行的。
.bind
返回函数的副本,但带有绑定上下文!它不是立即执行的。
四、手写bind,apply,call
https://juejin.im/post/5d2ddd9be51d4556d86c7b79
实现apply
先来实现apply吧
- 先给Function原型上扩展个方法并接收2个参数,
Function.prototype.myApply = function (context, args) {}
复制代码
- 因为不传context的话,this会指向window的,args也做一下容错处理
Function.prototype.myApply = function (context, args) {//这里默认不传就是给window,也可以用es6给参数设置默认参数context = context || windowargs = args ? args : []
}
复制代码
- 需要回想一下绑定this的五种方式,现在要来给调用的函数绑定this了, 这里默认绑定和new肯定用不了,这里就使用隐式绑定去实现显式绑定了
Function.prototype.myApply = function (context, args) {//这里默认不传就是给window,也可以用es6给参数设置默认参数context = context || windowargs = args ? args : []//给context新增一个独一无二的属性以免覆盖原有属性const key = Symbol()context[key] = this//通过隐式绑定的方式调用函数context[key](...args)
}
复制代码
- 最后一步要返回函数调用的返回值,并且把context上的属性删了才不会造成影响
Function.prototype.myApply = function (context, args) {//这里默认不传就是给window,也可以用es6给参数设置默认参数context = context || windowargs = args ? args : []//给context新增一个独一无二的属性以免覆盖原有属性const key = Symbol()context[key] = this//通过隐式绑定的方式调用函数const result = context[key](...args)//删除添加的属性delete context[key]//返回函数调用的返回值return result
}
这样一个简单的apply就实现了,可能会有一些边界问题和错误判断需要完善,这里就不做继续优化了
既然apply实现了,那么call同样也非常简单了,主要就是传参不一样
实现call
这里就直接上代码吧
//传递参数从一个数组变成逐个传参了,不用...扩展运算符的也可以用arguments代替
Function.prototype.myCall = function (context, ...args) {//这里默认不传就是给window,也可以用es6给参数设置默认参数context = context || windowargs = args ? args : []//给context新增一个独一无二的属性以免覆盖原有属性const key = Symbol()context[key] = this//通过隐式绑定的方式调用函数const result = context[key](...args)//删除添加的属性delete context[key]//返回函数调用的返回值return result
}
实现bind:https://blog.csdn.net/q3254421/article/details/82999718
该文章有详细解释:https://github.com/mqyqingfeng/Blog/issues/12
// 定义这个方法为myBind
Function.prototype.myBind = function(thisArg) {if (typeof this !== 'function') {return;}var _self = this;var args = Array.prototype.slice.call(arguments, 1) //从第二个参数截取return function() {return _self.apply(thisArg, args.concat(Array.prototype.slice.call(arguments))); // 注意参数的处理}
}
bind和apply的区别在于,bind是返回一个绑定好的函数,apply是直接调用.其实想一想实现也很简单,就是返回一个函数,里面执行了apply上述的操作而已.不过有一个需要判断的点,因为返回新的函数,要考虑到使用new去调用,并且new的优先级比较高,所以需要判断new的调用,还有一个特点就是bind调用的时候可以传参,调用之后生成的新的函数也可以传参,效果是一样的,所以这一块也要做处理 因为上面已经实现了apply,这里就借用一下,实际上不借用就是把代码copy过来
Function.prototype.myBind = function (context, ...args) {const fn = thisargs = args ? args : []return function newFn(...newFnArgs) {if (this instanceof newFn) {return new fn(...args, ...newFnArgs)}return fn.apply(context, [...args,...newFnArgs])}
}
以上所有实现可以再加点判断啊,例如调用的不是function就返回或者抛出错误啊之类的.我这里就不处理了
以上就是apply,call,bind的实现了。
JavaScript中的call,apply,bind区别及应用(包含手写call/apply/bind)相关推荐
- javascript中children和childNodes的区别
javascript中children和childNodes的区别 1.childNodes:它是标准属性,它返回指定元素的子元素集合,包括HTML节点,所有属性,文本节点. 可以通过nodeType ...
- JavaScript 中 Property 和 Attribute 的区别
转自:http://www.cnblogs.com/elcarim5efil/p/4698980.html property 和 attribute非常容易混淆,两个单词的中文翻译也都非常相近(pro ...
- JavaScript中eval()和$.parseJSON()的区别和联系以及JSON.stringify()的区别
JavaScript中eval()和$.parseJSON()的区别和联系以及JSON.stringify()的区别 一.第一个区别是:安全性 json格式非常受欢迎,而解析json的方式通常用JSO ...
- 【学习笔记】Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程、手写 Promise(二、JavaScript 异步编程)
[学习笔记]Part1·JavaScript·深度剖析-函数式编程与 JS 异步编程.手写 Promise(课前准备) [学习笔记]Part1·JavaScript·深度剖析-函数式编程与 JS 异步 ...
- JavaScript中的nodeName nodeType nodeValue区别
在JavaScript中,存在有nodeName .nodeType. nodeValue这三个属性,今天我们来了解下JavaScript中的nodeName .nodeType .nodeValue ...
- javascript中基本类型和引用类型的区别分析
大多数人系统学习过的程序设计语言,在这些语言的学习过程中最早学到的几个要点之一就是值类型和引用类型的区别.下面我们来看一下在 JavaScript 中基本数据类型(Primitive Types)和引 ...
- javascript中实例方法与类方法的区别
在javascript中,类有静态属性和实例属性之分,也有静态方法和实例方法之分 类属性(静态属性):通过类直接访问,不需要声明类的实例来访问 类方法(静态方法):通过类直接访问,不需要声明类的实例来 ...
- 手写bind_深入理解 JavaScript 之手写 call, apply, bind 方法
这是老生常谈的手写了,今天想自己试着实现一下,做个笔记. call 方法 Function.prototype.myCall = function (context) { if (context == ...
- jQuery中的read 和JavaScript中 的onload函数的区别
2019独角兽企业重金招聘Python工程师标准>>> 在JavaScript中,onload函数是最经常用到的,这个函数的作用是等待网页完全加载之后再去执行代码块中的语句,因为按照 ...
最新文章
- iOS UITextView 随键盘弹出界面上移
- 数组、链表、Hash(转)
- 模拟实现unordered_mapunordered_set
- 移动通信考前预习_第2章_蜂窝技术
- centos7.3部署django用uwsgi和nginx[亲测可用]
- diskData磁盘数据分析
- (转)对D3DXVec3Project 和D3DXVec3UnProject的认识
- 感染暴风一号u盘病毒的解决办法
- C++for循环经典九九乘法表打印
- Installation failed due to: ‘null‘
- Python计算细胞核与细胞质的面积比opencv或pil实验
- RT-thread Nano移植并实现多线程任务
- 计算机无法切换用户权限,“无法更改的Administrator账户密码”的解决方案
- 【比赛报告】2018.10.11校赛[8-2情人节欢乐赛] NOIP练习赛卷十二
- Youtube 预装到系统后运行报错
- 电阻接地再串联一个电容,电阻和电容并联
- 图解TCP/IP之半双工全双工通信
- python练习15:利用条件运算符的嵌套来完成此题:学习成绩>=90分的同学用A表示,60-89分之间的用B表示,60分以下的用C表示。
- 软件架构设计总结和理解
- Dashboard是什么意思 Dashboard怎么用?