(现实是此岸,梦想是彼岸,中间隔着湍急的河流,行动则是架在河上的桥梁。——克雷洛夫)
call
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
MDN链接
call方法可以将一个对象属性作为另一个对象方法的上下文来使用,比如以下代码。
const obj = {
name: '张三',
age: 20,
getData: function () {
return `${this.name}的年龄是${this.age}`
}
};
const obj2 = {
name: '李四',
age: 50,
getData: function () {
return `${this.name}的年龄已经${this.age}岁了`
},
getData2: function (sex, address) {
return `${this.name}的年龄已经${this.age},性别${sex}, 他家住在${address}`;
}
};
console.log(obj.getData()); // 使用自身属性和方法执行
console.log(obj.getData.call(obj2)); // 将obj2作为obj.getData方法的上下文来执行
console.log(obj2.getData2.call(obj, '男', '北京市')); // 将obj作为obj2.getData2方法的上下文来执行
call方法可以将一个函数的上下文作为另一个函数的上下文来使用,达到继承的目的
const Father = function () {
this.name = '父亲';
this.age = 60;
this.address = '北京市';
this.sex = '男';
this.getData = function () {
return `名称:${this.name} - 年龄:${this.age} - 地址:${this.address} - 性别:${this.sex}`;
};
this.getData2 = function (hobby) {
return `名称:${this.name} - 年龄:${this.age} - 地址:${this.address} - 性别:${this.sex} - 爱好:${hobby}`;
};
};
const father = new Father();
console.log(father.getData()); // 输入父函数自己的上下文
const Daughter = function () {
// 此处call要写在第一行,这样避免其他的初始化被call覆盖
// 此处call用意为继承Father对象的上下文
Father.call(this);
this.name = '女儿';
this.age = 12;
this.address = '河南';
this.sex = '女';
};
const daughter = new Daughter();
console.log(daughter.getData()); // 虽然Daughter函数没有getData方法,但因为使用call,this指向了Father,所以就可以使用父函数的getData方法
call方法可以将一个对象上下文作为另一个函数的上下问来使用
const obj = {
name: '张三',
getName: function () {
return this.name;
}
};
const Fun = function () {
const data = ['姓名', this.getName()].join('');
console.log(data);
};
Fun.call(obj); // 姓名张三
callee
MDN链接
callee 是 arguments 对象的一个属性。它可以用于引用该函数的函数体内当前正在执行的函数。
假如我们要写一个递归计算函数,如下代码,随着函数名称的更改,内部代码的函数名也要更改。并且如果这是一个匿名函数,那么将无法进行递归。
const fun = function (x) {
if (x === 0) {
return;
} else {
return fun(x - 1);
}
};
所以callee出现了,它可以引用该函数的函数体内当前正在执行的函数上下文,比如以下代码,通过记录内部上下文也可以达到递归的目的。
const fun2 = function (x) {
if (x === 0) {
return;
} else {
return arguments.callee(x - 1);
}
};
但为什么callee在es5严格模式中被禁用了呢?
const global = this;
const sillyFunction = function (recursed) {
if (!recursed)
return arguments.callee(true);
if (this !== global)
console.log("This is: " + this);
else
console.log("This is the global");
}
sillyFunction();
通过以上的代码结果得知,每次callee得到的this都不相同,并且如果一个业务中有多个不同的arguments.callee嵌套,那么维护的成本也是巨大的。
callee的危害
所以最好的办法还是通过命名函数的方式来进行业务操作,虽然会麻烦一些,但对后期代码的维护成本大大降低,性能也更高。
caller
返回调用指定函数的函数,比如如下代码。
const Fun = function () {
this.fun2 = function () {
console.log(this.fun2.caller.name);
}
};
(function Test () {
new Fun().fun2(); // Test
})();
caller的通常在追踪业务调用链很有用,它可以将函数名和函数文本打印出来
apply
apply的作用和call相同,只不过第二个参数是数组而非多个。
const obj = {
name: '张三',
age: 20,
getData: function () {
return `${this.name}的年龄是${this.age}`
}
};
const obj2 = {
name: '李四',
age: 50,
getData: function () {
return `${this.name}的年龄已经${this.age}岁了`
},
getData2: function (sex, address) {
return `${this.name}的年龄已经${this.age},性别${sex}, 他家住在${address}`;
}
};
console.log(obj.getData()); // 使用自身属性和方法执行
console.log(obj.getData.apply(obj2)); // 将obj2作为obj.getData方法的上下文来执行
console.log(obj2.getData2.apply(obj, ['男', '北京市'])); // 将obj作为obj2.getData2方法的上下文来执行
bind
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。比如以下代码
MDN链接
global.x = -1;
const data = {
x: 42,
getX: function () {
return this.x;
}
};
console.log(data.getX()); // 42
const unboundGetX = data.getX;
console.log(unboundGetX());
// undefind 因为函数被赋值给unboundGetX时并没有执行,再次执行时,使用的上下文就是全局的了
const boundGetX = unboundGetX.bind(data);
// 那么为了解决this指向的问题,就可以使用.bind 通过指定参数的形式来指定this指向
console.log(boundGetX()); // 42
bind在计时器中的应用
function LateBloomer() {
this.petalCount = Math.ceil(Math.random() * 12) + 1;
}
// 在 1 秒钟后声明 bloom
LateBloomer.prototype.bloom = function() {
window.setTimeout(this.declare.bind(this), 1000);
};
LateBloomer.prototype.declare = function() {
console.log('I am a beautiful flower with ' +
this.petalCount + ' petals!');
};
var flower = new LateBloomer();
flower.bloom(); // 一秒钟后,调用 'declare' 方法
bind在异步promise中的应用
假如有异步并行查询的业务场景,那么为了控制频率,减少内存和cpu占用。所以需要将异步待执行函数单独抽离出来,放入异步池中执行。
const queryAll = async () => {
const dbCollection = {}; // mysql or mongodb
const getUser = async () => { dbCollection.get };
const getMobile = async () => { dbCollection.get };
const getAddress = async () => { dbCollection.get };
const getSex = async () => { dbCollection.get };
const getHeight = async () => { dbCollection.get };
const getWeight = async () => { dbCollection.get };
// 每两个一组放入异步池,实际情况可能需要遍历并编写分组代码
const pool= [
[
getUser.bind(this),
getMobile.bind(this)
],
[
getAddress.bind(this),
getSex.bind(this)
],
[
getHeight.bind(this),
getWeight.bind(this),
]
];
for (const item of pool) {
const result = await Promise.all(item);
// ....一些集合处理
}
};
bind参数传递
bind和call一样都可以进行参数传递。
const data = {
x: 42,
getX: function (y, z) {
return this.x + (y || 0) + (z || 0);
}
};
const data2 = {
x: 10,
getX: function (y, z) {
return this.x + (y || 0) + (z || 0);
}
};
const unboundGetX = data.getX;
const boundGetX = unboundGetX.bind(data2, 1, 2);
console.log(boundGetX()); // 45
bind解决普通函数this指向问题
const Persion = function () { //定义构造函数
this.age = 0; // 定义age
setInterval(function () {
this.age++; // 这个时候输出为NaN 因为setInterval的this指向的是全局
console.log(this.age);
}, 1000)
}
new Persion();
// 那么为了解决this问题,就可以使用提前赋值this的方式
const Persion2 = function () { //定义构造函数
this.age = 0; // 定义age
const that = this;
setInterval(function () {
that.age++; // 这个时候输出为NaN 因为setInterval的this指向的是全局
console.log(that.age);
}, 1000)
}
new Persion2();
// 也可以使用bind的方式
const Persion3 = function () { //定义构造函数
this.age = 0; // 定义age
setInterval(function () {
this.age++; // 这个时候输出为NaN 因为setInterval的this指向的是全局
console.log(this.age);
}.bind(this), 1000);
}
new Persion3();