call、apply、bind
- call、apply 直接调用函数,临时改变
- call
- 函数体内的
this指向call方法传入的第一个实参 - 而
call方法后续的实参会依次传入作为原函数的实参传入
- 函数体内的
- apply
- 调用时将剩余的实参以一个数组的方式传参
- apply与call功能一致
- bind
- 不会调用函数,返回新的函数
- 新函数里 this 会指向之前绑定的对象
call()
call方法使用的语法规则
- 参数:对象,指定调用时函数中this的指向
函数名称.call( obj,arg1,arg2...argN )
this指向call方法传入的第一个实参,后续的实参会依次传入作为原函数的实参传入
let lisi = { names: 'lisi' }
let zs = { names: 'zhangsan' }
function f(age) {
console.log(this.names)
console.log(age)
}
f(23) //undefined 23
//将f函数中的this指向固定到对象zs上;
f.call(zs, 32) //zhangsan 32
function setDetails(name, color) {
this.name = name
this.color = color
}
let cat1 = {}
let cat2 = {}
setDetails.call(cat1, '大毛', '橘色')
setDetails.call(cat2, '二毛', '黑色')
console.log(cat1.name) //大毛
console.log(cat2.name) //二毛
let person1 = {
name: 'zs',
say: function (hobby) {
console.log(this.name)
console.log('爱好:' + hobby)
},
}
let person2 = {
name: 'ls',
}
person1.say('打游戏') // zs 爱好:打游戏
person1.say.call(person2, '健身') // ls 爱好:健身
实现call
// 第三版
Function.prototype.call2 = function (context) {
var context = context || window
context.fn = this
let arg = [...arguments].slice(1)
let result = context.fn(...arg)
delete context.fn
return result
}
// 测试一下
var value = 2
var obj = {
value: 1,
}
function bar(name, age) {
console.log(this.value)
return {
value: this.value,
name: name,
age: age,
}
}
bar.call2(null) // 2
console.log(bar.call2(obj, 'kevin', 18))
// 1
// Object {
// value: 1,
// name: 'kevin',
// age: 18
// }
简化版
Function.prototype.call2 = function (context, ...args) {
// 判断是否是undefined和null
if (typeof context === 'undefined' || context === null) {
context = window
}
let fnSymbol = Symbol()
context[fnSymbol] = this
let fn = context[fnSymbol](...args)
delete context[fnSymbol]
return fn
}
过程
- 在原型链上挂上我们自定义的
call2方法,让所有函数共享此方法
Function.prototype.call2 = function () {
console.log(this)
}
setDetails.call2()
-
在
call2中通过this拿到调用call2的原函数 -
this指向点'.'前面的对象(被调用的函数)
Function.prototype.call2 = function (context) {
//this === 原函数
console.log(this)
//将原函数作为cat1的方法调用
context.setDetails = this
context.setDetails()
}
-
其实将原函数作为
context的方法调用时,方法名并不影响功能,将方法名写死反而可能会造成方法名冲突-
在ES6中可以用
Symbol来解决 -
可以随机生成一个基本不可能冲突的字符串,万一冲突则继续生成到不冲突为止
-
-
在方法调用后删除方法,避免给
context增加多余的方法 -
原函数可能有返回值,要将改变
this并调用后的返回值也返回
Function.prototype.call2 = function (context) {
function mySymbol(obj) {
let unique = (Math.random() + new Date())
if (obj.hasOwnProperty(unique)) {
return mySymbol(obj) //如果还是冲突,递归调用
} else {
return unique
}
}
let uniqueName = mySymbol(context)
//this === 原函数
//console.log(this);
//将原函数作为context的方法调用
context[uniqueName] = this
let result = context[uniqueName]()
//用完删除
delete context[uniqueName]
return result
}
-
改进解决参数传递
-
通过
arguments或者剩余参数来获取除了context剩余的参数 -
将剩余参数传递给
context[uniqueName]
-
Function.prototype.call2 = function (context) {
function mySymbol(obj) {
let unique = (Math.random() + new Date())
if (obj.hasOwnProperty(unique)) {
return mySymbol(obj) //如果还是冲突,递归调用
} else {
return unique
}
}
let uniqueName = mySymbol(context)
//获取除了第一个参数外剩余的参数
let args = Array.from(arguments).slice(1)
//this === 原函数
//将原函数作为cat1的方法调用
context[uniqueName] = this
//使用扩展运算符传参,可以解决参数不确定的问题
let result = context[uniqueName](...args)
//用完删除
delete context[uniqueName]
return result
}
- 将剩余参数传递给
context[uniqueName]还可以通过eval来拼接调用语句
Function.prototype.call2 = function (context) {
function mySymbol(obj) {
let unique = (Math.random() + new Date())
if (obj.hasOwnProperty(unique)) {
return mySymbol(obj) //如果还是冲突,递归调用
} else {
return unique
}
}
let uniqueName = mySymbol(context)
//this === 原函数
//console.log(this);
//获取除了第一个参数外剩余的参数
let args = [];
for(let i = 1; i < arguments.length; i++) {
args.push('arguments[' + i + ']');
}
//将原函数作为cat1的方法调用
context[uniqueName] = this
//使用扩展运算符传参,可以解决参数不确定的问题
let result = eval('context[uniqueName](' + args.join(',') + ')');
//用完删除
delete context[uniqueName]
return result
}
apply()方法
- 参数:数组
函数名称.apply(obj,[arg1,arg2...,argN])
let lisi = { name: 'lisi' }
let zs = { name: 'zhangsan' }
function f(age, sex) {
console.log(this.name + age + sex)
}
//将f函数中的this指向固定到对象zs上
f.apply(zs, [23, 'nan'])
实现apply
Function.prototype.apply = function (context, arr) {
var context = Object(context) || window
context.fn = this
var result
if (!arr) {
result = context.fn()
} else {
result = context.fn(...arr)
}
delete context.fn
return result
}
简化版
Function.prototype.apply2 = function(context, args) {
// 判断是否是undefined和null
if (typeof context === 'undefined' || context === null) {
context = window
}
let fnSymbol = Symbol()
context[fnSymbol] = this
let fn = context[fnSymbol](...args)
delete context[fnSymbol]
return fn
}
```
```javascript
function setDetails(name, color) {
this.name = name
this.color = color
}
let cat1 = {}
let cat2 = {}
setDetails.apply(cat1, ['大毛', '橘色'])
setDetails.apply(cat2, ['二毛', '黑色'])
console.log(cat1.name) //大毛
console.log(cat2.name) //二毛
过程
- 兼容
apply第二个参数没有传入的情况
Function.prototype.apply2 = function (context,args) {
function mySymbol(obj) {
let unique = (Math.random() + new Date())
if (obj.hasOwnProperty(unique)) {
return mySymbol(obj) //如果还是冲突,递归调用
} else {
return unique
}
}
let uniqueName = mySymbol(context)
//this === 原函数
//console.log(this)
args = args || [] //兼容没有传参的情况
//将原函数作为context的方法调用
context[uniqueName] = this
//使用扩展运算符传参,可以解决参数不确定的问题
context[uniqueName](...args)
//用完删除
delete context[uniqueName]
}
- 使用eval:将传入的字符串当做 JavaScript 代码进行执行
Function.prototype.apply2 = function (context, args) {
function mySymbol(obj) {
let unique = Math.random() + new Date()
if (obj.hasOwnProperty(unique)) {
return mySymbol(obj)
} else {
return unique
}
}
let uniqueName = mySymbol(context)
//let args = Array.from(arguments).slice(1)
let arr = []
for (let i = 0; i < args.length; i++) {
arr.push('args[' + i + ']')
}
context[uniqueName] = this
/* let result = context[uniqueName](args.join(','))
//等同于context[uniqueName]('大毛','橘色')
*/
let result = eval('context[uniqueName](' + arr.join(',') + ')')
//删除临时方法
delete context[uniqueName]
return result
}
bind
-
只做
this的绑定 -
不会立即调用函数
-
返回一个运行的逻辑与原函数一致的新函数
this会指向之前绑定的对象
function setDetails(name,color){
this.name = name
this.color = color
}
let cat1 = {}
let setDetails2 = setDetails.bind(cat1)
setDetails2('大毛','橘色')
实现bind
- 处理边界条件
Function.prototype.myBind = function (target) {
target = target || {} // 处理边界条件
return function () {} // 返回一个函数
}
- 给target添加一个方法,让方法中的this指向该target
Function.prototype.myBind = function (target) {
target = target || {} // 处理边界条件
const symbolKey = Symbol()
target[symbolKey] = this
return function () { // 返回一个函数
target[symbolKey]()
delete target[symbolKey]
}
}
- 传入参数的
const mbs = {
name: '山河',
say(prefix, age) {
console.log(`${prefix},my name is ${this.name},i am ${age} year old`)
}
}
mbs.say('hello',13) // 'hello,my name is 山河,i am 13 year old'
const B = {
name: '梳梳'
}
const sayB = mbs.say.bind(B,'hello')
sayB(8) // 'hello,my name is 梳梳,i am 8 year old''
- bind中传递的参数,和调用bind的返回函数时传入的参数,都传递到say方法中
Function.prototype.myBind = function (target,...outArgs) {
target = target || {} // 处理边界条件
const symbolKey = Symbol()
target[symbolKey] = this
return function (...innerArgs) { // 返回一个函数
const res = target[symbolKey](...outArgs, ...innerArgs) // outArgs和innerArgs都是一个数组,解构后传入函数
// delete target[symbolKey] 这里千万不能销毁绑定的函数,否则第二次调用的时候,就会出现问题
return res
}
}
偏函数应用程序:通过绑定现有函数的一些参数来创建一个新函数
- 实现两个函数,分别是对传入的数进行翻倍和翻三倍
const double = n => n * 2
const double2 = double(2) // 4
const double4 = double(4) // 8
...
const triple = n => n * 3
const triple2 = triple(2) // 6
const triple4 = triple(4) // 12
...
- 用偏函数的概念实现
const base = (n,m) => n * m
const double = base.bind(null,2)
const double2 = double(2) // 4
const double4 = double(4) // 8
...
const triple = base.bind(null,3)
const triple2 = triple(2) // 6
const triple4 = triple(4) // 12
...
bind返回一个函数,在函数体内调用apply
Function.prototype.bind2 = function (context) {
let originFn = this
return function () {
return originFn.apply(context)
}
}
-
调用
bind后返回的函数是可以传参的 -
bind方法除了第一个参数,还可以额外传参,可以理解为预传参
function setDetails(name,color){
this.name = name
this.color = color
}
let cat1 = {}
let setDetails2 = setDetails.bind(cat1,'大毛')
setDetails2('橘色')
- 将第一次传参先存起来,在调用时将第一次和第二次传参进行拼接
Function.prototype.bind2 = function (context) {
let originFn = this;
let args = Array.from(arguments).slice(1);
return function () {
let bindArgs = Array.from(arguments);
return originFn.apply(context, args.concat(bindArgs));
}
}
bind返回的函数,如果之后是作为构造函数调用,则原函数中的this会指向创建的对象,而不会指向之前绑定的对象,并且生成的实例仍然可以使用原型对象上的属性
function Cat(name, color) {
this.name = name
this.color = color
}
Cat.prototype.miao = function(){console.log('喵~!')}
let cat1 = {}
let CatBind = Cat.bind(cat1)
let cat2 = new CatBind('大毛','橘色')
cat2.miao()
- 当返回的函数作为构造函数,通过
new xxx()调用时,返回的函数的this指向以返回函数作为构造生成的实例。因此只需要判断this instanceof 返回的函数,另外,返回的函数要继承原函数(将原型链连接起来):
Function.prototype.bind2 = function (context) {
let originFn = this;
let args = Array.from(arguments).slice(1);
function fBind() {
let bindArgs = Array.from(arguments);
return originFn.apply(this instanceof fBind ? this /*作为构造函数调用*/: context, args.concat(bindArgs));
}
fBind.prototype = Object.create(this.prototype) //继承
return fBind
}