- 如何灵活的运用 this
- 改变 this 的指向
- 常见应用场景
- 查找数组最大元素
- 将数组的空元素变为 undefined
- 调用对象的原生方法
- 使用 ES6 手写 call、apply、bind
面对对象的语言中,this 表示当前对象的一个引用,但在 JavaScript 中,this 不是固定不变的,它会随之执行环境的改变而改变。
一般情况下 this 指向全局变量 window。
作为对象方法调用时,this 指向上级对象。
作为构造函数调用时,this 指向 new 出的对象。
在函数中,非严格模式下 this 指向全局对象,但是在严格模式下 this 指向 undefined。
在事件中,this 表示接收事件的元素。
在 JavaScript 中,我们可以通过 call,apply,bind
来改变 this
的指向,以满足不同的场景需求。
举个栗子
var apple = {
name: '苹果',
color: '红色',
fn: function(){
console.log(`${this.name}是${this.color}的`)
}
}
apple.fn() //苹果是红色的
有一个 apple 对象,它有 name 和 color 两个属性,还有一个方法 fn 用来打印出 name 和 color。
如果现在有一个新的对象 banana:
var banana = {
name: '香蕉',
color: '黄色'
}
现在我也想打印出 banana 的信息,但是我并不想再写一遍 fn 方法,如果可以直接利用apple 的 fn 方法那就简化了很多。
apple.fn.call(banana) //香蕉是黄色的
通过 call 方法可以改变 apple.fn 中 this 的指向,指向的对象为接受的第一个参数。 我们还可以在后面传入多个参数,就像下面这样:
var apple = {
name: '苹果',
color: '红色',
fn: function(price, unit){
console.log(`${this.name}是${this.color}的,${price}元/${unit}`)
}
}
apple.fn.call(banana, 3.99, '斤') //香蕉是黄色的,3.99元/斤
apply 与 call 的用法几乎相同,唯一的区别就是传入参数不同,第一个参数同样是目标对象,剩余的参数以一个数组的形式传入,如下:
apple.fn.apply(banana, [3.99, '斤']) //香蕉是黄色的,3.99元/斤
bind 的作用也是改变 this 的指向,但它与 call 和 apply 不同的是,它会返回一个函数,而不是立即执行,传参方式与 call 相同。
var fn2 = apple.fn.bind(banana, 3.99, '斤')
fn2() //香蕉是黄色的,3.99元/斤
当第一个参数为空、null、undefined、NaN、空字符串时,默认传入全局对象
如果第一个参数为 number、boolean 类型的值时,会自动将其转为对应的包装对象。
index.js
c
var banana = {
name: '香蕉',
color: '黄色'
}
var apple = {
name: '苹果',
color: '红色',
fn: function() {
console.log(`${this.name}是${this.color}的`)
}
}
apple.fn()
apple.fn.call(banana) //香蕉是黄色的
var apple = {
name: '苹果',
color: '红色',
fn: function(price, unit) {
console.log(`${this.name}是${this.color}的,${price}元/${unit}`)
}
}
apple.fn.call(banana, 3.99, '斤') //香蕉是黄色的,3.99元/斤
apple.fn.apply(banana, [3.99, '斤']) //香蕉是黄色的,3.99元/斤
var fn2 = apple.fn.bind(banana, 3.99, '斤')
fn2() //香蕉是黄色的,3.99元/斤
常见应用场景
查找数组最大元素
var a = [3, 9, 5, 3, 6]
console.log(Math.max.apply(null, a)) //9
console.log(Math.max.apply(undefined, a)) //9
console.log(Math.max.apply(NaN, a)) //9
console.log(Math.max.apply('', a)) //9
console.log(Math.max.call(null, ...a)) //9
将数组的空元素变为 undefined
var b = [3, ,8 , , 4, 7]
console.log(Array.apply(null, b)) //[3, undefined, 8, undefined, 4, 7]
console.log(Array.call(null, ...b)) //[3, undefined, 8, undefined, 4, 7]
调用对象的原生方法
Object 的 hasOwnProperty() 方法返回一个布尔值,判断对象是否包含特定的自身(非继承)属性。
如果某个对象中重写了 hasOwnProperty 方法,那我们再使用 hasOwnProperty() 是无法得到正确的结果的。
var obj = {}
console.log(obj.hasOwnProperty('name')) //false
obj.hasOwnProperty = function(){
return true
}
console.log(obj.hasOwnProperty('name')) //true
console.log(Object.prototype.hasOwnProperty.call(obj, 'name')) //false
在上面的代码中,在 obj 对象中使用原生的 Object.prototype.hasOwnProperty 方法可以避开使用被覆盖的 hasOwnProperty 。
对于许多原生的方法,我们都可以使用 call、apply、bind
来得到同样的结果,他们是等价的,就像下面这样:
[1, 2, 3].slice(0, 1) //[1]
Array.prototype.slice.call([1, 2, 3], 0, 1) //[1]
使用 ES6 手写 call、apply、bind
function add(c, d) {
return this.a + this.b + c + d;
}
const obj = { a: 1, b: 2 };
// ES6 call 实现
Function.prototype.es6call = function (context, ...rest) {
var context = context || window;
context.fn = this;
const result = context.fn(...rest);
delete context.fn;
return result;
}
console.log(add.es6call(obj, 3, 4)); //10
context 代表接收的上下文环境,...rest
接收的其他参数。
1 若 context 为空值,则默认为 window。
2 将被调用对赋值给 context.fn,此时 context.fn 中可以访问到 context 中的其他属性,context.fn 中的 this 不再指向它的原对象,而是新的对象 context。
3 context.fn(…rest) 将其他的参数传入函数中执行得到结果。
Function.prototype.es6apply = function(context, arr){
var context = context || window
context.fn = this
const result = context.fn(...arr)
delete context.fn
return result
}
console.log(add.es6apply(obj, [3, 4])); //10
apply 的实现与 call 的实现几乎相同,唯一区别就是接受参数的形式不同,一个是多个参数形式,一个是把多个参数转为一个数组的形式。
Function.prototype.es6bind = function(context, ...rest) {
var self = this;
return function fn(...args) {
var result = this instanceof fn ? new self(...rest, ...args) : self.apply(context, rest.concat(args))
return result
}
}
bind 接受参数与 call 相同,返回结果为一个函数。