对于this指向的问题,永远不会过时,这次就好好聊一聊这个this这个小可爱
this的原理
var obj = {
fn: function() {
console.log(this.a);
},
a:1
};
var fn = obj.fn;
var a = 2
// 写法一
obj.fn(); // 1
// 写法二
fn(); // 2
对于这两种写法,我们知道,谁调用,this指向谁,写法一是通过对象来调用这个方法,运行在obj这个环境,所以this指向这个对象,而写法二是直接调用这个方法,运行在全局环境,this指向winodw。
但是为什么会这样,函数运行的环境是怎么怎么决定的?那就得看一下javascript这样处理的原理。
内存方式
js的对象数据储存在堆和栈中。栈的数据读取,写入速度快,但是存储的内容较少。堆的读取和写入速度慢,但是存储的内容多。- 栈中存储的基本数据类型的值本身,而堆中存储的这个对象。
- 当创建一个对象时,会把数据存储在堆中,但是会在栈中存储一个地址值,这个地址指向在堆中存储的这些数据,而栈中也可以有多个不同的地址值指向同一个数据源。
var obj = {
fn: function() { console.log(this.a); },
a:1
};
var a = 2
console.log(obj.a) // 1
console.log(a) // 2
- 当创建对象后,调用需要先从obj中拿到地址, 然后再读取fn。
- 如果存储的是一个函数,引擎会将函数单独保存在内存中,然后再将函数的地址赋值给
fn属性
环境变量
- 函数是一个单独的值,所以可以在任何环境下执行
- 由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境。所以,
this就出现了,它的设计目的就是在函数体内部,指函数当前的运行环境。
function fn() {
console.log(this.x)
}
fn() // undefined
上面代码中,函数体里面使用了变量x。该变量由运行环境提供。fn()运行在全局环境中,指向他本身的window对象,window对象中没有变量x,所以报undefined。
var obj = {
fn: function() {
console.log(this.a);
},
a:1
};
obj.fn() // 1
当我们执行obj.fn时,是通过obj对象找到的fn执行,所以它的运行环境是obj对象,this指向obj。
回到开头,一旦var foo = obj.foo,变量foo就直接指向函数本身,所以foo()就变成在全局环境执行。
注意
var具有变量提升,使用它创建的变量可以变成全局变量,可以直接通过window调用- es6新增的
let,const创建的变量,可以单独变成一个块级作用域,没有添加到window上,不能通过全局的this调用。
this的指向
this说白了就是找拥有当前上下文的对象。
谁调用,指向谁
- 在普通情况下就是全局,浏览器里就是
window;在use strict严格模式下,就是undefined。
function fnThis () {
console.log(this)
}
function fnStrictThis () {
'use strict'
console.log(this)
}
fnThis() // window
fnStrictThis() // undefined
谁调用,指向谁说白了就是找这个函数前面的点.。
var obj = {
fn: function () {
console.log(this);
},
};
obj.fn() // obj
直接上一道面试题
var obj1 = {
fn1() {
console.log(this);
},
};
var obj2 = {
fn2() {
return obj1.fn1();
},
};
var obj3 = {
fn3() {
var fn = obj1.fn1;
return fn();
},
};
obj1.fn1(); // boss1
obj2.fn2(); // ?
obj3.fn3(); // ?
答案是obj1和window。
在obj2.fn2里,使用this的函数是obj1.fn1,所以this绑定到obj1;
在obj3.fn3里,使用this的函数是fn,所以this绑定到window。
如果this要绑定到obj2呢?
var obj1 = {
fn1() {
console.log(this);
},
};
var obj2 = {
fn2: obj1.fn1,
};
obj2.fn2() // obj2,只要使用this的函数是obj2就可以
改变this的指向
Object.prototype.call,Object.prototype.apply和Object.prototype.bind可以改变this的指向。
function changeThis () {
console.log(this)
}
var obj = { name: '张三' }
changeThis() // window
changeThis.call(obj) // obj
changeThis.apply(obj) // obj
Object.prototype.bind,他不但通过一个新函数来提供永久的绑定,还会覆盖call和apply的命令。
function returnThis() {
console.log(this);
}
var obj = { name: "obj" };
var bindThis = returnThis.bind(obj);
bindThis(); // obj
var obj2 = { name: "obj2" };
bindThis.call(obj2); // obj
bindThis.apply(obj2); // obj
实例化后的this
- 当我们使用
new实例化一个函数时,调用这个函数。this指向这个函数本身,当改变自身this的指向时,会报错。 - 使用
bind改变this的指向时,如果这个对象使用new实例化,会覆盖bind的指向,还指向本身。 - 所以只要使用
new实例化的函数,都会指向本身这个函数
function FnThis() {
console.log(this);
}
FnThis() // window
var newThis = new FnThis() // FnThis
var obj = {name:'obj'}
FnThis.call(obj) // obj
FnThis.apply(obj) // obj
newThis.call(obj) // TypeError
var bindThis = FnThis.bind(obj)
bindThis() // obj
new bindThis() // FnThis
箭头函数
在es6中,新增了一种箭头函数。
箭头函数中没有this: 这意味着 call() apply() bind()无法修改箭头函数中的this- 箭头函数中的this指向 :访问上一个作用域的this
var funs = () => {
console.log(this);
};
funs(); // window
var obj = {
cb: () => {
console.log(this);
},
};
obj.cb(); // winodw
var obj2 = {
fn1: function () {
console.log(this); //obj2
//在局部作用域声明一个箭头函数
let fn2 = () => {
// fn2是一个箭头函数, 所以this访问的是上一级作用域中的this
console.log(this); //obj2
};
fn2();
},
};
obj2.fn1();
所以对于箭头函数,只要看它在哪里创建的就行。