箭头函数相信大家在日常开发中用到的地方非常之多,因为它很简洁,可读性强,但是它最大的好处,其实是解决了匿名函数的this指向问题,有利于封装回调函数。
var name = 'window'; // 其实是window.name = 'window'
var A = {
name: 'A',
sayHello: function(){
console.log(this.name)
}
}
A.sayHello();// 输出A
var B = {
name: 'B'
}
A.sayHello.call(B);//输出B
A.sayHello.call();//不传参数指向全局window对象,输出window.name也就是window
从上面可以看到,sayHello这个方法是定义在A对象中的,当当我们使用call方法,把其指向B对象,最后输出了B;可以得出,sayHello的this只跟使用时的对象有关。
var name = 'window';
var A = {
name: 'A',
sayHello: () => {
console.log(this.name)
}
}
A.sayHello();// 还是以为输出A ? 错啦,其实输出的是window
一开始,我重点标注了“该函数所在的作用域指向的对象”,作用域是指函数内部,这里的箭头函数,也就是sayHello,所在的作用域其实是最外层的js环境,因为没有其他函数包裹;然后最外层的js环境指向的对象是winodw对象,所以这里的this指向的是window对象。
var name = 'window';
var A = {
name: 'A',
sayHello: function(){
var s = () => console.log(this.name)
return s//返回箭头函数s
}
}
var sayHello = A.sayHello();
sayHello();// 输出A
var B = {
name: 'B';
}
sayHello.call(B); //还是A
sayHello.call(); //还是A
全局环境
无论是否在严格模式下,在全局执行环境中(在任何函数体外部)this
都指向全局对象。
// 在浏览器中, window 对象同时也是全局对象:
console.log(this === window); // true
a = 37;
console.log(window.a); // 37
this.b = "MDN";
console.log(window.b) // "MDN"
console.log(b) // "MDN"
函数(运行内)环境
在函数内部,this
的值取决于函数被调用的方式。
function f1(){
return this;
}
//在浏览器中:
f1() === window; //在浏览器中,全局对象是window
然而,在严格模式下,this
将保持他进入执行环境时的值,所以下面的this
将会默认为undefined
。
function f2(){
"use strict"; // 这里是严格模式
return this;
}
f2() === undefined; // true
如果要想把 this
的值从一个环境传到另一个,就要用 call
或者apply
方法。
// 将一个对象作为call和apply的第一个参数,this会被绑定到这个对象。
var obj = {a: 'Custom'};
// 这个属性是在global对象定义的。
var a = 'Global';
function whatsThis(arg) {
return this.a; // this的值取决于函数的调用方式
}
whatsThis(); // 'Global'
whatsThis.call(obj); // 'Custom'
whatsThis.apply(obj); // 'Custom'
当一个函数在其主体中使用 this
关键字时,可以通过使用函数继承自Function.prototype
的 call
或 apply
方法将 this
值绑定到调用中的特定对象。
function add(c, d) {
return this.a + this.b + c + d;
}
var o = {a: 1, b: 3};
// 第一个参数是作为‘this’使用的对象
// 后续参数作为参数传递给函数调用
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
// 第一个参数也是作为‘this’使用的对象
// 第二个参数是一个数组,数组里的元素用作函数调用中的参数
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
使用 call
和 apply
函数的时候要注意,如果传递给 this
的值不是一个对象,JavaScript 会尝试使用内部 ToObject
操作将其转换为对象。因此,如果传递的值是一个原始值比如 7
或 'foo'
,那么就会使用相关构造函数将它转换为对象,所以原始值 7
会被转换为对象,像 new Number(7)
这样,而字符串 'foo'
转化成 new String('foo')
这样,例如:
记住最核心的一句话:
哪个对象调用函数,函数里面的this指向哪个对象。
分几种情况谈论下:
一、普通函数调用
二、对象函数调用
三、构造函数调用
四、apply和call调用
五、箭头函数调用
一、普通函数调用
这个情况没特殊意外,就是指向全局对象-window。
1、使用let
/*普通函数调用*/
let username = "程新松";
function fn(){
console.log(this.username); //undefined
}
fn();
2、使用var
var name = "程新松";
function fn(){
console.log(this.name);//程新松
}
fn();
4、let和var区别:
(1)let
允许把变量的作用域限制在块级域中;var 申明变量要么是全局的,要么是函数级的,而无法是块级的。
let varClass = function(){
var name='程新松';
if(true){
var name='saucxs';
console.log(name);// saucxs
}
console.log(name);// saucxs
}
varClass();
let letClass = function(){
let name='程新松';
if(true){
let name='saucxs';
console.log(name);// saucxs
}
console.log(name);// 程新松
}
letClass();
(2)先let后var
let subClass = function(){
let name='程新松';
if(true){
var name='saucxs';
console.log(name);
}
console.log(name);
}
subClass();
var 是函数级作用域,相当于一个作用域中有两个n的变量了
var 作用于整个 subClass ,和let冲突了,let不能重复声明
(3)先var后let
let subClass = function(){
var name='程新松';
if(true){
let name='saucxs';
console.log(name);//saucxs
}
console.log(name);//程新松
}
subClass();
先申明var,再申明let,这个没有问题。
二、对象函数调用
这个相信不难理解,就是哪个函数调用,this指向哪里。
/*对象函数调用*/
//window.name='程新松';
//var name='程新松';
let name='程新松';
let obj={
id:201102304,
fn:function(){
console.log(this.name); //undefined
console.log(this.id); //201102304
}
}
obj.fn();
/*需要注意的情况*/
let obj1={
a:111
}
let obj2={
a:222,
fn:function(){
console.log(this.a);
}
}
obj1.fn=obj2.fn;
obj1.fn(); //111
这个也不难理解,虽然 obj1.fn
是从 obj2.fn
赋值而来,但是调用函数的是 obj1
,所以 this
指向 obj1
。
三、构造函数调用
/*构造函数调用*/
let structureClass=function(){
this.name='程新松';
}
let subClass1=new structureClass();
console.log(subClass1.name);//程新松
let subClass=new structureClass();
subClass.name='成才';
console.log(subClass.name);//成才
但是有一个坑,虽然一般不会出现,但是有必要提一下。
在构造函数里面返回一个对象,会直接返回这个对象,而不是执行构造函数后创建的对象
let structureClass=function(){
this.name='程新松';
return {
username:'saucxs'
}
}
let subClass1=new structureClass();
console.log(subClass1);
console.log(subClass1.name);
四、apply和call调用
1、apply和call简单来说就是会改变传入函数的this。
/*apply和call调用*/
let obj1={
name:'程新松'
};
let obj2={
name:'saucxs',
fn:function(){
console.log(this.name);
}
}
obj2.fn.call(obj1);//程新松
此时虽然是 obj2
调用方法,但是使用 了 call
,动态的把 this
指向到 obj1
。相当于这个 obj2.fn
这个执行环境是 obj1
。
call
和 apply
两个主要用途:
1.改变 this
的指向(把 this
从 obj2
指向到 obj1
)
2.方法借用( obj1
没有 fn
,只是借用 obj2
方法)
2、call与apply区别
call
和 apply
的作用,完全一样,唯一的区别就是在参数上面。
call
接收的参数不固定,第一个参数是函数体内 this
的指向,第二个参数以下是依次传入的参数。
apply接收两个参数,第一个参数也是函数体内 this
的指向。第二个参数是一个集合对象(数组或者类数组)
/*apply和call区别*/
let fn=function(a,b,c){
console.log(a,b,c);
}
let arrArray=[1,2,3];
fn.call(window,arrArray);
fn.apply(window,arrArray);
五、箭头函数调用
首先不得不说,ES6 提供了箭头函数,增加了我们的开发效率,但是在箭头函数里面,没有 this
,箭头函数里面的 this
是继承外面的环境。
/*箭头函数调用*/
let obj={
name:'程新松',
fn:function(){
setTimeout(function(){console.log(this.name)})
}
}
obj.fn();//undefined
不难发现,虽然 fn() 里面的 this 是指向 obj ,但是,传给 setTimeout 的是普通函数, this 指向是 window , window 下面没有 name ,所以这里输出 underfind 。
setTimeout的常见用法是让某个方法延迟执行。setTimeout方法是挂在window对象下的。
《javascript高级程序设计》中有:“超时调用的代码都是在全局作用域中执行的,因此函数中的this的值在非严格模式下指向window对象,在严格模式下是undefined”。这里讨论的是非严格模式。
setTimeout的延迟执行函数中的this,永远指向window,而setTimeout调用环境中的this,是根据上下文来确定的,默认为window。看下面的例子:
上面的例子可以看出。延迟执行函数中的this的确是指向了window,但是执行函数的其他变量需要根据上下文来确认。
/换成箭头函数
let obj={
name:"程新松",
fn:function(){
setTimeout(()=>{console.log(this.name)});
}
}
obj.fn();//程新松
这次输出 程新松 是因为,传给 setTimeout 的是箭头函数,然后箭头函数里面没有 this ,所以要向上层作用域查找,在这个例子上, setTimeout 的上层作用域是 fn 。而 fn 里面的 this 指向 obj ,所以 setTimeout 里面的箭头函数的 this ,指向 obj 。所以输出 程新松。