Javascript中的this,call,apply,bind
(以下的示例代码全都在浏览器环境下运行)
this
的指向
【在es5规范的用法中!可以大声的说!谁最终调用我,我最终就归谁!】(记住这个最终!)
function codeMethod(){
console.log(this);
}
const obj = {
name:'codeHope',
age:23,
codeMethod,
}
codeMethod();
obj.codeMethod();
codeMethod();是在全局的环境下调用的(浏览器下window),而第二个obj.codeMethod();是obj对象调用的,谁调用这个函数,那么里面的那个this就指向那个对象
下面我们对上面那些变形一下,看看有没有什么坑!
1 第一个
window.obj = obj;
window.obj.codeMethod();//{name: "codeHope", age: 23, codeMethod: ƒ}
其实还是指向Obj
因为还是obj
,最终调用的这个方法!
2 第二个
function codeMethod(){
console.log(this);
}
const obj = {
name:'codeHope',
age:23,
codeMethod,
}
const f = obj.codeMethod;
f(); //Window
不管你如何变化,因为codeMethod
函数对象,是引用类型,你所有的变量( obj.codeMethod
,f
)存储的都只是他的内存地址,永远指向全局中定义的那个codeMethod
,所以你这样子最终还是在全局中调用的这个方法!那么this
指向的就是window
.
3 第三个
function method(){
console.log(this);
innerMethod();
function innerMethod() {
console.log(this);
}
}
const obj = {
name:"codeHopeObj",
myMethod:method,
}
method();
obj.myMethod();
method();
在浏览器环境下是window
对象调用,所以第一个里面的this 打印的是window
,innerMethod
,在内部这个方法没有被任何对象调用,所以他找不到this
,默认指向window
obj.myMethod();
obj.myMethod
指向的全局的method
方法,等于是Obj
调用method
,所以第一个this
打印的是obj
对象,第二个和上面同理,innerMethod
,在内部这个方法没有被任何对象调用,所以他找不到this
,默认指向window
所以打印结果如下:
填一填,我们拍拍土巩固一下啦!
var name = 'Nicolas';
function Person(){
this.name = 'Smiley';
this.sayName=function(){
console.log(this);
console.log(this.name);
};
setTimeout(this.sayName, 0);
}
var person = new Person();
person.sayName();
点击查看答案
Person {name: "Smiley", sayName: ƒ}
Smiley
Window {window: Window, self: Window, document: document, name: "Nic..}
Nicolas
person.sayName();这个调用的打印结果,很好解释,因为是person这个对象调用的,所以this指向的是person对象,this.name也是Smiley,setTimeout(this.sayName, 0);这个虽然是在构造函数里面,但是他的调用者其实是window,因为你也找不到其他的调用了是不是,其实他写全了是window.setTimeout(this.sayName, 0);所以this指向window对象,指向name是Nicolas
怎么改变 this
的指向
比如下面这个对象中的一个方法,我们需要在一段时间后调用对象中的另一个方法
var name = "windowsName";
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout( function () {
this.func1()
},100);
}
};
a.func2()
当func2
被执行的时候,其实里面的setTimeout
定时器最终是window对象调用的,而全局中没有func1
这个函数所以直接报错了
Uncaught TypeError: this.func1 is not a function at index.js:12
如果需要完成我们的需求的话,就需要我们去改变this
指向,让这个this
,指向我们对象的内部!
1 使用箭头函数
箭头函数的 this 始终指向函数定义时的 this,而非执行时。箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined”
var name = "windowsName";
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout(()=> {
//这个函数,因为是在箭头函数中调用,本身内部不存在this,他就向上在func2中找this,
//func2是被a对象调用的,所以func2中的this,指向a对象,然后调用a对象的func1的方法,就可以顺利执行了
this.func1();
},100);
}
};
a.func2();// Cherry
2 在函数内部使用 _this = this
var name = "windowsName";
var a = {
name: "Cherry",
func1: function () {
console.log(this.name);
},
func2: function () {
//将func2中的this对象指向的a对象地址存入一个变量,然后再其他能访问的这个变量的地方都可以调用里面的方法!
const _this = this;
setTimeout(function () {
_this.func1();
}, 100);
},
};
a.func2();// Cherry
3 使用 apply、call、bind
-
使用
apply
var name = "windowsName"; var a = { name: "Cherry", func1: function () { console.log(this.name); }, func2: function () { setTimeout(function () { this.func1(); }.apply(a), 100); }, }; a.func2();//Cherry
-
使用
call
var name = "windowsName"; var a = { name: "Cherry", func1: function () { console.log(this.name); }, func2: function () { setTimeout(function () { this.func1(); }.call(a), 100); }, }; a.func2();//Cherry
-
使用
bind
var name = "windowsName"; var a = { name: "Cherry", func1: function () { console.log(this.name); }, func2: function () { setTimeout(function () { this.func1(); }.bind(a), 100); }, }; a.func2();//Cherry
【看上面的用法很相似,而且都可以达到改变 this 指向的目的,那么这三个函数有什么不同呢?】
call
call()
方法使用一个指定的this
值和单独给出的一个或多个参数来调用一个函数。(多个参数用逗号","
隔开)
现在有一个codeHope
对象
const codeHope = {
nickname:"codeHope",
age:23,
introduce(hobby,job){
console.log(
`my name is ${this.nickname}, i'm ${this.age} years old. my hobby is ${hobby},my job is ${job}`
);
}
}
codeHope.introduce("play guitar","coder")
//my name is codeHope, i'm 23 years old. my hobby is play guitar,my job is coder
然后一个Marry
来了
const Marry = {
nickname:"Marry",
age:20,
}
她没有codeHope
里面的方法,于是想借用一下,来介绍自己!
codeHope.introduce.call(Marry,"swimming","Animal Helper")
//my name is Marry, i'm 20 years old. my hobby is swimming,my job is Animal Helper
这就通过call()
来改变了codeHope
里面方法的this
,而且还可以做到传参。
apply
其实 apply 和 call 基本类似,他们的区别只是传入的参数不同。apply 接收的是一个包含多个参数的数组。
codeHope.introduce.apply(Marry,["swimming","Animal Helper"])
//my name is Marry, i'm 20 years old. my hobby is swimming,my job is Animal Helper
bind
bind()
方法创建一个新的函数,并不会帮我调用,它只是帮我们返回了一个更换了this
指向的函数,我们还要自己去调用,传参的
const merryIntroduce = codeHope.introduce.bind(Marry);
merryIntroduce("swimming","Animal Helper");
//my name is Marry, i'm 20 years old. my hobby is swimming,my job is Animal Helper
回首掏之总结一下apply,call,bind
1 call
和 apply
的共同点
【call 和 apply 它们的共同点是,都能够改变函数执行时的上下文,将一个对象的方法交给另一个对象来执行,并且是立即执行的。】
为何要改变执行上下文?举一个生活中的小例子:
平时没时间做饭的我,周末想给自己炖个砂锅麻辣烫尝尝。但是家里没有砂锅,而我又不想出去买。所以就问邻居借了一个砂锅来用,这样既达到了目的,又节省了开支,一举两得。
【改变执行上下文也是一样的,A 对象有一个方法,而 B 对象因为某种原因,也需要用到同样的方法,那么这时候我们是单独为 B 对象扩展一个方法呢,还是借用一下 A 对象的方法呢?当然是借用 A 对象的啦,既完成了需求,又减少了内存的占用。】
2 call
和apply
的区别
它们的区别,主要体现在参数的写法上。先来看一下它们各自的具体写法。
-
call
- 调用 call 的对象,必须是个函数 Function。
- call 的第一个参数,是一个对象。 Function 的调用者,将会指向这个对象。如果不传,则默认为全局对象 window。
- 第二个参数开始,可以接收任意个参数。每个参数会映射到相应位置的 Function 的参数上
Function.call(obj,params1,params2,...,paramsN)
-
apply
- 它的调用者必须是函数 Function,并且只接收两个参数,第一个参数的规则与 call 一致。
- 第二个参数,必须是数组或者类数组,它们会被转换成类数组,传入 Function 中,并且会被映射到 Function 对应的参数上。这也是 call 和 apply 之间,很重要的一个区别。
Function.apply(obj,[params1,params2,...,paramsN])
3 bind
最后来说说 bind。在 MDN 上的解释是:bind() 方法创建一个新的函数,在调用时设置 this 关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。
【bind 方法 与 apply 和 call 比较类似,也能改变函数体内的 this 指向。不同的是,bind 方法的返回值是函数,并且需要稍后调用,才会执行。而 apply 和 call 则是立即调用。】
//定义一个累数组
const obj = {
1:1,
2:2,
3:3,
length:4
}
const t = Array.prototype.filter.bind(obj);
console.log(typeof t);//function
const newObj = t(item=>{
return item ==3;
})
console.log(newObj);//[ 3 ]
【如果 bind,apply,call 的第一个参数是 null 或者 undefined,this 就指向全局对象 window】
【注意,在浏览器环境下,只有通过var定义的变量才会被挂载到window对象中】
var nickname1 = "windowsName1"
const nickname2 = "windowsName2"
let nickname3 = "windowsName3"
const obj = {
nickname1:"codeHope1",
nickname2:"codeHope2",
nickname3:"codeHope3",
logName(){
console.log(this);
console.log(this.nickname1);
console.log(this.nickname2);
console.log(this.nickname3);
}
}
obj.logName();
/*
{nickname1: "codeHope1", nickname2: "codeHope2", nickname3: "codeHope3"..}
codeHope1
codeHope2
codeHope3
*/
obj.logName.apply(null)
obj.logName.call(null)
obj.logName.bind(null)();
/*
Window {window: Window, self: Window, document: document, name: "" …}
windowsName1
undefined
undefined
Window {window: Window, self: Window, document: document , name: ""…}
windowsName1
undefined
undefined
Window {window: Window, self: Window, document: document , name: ""…}
windowsName1
undefined
undefined
*/
apply,call,bind
的使用场景实例分析
-
使用apply实现继承
function Father(name,age){ this.name = name; this.age =age; } function Son(name,age,grade){ Father.call(this,name,age) this.grade = grade; } const son = new Son('codeHope"s',2,1) console.log(son); //Son { name: 'codeHope"s', age: 2, grade: 1 }
-
使用apply实现多重继承
-
借用方法
let domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));
这样,domNodes 就可以应用 Array 下的所有方法了。
-
Math.max|.min
。用它来获取数组中 最大|最小 的一项。let max = Math.max.apply(null, array); let min = Math.min.apply(null, array);
-
实现两个数组合并
let arr1 = [1, 2, 3]; let arr2 = [4, 5, 6]; Array.prototype.push.apply(arr1, arr2); //arr2相当于是数组参数! console.log(arr1); // [1, 2, 3, 4, 5, 6]
最后,分享一个在知乎上看到的,关于 call 和 apply 的便捷记忆法:
猫吃鱼,狗吃肉,奥特曼打小怪兽。
有天狗想吃鱼了
猫.吃鱼.call(狗,鱼)
狗就吃到鱼了
猫成精了,想打怪兽
奥特曼.打小怪兽.call(猫,小怪兽)
猫也可以打小怪兽了
评论区