this 是 javascript 中的一个关键字,但是它又是一个相对比较特殊的关键字,不像var let const 这些关键字一样,可以很清楚的搞明白它到底是如何使用的.
this会在执行上下文中绑定一个对象,但是是根据什么条件绑定的呢?在不同的执行条件下会绑定不同的对象,这也是让人捉摸不定的地方。
最简单的情况就是this在默认情况下指向全局对象window
,
console.log(this); // window
var name = "why";
console.log(this.name); // why
console.log(window.name); // why
但是,在平时使用this的过程中,我们很少会直接在全局作用于中使用this,通常都是在函数中使用.
所以函数在被调用的时候,会创建一个执行上下文,这个上下文中巨鹿中函数的调用栈,调用方法,传入的参数信息等,this也是其中的一个属性;
我们先来看一个让人困惑的问题:
定义一个函数,我们采用三种不同的方式对它进行调用,它产生了三种不同的结果
function foo() {
console.log(this);
}
//1. 直接在全局作用域下调用函数
foo(); //window
// 2.调用方式二: 将foo放到一个对象中,再调用
var obj = {
name :'why',age : 18,foo :foo};
obj.foo(); //obj对象
// 3.调用方式三: 通过call/apply调用
foo.call("123"); // String {"123"}对象
从上面的代码中我们可以得知什么呢?
下面我们就来具体的学习一下this到底是怎么绑定的.
在什么情况下函数的this会进行一个默认的绑定呢??
function foo() {
console.log(this); // window
}
foo();
function test1() {
console.log(this); // window
test2();
}
function test2() {
console.log(this); // window
test3()
}
function test3() {
console.log(this); // window
}
test1();
function foo(func) {
func()
}
function bar() {
console.log(this); // window
}
foo(bar);
我们对案例进行一些修改,考虑一下打印结果是否会发生变化:
function foo(func) {
func()
}
var obj = {
name: "why",
bar: function() {
console.log(this); // window
}
}
foo(obj.bar);
隐式绑定指的是在调用的位置是否有上下文关系(是否被某个对象所包含),这种情况下谁调用指向谁
function foo() {
console.log(this);
}
var obj = {
name :'why',
foo :foo
};
obj.foo(); //obj对象
function foo() {
console.log(this);
}
var obj1 = {
name :'why',
foo :foo
};
var obj2 = {
name: 'gh',
obj1: obj1,
}
obj2.obj1.foo(); //obj1对象
所以说,一个多级对象在调用方法时,this绑定在离他最近的那个对象上
通常会有下面三种情况:
如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window,这里需要说明的是在js的严格版中this指向的是undefined;
如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象。
如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象,
隐式绑定有一种特殊情况,就是隐式丢失 当并不是直接调用某个函数或方法时,这时候会出现this绑定丢失的情况。
function foo() {
console.log(this);
}
var obj = {
name : 'why',
foo : foo,
};
var xyz = obj.foo;
xyz(); //window
function foo(){
console.log(this.a)
}
var obj = {
a:2,
foo: foo
}
function doFoo(fn){
fn()
}
var a = 'baz'
doFoo(obj.foo) // baz
通过前面的分析,我们可以得知,隐式绑定是由前提条件的:
有时候我们并不希望在对象内部引用或者定义函数,同时又希望在这个对象的身上能对这个方法进行强制的调用,这个时候我们应该怎么做呢???
JavaScript所有的函数都可以使用call和apply方法(这个和Prototype有关)。
因为上面的过程,我们明确的绑定了this指向的对象,所以称之为显示绑定。
通过call或者apply绑定this对象
function foo() {
console.log(this);
}
var obj = {
name : 'why',
};
foo.call(obj); //obj
foo.apply(obj); //obj
foo.bind(obj)(); //obj
有些时候,我们会调用一些JavaScript的内置函数,或者一些第三方库中的内置函数。
setTimeout(function() {
console.log(this); // window
}, 1000);
为什么这里是window呢?
数组有一个高阶函数forEach,用于函数的遍历:
var names = ["1", "2", "3"];
names.forEach(function(item) {
console.log(this); // 三次window
});
JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用new关键字。
使用new关键字来调用函数时,会执行如下的操作:
function Foo(a){
this.a = a
}
var foo = new Foo(2)
console.log(foo.a) // 2
//这就说明当前的this不是全局对象
箭头函数没有自己的this,当在北部使用了this时,它会指向最近一层作用域内的this,所以说箭头函数的this在定义时就已经确定了.
看了很多文章,都在说箭头函数指向最近一层作用域内的this,其实它也会分下面的两种情况
var name = 'why';
var obj = {
name: 'gh',
sayName :()=> {
console.log(this); //window
console.log(this.name); //why
}
}
obj.sayName()
var name = 'why';
var obj = {
name: 'gh',
sayName(){
(() => {
console.log(this); //obj
console.log(this.name); //gh
})()
}
}
obj.sayName()
像上面的立即执行函数如果是function的话就输出为全局a,箭头函数同样解决了一些问题,当我们在被函数包裹时就可以采取它然后就可以使用对象自身的方法以及属性值。
function foo()
{
this.user = 'haha';
return {
};
}
var a = new foo;
console.log(a.user); //undefined
function foo()
{
this.user = 'haha';
return function () {
};
}
var a = new foo;
console.log(a.user); //undefined
function foo()
{
this.user = 'haha';
return 1;
}
var a = new foo;
console.log(a.user); //haha
function foo()
{
this.user = 'haha';
return undefined;
}
var a = new foo;
console.log(a.user); //haha
上面的代码什么意思呢???
如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例。
function foo(){
this.user = 'haha';
return undefined;
}
var a = new foo;
console.log(a.user); //haha
console.log(a); //foo {user: "haha"}
还有一点就是虽然null也是对象,但是在这里this还是指向那个函数的实例,因为null比较特殊。
function foo(){
this.user = 'haha';
return null;
}
var a = new foo;
console.log(a.user); //haha
console.log(a); //foo {user: "haha"}
例一
function a(){
var user = "哈哈";
console.log(this.user); //undefined
console.log(this); //Window
}
a();
按照我们上面说的this最终指向的是调用它的对象,这里的函数a实际是被Window对象所点出来的.
例二
var o = {
user:"haha",
fn:function(){
console.log(this.user); //haha
}
}
o.fn()
这里的this指向的是对象o,因为你调用这个fn是通过o.fn()执行的,那自然指向就是对象o,这里再次强调一点,this的指向在函数创建的时候是决定不了的,在调用的时候才能决定,谁调用的就指向谁,一定要搞清楚这个。
例三
var obj = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a); //undefined
console.log(this); //window
}
}
}
var j = obj.b.fn;
j();
这里this指向的是window,是不是有些蒙了?其实是因为你没有理解一句话,这句话同样至关重要。
this永远指向最后调用它的对象,也就是看他执行的时候是谁调用的,上面的代码中虽然fn是被b对象引用了,按时将fn赋值给变量j的时候并没有直接执行,所以这里的this最终指向了window
当我们修改最后一行代码如下的时候:
var j = o.b.fn();
// 12 obj
此时的this才会指向obj对象
例四
var length = 100;
function f1() {
console.log(this.length);
}
var obj = {
length: 10,
f2 : function(f1) {
f1();
arguments[0](); //指向arguments,拿到arguments的长度值
}
}
obj.f2(f1,1);
//100
//2
例五
var name = "window";
var person = {
name: "person",
sayName: function () {
console.log(this.name);
}
};
function sayName() {
var sss = person.sayName;
sss();
person.sayName();
(person.sayName)();
(b = person.sayName)();
}
sayName();
//window
//person
//person
//window
例六
var name = 'window'
var person1 = {
name: 'person1',
foo1: function () {
console.log(this.name)
},
foo2: () => console.log(this.name),
foo3: function () {
return function () {
console.log(this.name)
}
},
foo4: function () {
return () => {
console.log(this.name)
}
}
}
var person2 = {
name: 'person2' }
person1.foo1();
person1.foo1.call(person2);
person1.foo2();
person1.foo2.call(person2);
person1.foo3()();
person1.foo3.call(person2)();
person1.foo3().call(person2);
person1.foo4()();
person1.foo4.call(person2)();
person1.foo4().call(person2);
解析:
// 隐式绑定,肯定是person1
person1.foo1(); // person1
// 隐式绑定和显示绑定的结合,显示绑定生效,所以是person2
person1.foo1.call(person2); // person2
// foo2()是一个箭头函数,不适用所有的规则
person1.foo2() // window
// foo2依然是箭头函数,不适用于显示绑定的规则
person1.foo2.call(person2) // window
// 获取到foo3,但是调用位置是全局作用于下,所以是默认绑定window
person1.foo3()() // window
// foo3显示绑定到person2中
// 但是拿到的返回函数依然是在全局下调用,所以依然是window
person1.foo3.call(person2)() // window
// 拿到foo3返回的函数,通过显示绑定到person2中,所以是person2
person1.foo3().call(person2) // person2
// foo4()的函数返回的是一个箭头函数
// 箭头函数的执行找上层作用域,是person1
person1.foo4()() // person1
// foo4()显示绑定到person2中,并且返回一个箭头函数
// 箭头函数找上层作用域,是person2
person1.foo4.call(person2)() // person2
// foo4返回的是箭头函数,箭头函数只看上层作用域
person1.foo4().call(person2) // person1
例七:
var name = 'window'
function Person (name) {
this.name = name
this.foo1 = function () {
console.log(this.name)
},
this.foo2 = () => console.log(this.name),
this.foo3 = function () {
return function () {
console.log(this.name)
}
},
this.foo4 = function () {
return () => {
console.log(this.name)
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')
person1.foo1()
person1.foo1.call(person2)
person1.foo2()
person1.foo2.call(person2)
person1.foo3()()
person1.foo3.call(person2)()
person1.foo3().call(person2)
person1.foo4()()
person1.foo4.call(person2)()
person1.foo4().call(person2)
解析:
// 隐式绑定
person1.foo1() // peron1
// 显示绑定优先级大于隐式绑定
person1.foo1.call(person2) // person2
// foo是一个箭头函数,会找上层作用域中的this,那么就是person1
person1.foo2() // person1
// foo是一个箭头函数,使用call调用不会影响this的绑定,和上面一样向上层查找
person1.foo2.call(person2) // person1
// 调用位置是全局直接调用,所以依然是window(默认绑定)
person1.foo3()() // window
// 最终还是拿到了foo3返回的函数,在全局直接调用(默认绑定)
person1.foo3.call(person2)() // window
// 拿到foo3返回的函数后,通过call绑定到person2中进行了调用
person1.foo3().call(person2) // person2
// foo4返回了箭头函数,和自身绑定没有关系,上层找到person1
person1.foo4()() // person1
// foo4调用时绑定了person2,返回的函数是箭头函数,调用时,找到了上层绑定的person2
person1.foo4.call(person2)() // person2
// foo4调用返回的箭头函数,和call调用没有关系,找到上层的person1
person1.foo4().call(person2) // person1