this绑定
在javascript中,this的指向通常是一个令人头痛的问题,js中的this的指向与真正的有类的概念的语言(java,c++,c#)不同,js中的this并不是表示指向自身,而是会随着调用上下文和作用域而改变。
通常this的绑定有四种情况
1.隐式绑定
即作为对象的函数进行调用,此时this指向上下文的对象
function sayName() {
console.log(this.name);
}
let person = {
name: "Jack",
sayName: sayName
}
person.sayName() // Jack
复制代码
2显示绑定
即通过apply,call和bind进行绑定
// apply, call软绑定, 绑定后可以更改this的指向
function sayName() {
console.log(this.name);
}
let person = {
name: 'Jack'
}
let child = {
name: 'Tim',
say: say
}
sayName.apply(person); // Jack
sayName.apply(child); // Tim
sayName.call(person); // Jack
sayName.call(child); // Tim
//bind 硬绑定,一旦绑定,便无法更改this的指向
let say = sayName.bind(person);
say(); // Jack
child.say(); // Jack
let log = say.bind(child);
log(); // Jack, this并没有更改指向
let says = sayName.bind(child);
says(); // Jack, this也没有更改指向
复制代码
3.new绑定
当用new创建一个对象时,this指向这个对象
function Person(name) {
this.name = name;
}
let person = new Person('Jack');
console.log(person.name); // Jack
复制代码
4.默认绑定
当不符合上面三种情况时,则使用默认绑定规则。严格模式下绑定到undefined,普通模式下绑定到window(nodejs中绑定到global)
var name = 'Jack';
function sayName() {
console.log(this.name);
}
let person = {
name: 'Tim',
sayName: function() {
sayName();
}
};
person.sayName();
复制代码
上面的代码在nodejs和浏览器环境下的结果是不同的。虽然nodejs中默认绑定会绑定到global对象,但是上面的代码结果是输出undefined;在浏览器环境下会输出Jack
如果将var换成let声明变量方式,浏览器环境下回输出空字符串,而nodejs下回输出undefined
绑定的优先级:new > 显示 > 隐式 > 默认
胖箭头函数的this绑定
胖箭头函数没有自己的this,胖箭头函数的this继承自外层代码块所在作用域的this,如果外层代码块不存在this,则继续向上查找。
let person = {
fun1: function() {
console.log(this);
return () => {
console.log(this);
}
},
fun2: function() {
console.log(this);
return function() {
console.log(this);
}
},
fun3: () => {
console.log(this);
}
}
let fun1 = person.fun1(); // person
fun1(); // person
let fun2 = person.fun2(); // person
fun2(); // window
person.fun3(); // window
/*
第一次函数调用,采用隐式绑定,this指向上下文对象,也就是person
第二次函数调用,调用了胖箭头函数,胖箭头函数的this继承自上层代码块中的this,上层
代码块中的this指向person,所以胖箭头函数的this也指向person
第三次函数调用,采用隐式绑定,this指向上下文对象
第四次函数调用,既没有隐式绑定条件,没有显示绑定条件,也没有new绑定条件,所以采用默认绑定规则,this指向window
第五次函数调用,调用了胖箭头函数,而上层person中没有this,所以向上查找,person的上层为window,所以此次胖箭头函数的this指向window
*/
复制代码
apply,call,bind详解
apply
apply接受两个参数,第一个参数指定函数体内this对象的指向,第二个参数是一个数组或类数组,并将此数组或类数组作为参数传递给所调用的函数
let person = {
0: "Jack",
1: "Tim",
length: 2
}
let persons = [
"Jack",
"Tim"
]
function log(a, b) {
console.log(a);
console.log(b);
}
log.apply(null, person); // Jack Tim
log.apply(null, persons); // Jack Tim
复制代码
call
call可以接受多个参数,第一个参数与apply相同,指定函数体内的this对象的指向,其余的参数传给被调用的函数,作为被调用函数的参数
function log(a, b) {
console.log(a);
console.log(b);
}
log.apply(null, "Jim", "Jack"); // Jim Jack
复制代码
call和apply的用途
apply和call除了可以更改this指向外,还可以用来实现类似于继承的效果
function Person(name) {
this.name = name;
this.sayName = function() {
console.log(this.name);
}
}
function Student() {
Person.apply(this, arguments);
}
let student = new Student("Jack");
student.sayName();
复制代码
借用其他对象的方法
// 数组没有取得最大值的方法,可以通过调用Math.max()函数来取得数组的最大值
let arr = [1, 2, 3, 4, 7, 9, 5, 4];
console.log(Math.max.apply(null, arr)); // 9
复制代码
bind
bind可接受多个参数,并返回一个新函数。第一个参数指定新函数体内的this对象的指向,其余的参数会在传递实参之前传给新函数,作为新函数的参数
bind只用一个用途,改变this的指向。并且一旦通过bind绑定了this的指向,无法再次通过bind,apply或call更改this的指向
function sayName() {
console.log(this.name);
}
let person = {
name: 'Jack'
};
let child = {
name: 'Tim',
};
let say = sayName.bind(person);
say(); // Jack
let log = say.bind(child);
log(); // Jack, this并没有更改指向
say.apply(child); // Jack this并没有更改指向
say.call(child); // Jack this并没有更改指向
复制代码
实现apply函数
Function.prototype.mApply = function(context, arr) {
// 如果context为null,则将context设置为window(浏览器环境)或global(nodejs环境)
if (!context) {
context = typeof window === 'undefined' ? global : window; // 判断是在什么环境下运行,根据环境来给context设置默认值
}
context.fn = this; // 给context添加函数,使添加的函数指向当前被调用函数
let res; // 返回值
if (!arr) {
// 若参数为null,可以直接调用
res = context.fn(arr); // 调用添加函数
} else if (typeof arr === 'object') {
// 若参数为数组或类数组,则通过...运算符将其传入
res = context.fn(...arr); // 调用添加的函数
}
delete context.fn; // 删除添加的函数
return res; // 传出返回值
}
let arr = [1, 2, 3, 4, 7, 9, 5, 4];
console.log(Math.max.mApply(null, arr)); // 9
复制代码
实现call函数
Function.prototype.mCall = function(context) {
// 如果context为null,则将context设置为window(浏览器环境)或global(nodejs环境)
if (!context) {
context = typeof window === 'undefined' ? global : window; // 判断是在什么环境下运行,根据环境来给context设置默认值
}
context.fn = this; // 给context添加函数,使添加的函数指向被调用的函数
let args = [...arguments].slice(1); // 取得除指定上下文参数之外的参数
let res = context.fn(...args); // 调用添加的函数,并存储返回值
delete context.fn; // 删除添加的函数
return res; // 传出返回值
}
let arr = [1, 2, 3, 4, 7, 9, 5, 4];
console.log(Math.max.mCall(null, ...arr)); // 9
复制代码
实现bind
Function.prototype.mBind = function(context) {
// 若不是函数
if (typeof this !== 'function') {
throw new TypeError("is not a funtion")
}
let args = [...arguments].slice(1);
function Fn() {}
fun.prototype = this.prototype;
let self = this;
let bound = function() {
let res = [...args, ...arguments]; // 将bind传递的参数与调用时传递的参数拼接
context = this instanceof Fn ? this : context || this;
return self.apply(context, res); // 返回值
}
bound.prototype = new Fn();
return bound;
}
function sayName() {
console.log(this.name);
}
let person = {
name: 'Jack'
};
let child = {
name: 'Tim',
};
let say = sayName.mBind(person);
say(); // Jack
let says = say.mBind(child);
says(); // Jack
复制代码
转载于:https://juejin.im/post/5cfc74c5e51d4510624f9797