1.call
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
1.1 语法
参数
function.call(thisArg, arg1, arg2, ...)
thisArg:可选的,在function函数运行时使用的this值。(在非严格模式下,当传入null或者undefined时会自动指向全局对象)
arg1,arg2...:指定的参数列表,可以用arguments代替
返回值
使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined。
描述
call() 允许为不同的对象分配和调用属于一个对象的函数/方法。
call() 提供新的 this 值给当前调用的函数/方法。你可以使用 call 来实现继承:写一个方法,然后让另外一个新的对象来继承它(而不是在新对象中再写一次这个方法)。
1.2 示例
1.2.1 通过call()方法实现继承
function Product(name,price){
this.name = name;
this.price = price;
}
function Fruit(name,price){
this.type = 'fruit';
Product.call(this,name,price)
}
var apple = new Fruit('apple',10)
console.log(apple);
1.2.2 利用call传递对象参数
function greet(){
var reply = this.animal+' typically sleep between ' + this.sleepDuration;
console.log(reply);
}
var obj = {
animal:'cats',
sleepDuration:'0-24h'
}
greet.call(obj)
1.2.3 箭头函数中的call/apply
由于箭头函数中的this已经绑定为上一层作用域中的this,所以使用call/apply时传入的第一个参数this无效。
例如下列代码,由于getAge方法被obj调用,所以getAge中的this指向obj,则b = 1990。
调用getAge时传入参数使得year = 2015,通过call的第二个参数将2015传入到fn的参数y中,使得y = 2015,而由于fn是箭头函数,其中的this相当于getAge中的this,因此最终返回2015 - 1990 = 25。
var obj = {
birth: 1990,
getAge: function (year) {
var b = this.birth; // 1990
var fn = (y) => y - this.birth; // this.birth仍是1990
return fn.call({birth:2000}, year);
}
};
obj.getAge(2015); // 25
1.2.4 严格模式和非严格模式下的call
call是可以传参的,但是在严格模式和非严格模式下,得到的值不一样。
非严格模式
const obj = {
name: 'yoimiya'
}
const fn = function (a, b) {
console.log(a + b);
console.log(this);
}
fn.call(100, 200);//this=100 a=200 b=undefined 输出NaN和Number{100}
fn.call(obj, 100, 200);//this=obj a100 b200 输出300和obj
fn.call();
fn.call(null);
fn.call(undefined);//这三条都相当于this=window 都输出NaN和Window
严格模式
fn.call();//this=undefined 输出NaN undefined
fn.call(null);//this=null 输出NaN null
fn.call(undefined);//this=undefined 输出NaN undefined
1.3 手写call
Function.prototype.myCall = function (context = window, ...args) {
if (typeof this !== 'function') throw new TypeError('123123');
context.fn = this;
let res = context.fn(...args);
delete context.fn;
return res;
}
测试代码
var value = 1;
var obj = {
value: 2
}
function bar(name, age) {
return {
value: this.value,
name: name,
age: age
}
}
console.log(bar.myCall(null, 'lebron', 37));
// 相当于指向window {value: 1, name: 'lebron', age: 37}
console.log(bar.myCall(obj, 'lebron', 37));
// {value: 2, name: 'lebron', age: 37}
2.apply
2.1 手写apply()
别的完全一样,唯一区别是apply()传的是数组作为参数,中间部分处理有些区别:
let myApply = function (context = window , arr) {
if(typeof this !== 'function') throw new TypeError('...');
context.fn = this;
const res = context.fn(...arr);
delete context.fn;
return res;
}
测试用例就是把传入的后几个参数变成数组,此处略。
3.bind
3.1 bind作用
bind和call/apply的区别是体现了预处理的思想,返回一个函数,需要调用时再调用:
const obj = {
name : 'yoimiya'
}
const fn = function(a , b){
console.log(a + b);
}
fn.call(obj, 1, 2);//->改变this和执行fn函数同时完成了
const afterBindFn = fn.bind(obj, 1, 2);
//改变了fn中的this为obj,并且给fn传递了两个参数值1、2,
//但是此时并没有把fn这个函数执行,而是返回一个函数
//需要手动执行
afterBindFn();
3.2 手写bind()
手写bind主要要注意三点:
- bind会创建一个新函数
- bind返回的函数可以通过new构造,但是提供的this值会被忽略
- bind需要考虑实例化后对原型链的影响
Function.prototype.myBind = function (context = window, ...args) {
if (typeof this !== 'function') throw new TypeError('123');
let fn = this;
let resFn = function () {
// 如果bind返回的函数被当作构造函数(被new)
if (this instanceof resFn) {
return new fn(...args, ...arguments);
}
// 直接执行bind返回的函数
return fn.apply(context, [...args, ...arguments])
}
// 设置原型
resFn.prototype = this.prototype;
return resFn;
}
这里补充一个小知识点:new.target:
new.target 属性允许你检测函数或构造方法是否是通过new运算符被调用的。 在通过new运算符被初始化的函数或构造方法中,new.target返回一个指向构造方法或函数的引用。在普通的函数调用中,new.target 的值是undefined。
function Foo() {
if (!new.target) throw "Foo() must be called with new";
console.log(new.target);
}
// Foo(); // throws "Foo() must be called with new"
new Foo(); // Foo(){...}
测试myBind:
情况1 直接调用resFn
const obj = {
val: 1
}
// bar1函数用于直接调用的情况
const bar1 = function (name, age) {
return {
val: this.val,
name,
age
}
}
// 直接调用
const afterBindFn1 = bar1.myBind(obj, 'yoimiya', 21);
console.log(afterBindFn1()); // {val: 1, name: 'yoimiya', age: 21}
情况2 resFn作为构造函数
// bar2函数用于作为构造函数的情况
const bar2 = function (name, age) {
this.val = 2;
this.name = name;
this.age = age;
}
// new出实例再调用
const afterBindFn2 = bar2.myBind(obj, 'yoimiya', 21);
const test = new afterBindFn2();
console.log(test); // {val: 2, name: 'yoimiya', age: 21}