# 从本质上理解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>

# 所以我们就记住以下规则:

  • thiscall的第一个参数
  • new重新设计了this
  • 箭头函数不接受this
Last Updated: 8/5/2020, 5:39:29 PM