# 从本质上理解this指向问题
我们先来真正了解一下js
里面的函数,
- 特点:
- 有名字(匿名也行)
- 支持闭包
- 创建函数作用域
- *有
arguments
- *有
this
- *支持
new语法糖
- 是一个对象,有
name/lenght/call/apply/bind
属性 0~n
个参数、1
个返回值- 参数相同,返回值可能不同
- 注意:标有*的都是ES6中箭头函数不支持的。
我们再来复习一下js
中声明一个函数的四种方式:
//使用创建函数对象的方式
const f1 = new Function('x','y','return x+y')
//最普通的创建函数的方式
function f2(x,y){return x+y}
//创建匿名函数方式
const f3 = function(x,y){return x+y}
//ES6创建匿名函数的方式
const f4 = (x,y)=>x+y
//其中前三种方式是ES6之前的语法,支持this/arguments/new
//而第四种是ES6新语法,不支持this/arguments/new
//这里没有介绍generator函数和async函数
这里说的箭头函数不支持this
??箭头函数里面写this
不是不报错吗?为什么说它不知this
呢??
我们来继续看下面两段代码:
//代码1
const a = 233
const f2=>()=>console.log(a)
//代码2
console.log(this)
const f1=()=>console.log(this)
箭头函数如何处理a
,就如何处理this
,即箭头函数把this
当做外部变量,仅此而已,但是非ES6箭头函数的this就做了很多特殊处理。
我们这里说的箭头函数不支持this
指的就是箭头函数对this
和其他变量一视同仁,不会特殊处理。
说到这我们进入今天的正题,轻轻松松搞定this
指向!!!
# 非箭头函数的this
# 网上也有很多js函数this指向的相关理解,比如:
this
是执行上下文;- 全局环境执行函数时,
this
是全局对象; - 调用对象的方法时,
this
是该对象; - 函数里调用函数时,
this
是全局对象; - 箭头函数里的
this
,不看调用,看定义(静态作用域); - 还有人说箭头函数里的
this
指向外面的this
; - 用
new
调用函数时,this
是新增对象; - 可以用
call/apply/bind
指定this
;
# 我来说说我在方应杭老师教的方法:
我们先来回顾一下js中函数返回值的影响因素:
- 调用时输入的参数
params
(执行上下文) - 定义时的环境
evn
(词法环境)
那么this
是参数还是环境呢?
有人说this
是上下文,不是他们说的的不对,但是对于新手萌新来说就是理解不清楚,这种用概念去解释概念的说法,就是脱了裤子放屁。
我认为this
是参数,就是一个函数隐式的参数而已。
# this的确定:
其实所有的函数在调用执行的时候都是使用了.call
或者.bind
方法进行this
的绑定,只是分为了显式绑定和隐式绑定(默认绑定this)而已。
//显式绑定
fn.call(asThis,1,2)
fn.bind(asThis,1,2)()
obj.method.call(obj,'hi')
//默认绑定this(隐式绑定)
//我们现在代码都是省略的写法,本来很久以前创建函数都是需要绑定this的,但是js设计者为了让代码看起来跟简约,就约定了简便的写法,然后后来大家就不了解其本质。
//1.对于普通函数
fn(1,2)//等同于fn.call(undefined,1,2)
//2.对于对象中的方法
obj.method('hi')//等同于obj.method.call(obj,'hi')
//3.对于数组
array[0]('hi')//等同于array[0].call(array,'hi')
所以我们确定this
指向就直接可以把隐式绑定修改成显式绑定就可以看出来了。
也就是说我们只需要记住上面三种对普通函数、对js对象中的方法、对数组的隐式绑定,就可以了
来实战一道题:
const vm = new Vue({
data:{
message: 'hi'
},
metheds:{
sayHi(){
console.log(this.message) // this
}
}
})
//vm.sayHi.call(vm)
//改写成隐式绑定this
button.onclick = function(e){
console.log(this)
}
//答:button是错的。没有说是在这个代码在哪里写来执行的。
//改写成隐式绑定this:button.click.call(button,e)
//如果在浏览器中执行代码。用户触发点击事件的时候,会被改成,
//button.click.call(window,e)此时this又变成了window
//所以说不同的地方调用.call中指向是不一样的。
let length = 10
function fn(){
console.log(this.length)
}
let obj = {
length: 5,
method(fn){
fn()
}
}
obj.method(fn)//输出什么
//obj.method.call(obj,fn)此时this指向obj
//window.fn.call(window)此时this指向window
此时你是不是觉得会打印出:“10”
,答案是错的,是不是很懵逼哈哈哈哈。
一看到let
就要警觉一下了,ES6中const、let、class
声明的全局变量不属于顶层window对象属性,但是var、function声明的全局变量依然属于顶层对象window的属性。也就是说从ES6开始全局变量和顶层对象的属性就开始脱离、脱钩。
在我的浏览器上面打印出来的答案是“1”
,是不是很神奇,因为这个window.length
是window对象的内置属性,指的是frames
的数量。
我们再来看一道面试题:
<script>//在浏览器上运行此代码
var xuuu = 123
function t() {
var xuuu = 456
this.aa = 666
return function () {
console.log(xuuu);
console.log(this.aa);
console.log(this.xuuu);
}
}
t()()//结果:456 666 123
// 上面的代码等同于:
//var c = window.t.call(window)
// window.c.call(window)
var obj = new t()
obj()//结果:456 undefined 123
//此时上面代码等同于:
//var obj = new t()
//window.obj.call(window)
//为什么这时候this.aaa是undefined,因为new构造函数创建对象的时候,会执行这四步:
//1、创建一个新对象
//2、将构造函数的作用域赋给新对象(将this指向这个新对象)
//3、执行构造函数代码(为这个新对象添加属性)
//4、返回新对象
//所以在第二步的时候修改this执行到了obj对象上面,aaa是obj对象里面的属性,但是再次执行obj()的时候this是指向的window,所以说不能访问到obj里面的aaa,此时window.aaa是undefined
</script>
# 所以我们就记住以下规则:
this
是call
的第一个参数new
重新设计了this
- 箭头函数不接受
this