关于this,首先我们要知道this究竟是什么,用官方语言说:
面向对象语言中 this 表示当前对象的一个引用。
但在 JavaScript 中 this 不是固定不变的,它会随着执行环境的改变而改变。
确切地说,this是当前环境执行期上下文对象的一个属性,不同的环境下,不同作用下,表现是不同的,用一句话概括this的指向的话,就是:this的指向,是在调用函数时根据执行上下文动态确定的
默认绑定规则
在全局作用下,this===window,在全局作用域下,window.a ===this.a,此时相当于this的指向为window
在匿名函数里,this也指向window
(注意,在严格模式下,全局作用时this绑定的是undefined)
在函数中,this指向window
` console.log(window === this)
//true
//函数的独立调用
function test() {
console.log(this === window)
}
//等价于window.test()
test();
//true
隐式绑定规则
对象调用时
谁调用就指向谁
`
每一个函数执行,都会有一个自身的this指向。函数执行,才会产生自身的this指向,this指向才有意义。因此每一个函数的this指向都不相同,但可能相等。每个函数都有独立的this。
`
let obj = {
a: 2,
foo: function() {
// 指向obj对象
console.log(this);
//指向window
function test() {
console.log(this)
}
//window调用,独立调用一定指向window
test();
//立即调用,依然是window调用,指向window
(function() {
console.log(this)
})()
function test() {
console.log(this)
}
// 产生闭包
return test;
}
}
//obj调用
obj.foo();
obj.foo()()//闭包执行
`
闭包:当函数执行的时候导致函数被定义,并抛出
闭包ruturn一个函数,在window下调用 (obj.foo()==test)
但是,一个按钮绑定的回调函数,如果不闭包this指向window,如果发生了闭包,那么this指向调用它的按钮
在隐式调用时还会有一些嵌套调用的情况,这时this的指向是最后调用它的对象
隐式绑定的例外情况
给出了一个对象,但没有在对象中执行(隐式丢失)
隐式调用的this指向与调用有关,与定义无关
`
let a = 1;
function foo() {
console.log(this)
}
let obj = {
a: 2,
foo: foo
}
//bar持有foo的引用
let bar = obj.foo;
//指向window,它在赋值后再执行,赋值后与原对象无关,是独立调用
bar();`
用函数作为参数
```javascript
` function foo() {
console.log(this);
}
// 在编译阶段,实参被赋值为形参(值的浅拷贝)或者说fn获取到foo的引用,所以fn的执行方式就是fn的执行方式
// 父函数是有能力决定子函数的this指向的
function bar(fn) {
new fn();
fn(obj);
fn.bind(obj)();//指向this
}
let obj = {
a: 2,
foo: foo
}
//obj.foo是一个持有函数的引用,最终指向一个函数
bar(obj.foo)`
## 显式绑定
有些高阶函数的this指向是通过api中的参数指明的。比如foreach第二个函数就可以指明函数的this指向;没有则默认指向window;sort,setIntenval默认的指向也是window,可以通过参数更改
可以通过apply,call,bind绑定来更改this指向
```javascript
function foo(a, b, c) {
console.log(this);
console.log(a, b, c)
}
let obj = {
a: 2,
foo: foo
}
obj.foo(1, 2, 3);
let bar = obj.foo;
// bar(); //指向window
bar.call(obj, 1, 2, 3); //直接传参
bar.call(1, 2, 3); //this指向包装类Number
bar.apply(obj, [1, 2, 3]); //用数组收集参数
bar.apply(null, [1, 2, 3]); //传null则指向window(绑定失败,采用默认window)
bar.bind(obj)(1, 2, 3); //bind返回一个函数,在函数中传参
new绑定
使用了new,this就指向了实例化对象,这就是我们在vue中可以直接用this来调用data中值的原因,我们使用的vue就是一个实例化的对象
`
function Person() {
this.a = 1;
// return 1 //输出实例化对象
// return的值为引用值,就会改变当前的this指向
return {} //输出空
}
//这时this是实例化后的对象
var person = new Person();
console.log(person)`
new绑定的优先级大于显式绑定,显式绑定的优先级大于隐式绑定
// function foo(b) {
// this.a = b;
// }
// var obj1 = {}
// var bar = foo.bind(obj1)
// bar(2);
// console.log(obj1.a) //2
// var baz = new bar(3)
// // 此时bar是bind返回的一个新的函数
// console.log(obj1.a) //2,证明之前的obj1没有改变,new的优先级高,此时bind不执行
// console.log(baz.a) //3 证明new时this指向了构造函数的实例baz,
// // 只要有new,this一定指向实例对象,本质上实例对象就是构造函数返回的this
这里还有一些关于new的补充,在我们用new调用构造函数时,具体做了以下操作
1.创建一个新的对象
2、将构造函数的this指向这个新对象
3.为这个对象添加属性或方法
4、返回这个新对象
关于构造函数的this,我们还要注意构造函数返回的东西,如果返回一个对象,那么this指向这个返回的对象;如果返回的不是对象,那么this指向实例
```javascript
` function Foo() {
this.user = 'Mike'
const o = {
user: 'SAM'
}
return o;
}
const instance = new Foo()
console.log(instance.user) //SAM`
## 箭头函数
**箭头函数的指向取决于父环境中的this指向**
当我们需要在内层函数中调用外层函数的this,我们可以将this存起来,或者显式改变指向
```javascript
function foo() {
var that = this;
console.log(this)
function test() {
console.log(this)
console.log(that)
}
test.call(obj) //三个this全部指向obj
test.call(foo) //第二个this会指向foo()
}
var obj = {
a: 1,
foo: foo
}
obj.foo()
但我们这时也可以用箭头函数来改变this指向,让他们指向外层,箭头函数内部没有this指向,箭头函数的this取它父级的this
` function foo() {
console.log(this)
let test = () => {
console.log(this)
}
te`
这时两个this都指向对象obj
箭头函数本质上没有this,完全使用父级this
function foo() {
console.log(this)
let test = () => {
console.log(this)
}
return test
}
var obj = {
a: 1,
foo: foo
}
obj.foo()()
//此时第一个this由于隐式调用所以指向obj,第二个this的this与第一个相同,所以也指向obj`
在箭头函数中,显示绑定和隐式绑定都无法改变this的指向
`
function foo() {
console.log(this)
let test = () => {
console.log(this)
}
return test
}
var obj = {
a: 1,
foo: foo
}
var obj2 = {
a: 2,
foo: foo
}
//当使用箭头函数时,显式绑定规则无效,this仍然跟父元素一致
var bar = foo().call(obj2) //这时两次打印都是window`
在对象中使用箭头函数时,由于对象没有this,会继续往上找,最终this指向window
` var obj = {
a: 1,
foo: () => {
console.log(this)
}
}
obj.foo() //打印出window
`
箭头函数不允许作为构造函数使用。
最后看一波题
`
var name = 'window';
var obj1 = {
name: '1',
fn1: function() {
console.log(this.name);
},
fn2: () => console.log(this.name),
fn3: function() {
return function() {
console.log(this.name);
}
},
fn4: function() {
return () => console.log(this.name)
}
}
var obj2 = {
name: '2'
};
obj1.fn1(); //1
obj1.fn1.call(obj2); //2
obj1.fn2(); //window 在父作用域(window)找this
obj1.fn2.call(obj2); //window 箭头函数的调用规则不适用
obj1.fn3()(); //window 自调用指向window
obj1.fn3().call(obj2); //2 子调用,call生效
obj1.fn3.call(obj2)(); //window 父作用域的this指向obj2,子函数仍是自调用
obj1.fn4()(); //1 箭头函数不存在this,找到父作用域fn4,fn4对象调用,指向obj1
obj1.fn4().call(obj2); //1 箭头函数不适用于call,仍然指向obj1
obj1.fn4.call(obj2)(); //2 fn4先绑定到obj2,因此指向obj2`