JS中的this(附arguments对象介绍)

188 阅读6分钟

arguments对象

定义

js允许函数有不定数目的实参,当函数调用时,有时候需要知道传进来多少个实参,所以需要一种机制可以获取到传递的实参信息,这就是arguments的由来。

使用arguments的length属性,可以获取调用时传入实参的个数。

与数组的关系

在函数中,arguments是所有参数的集合,是一个伪数组。

之所以是伪数组是因为arguments没有数组的prototype 可以看到arguments的__proto__没有指向数组的prototype,而是直接到了Object这个根原型。

如果想让arguments使用数组的方法,那么可以使用Array.from(arguments)把伪书组变成真正的数组

this

arguments属于js函数中的普通参数,而this是隐式参数。

this在函数中指向的是运行时的环境。

例如下面代码:

var fo={
    myname:'yan',
    fn:function(){
    	console.log(this.myname)}
    }
myname='xi';
var foo=fo.fn;
foo() //'xi'
fo.fn() //'yan'

造成两者的不同是因为运行环境的改变,在js中,当处于顶层的运行环境,this指向的是window。

而fo.fn()函数运行在fo对象环境下,所以造成两者结果不同。

那么,为什么obj.foo()就是在obj环境执行,而一旦var foo = obj.foo,foo()就变成在全局环境执行?

函数在对象中的内存图

假设现在有两个对象var obj={age:18}var obj2={fn:function(){}}

它的内存图结构是这样的

在stack中,分别保存了对象的地址,分别指向heap中的数据。

obj2中,虽然这个变量在stack中保存的是地址,但是在heap中实际的数据fn对应的值保存的是一个函数的地址,因为函数属于复杂数据结构,会另外开辟空间保存。

因为函数是一个单独的值,所以可以在不同环境下运行

var f =function(){}
f() //在全局环境下运行
var obj={f:f}
obj.f() //在obj环境下运行

运行环境的影响

运行环境会影响函数内部的变量

var f = function () {
  console.log(x);
};

上面代码中,函数体里面使用了变量x。该变量由运行环境提供。

那么,因为函数可以在不同的运行环境下运行,所以我们需要一种机制,在内部获取当前的运行环境。

现在我有一个需求

var person={
	name:qiuyanxi,
	say:function(){
		console.log(?)}
	}

怎样才能调用say的函数打印出person的name呢? 我可以写console.log(person.name)??

这样会造成一个问题,如果我修改了person这个对象名,所以也要同步修改函数中的person,一个函数还好,如果有非常多的函数,需要打印出person.ageperson.mum呢?

再问一个问题:

假设某个对象没有被创建出来呢?我要如何打印未来要创建出来的这个对象的name?

function Person(name){
	this.name=name //这里的this是强制的关键字,代表实例对象自己
}
Person.prototype.fn=function(){
console.log(xxx?.name)
	}

解决方法

解决方法是有的,就是用参数的方式传自己

var person={
	name:qiuyanxi,
	say:function(p){
		console.log(p.name)}
	}
person.say(person) //qiuyanxi
function Person(name){
	this.name=name //这里的this是强制的关键字,代表实例对象自己
}
Person.prototype.fn=function(p){
console.log(p.name)
}

var obj=new Person('ldh');
obj.fn(obj) //ldh

这种方式确实很傻,但是很容易让人理解。python用了这种方法,只是在调用时,把括号里的自己给隐藏起来了 python是把对象自己用了self关键字,代表着实例对象自己。

但js用了this,这个this在定义时候被隐藏了

class Person{
	constructor(name){
	this.name=name
}
say(//这里的this被隐藏了){
console.log(this.name)}
}

var pers=new Person('ldh');
pers.say() 

pers.say() 实际上就是把pers当成this传给say(),但是将过程都隐藏起来了。

  • this隐式地被当成是参数,不能写出来,写出来报错。在这点的理解上真的python好理解多了。【python是写了形参self,隐藏了实参自己,而js是将形参this跟实参自己都隐藏起来了】

需要注意一点,当你用了this关键字之后,pers.say(pers)这种写法虽然不报错,但并不是正确的。因为per会被当成是arguments而不是this。所以还是老老实实写pers.say()吧。

结论

通过上述,我们已经知道,this能够帮助在一个对象中找到当前对象(this)的属性或方法。

我们或许可以用一个结论来概述:this就是其直属的函数所属的对象

先来两个条件:

1、如果一个函数没有标明有直属对象,那么直属对象就是window。

2、let、const之后的变量不在window属性下,不属于global领域

来看一个例子:

let name='qiu';
let a={
	name:'da',
    say(){console.log(this.name)} 
}
let b=a.say
b() // ""
a.say() //"da"

上面代码中,由于a.say函数拷贝了地址给b,此时函数的运行环境已经发生了变化:

this的直属函数变成了b

b函数不属于window下的属性,此时没有直属对象。那么this就会找到window,window就会找它下面有没有name。代码第一行,由于let后的name属性不属于window,所以window就找到自己的name属性,最后输出为空。

总而言之,如果this所属的函数没有直属对象,那么就会找window。如果有,那么就找这个直属对象。

但是,上面都是基于普通函数而得出的结论,如果换成箭头函数则不行。

箭头函数中的this

来看几个隐晦的例子

let people = {
  name: '若愚',
  sayName() {
    setTimeout(function(){
      console.log(this.name)
    }, 0)
  }
}
window.name='123'
people.sayName() // '123'

上面代码中,this属于setTimeout,而setTimeout所属的对象是window,所以打印出来的是window.name。

如果把函数变成箭头函数呢?

箭头函数本身是没有this的,它会根据定义时的上下文往外捕获来捕获this。

let people = {
  name: '若愚',
  sayName:()=>{
      console.log(this.name)
    }
}
window.name='123'
people.sayName() //123

上面函数中,箭头函数挂在people.sayName中,但是此时箭头函数中的this会往外层查找,从而找到的是window,此时this变成了window。

再来看这个代码

let people = {
  name: '若愚',
  sayName() {
    setTimeout(()=>{
      console.log(this.name)
    }, 0)
  }
}
window.name='123'
people.sayName() //若愚

上面代码中,this的直属函数挂在window.setTimeout下,此时所属函数是window,但是由于是箭头函数,箭头函数本身是没有this的,里面的this会根据定义时的上下文往外层捕获,于是就找到sayName这个函数,捕获到的就是sayName函数所属的对象people

最后

最终我们得出结论: 1、普通函数下,this就是直属函数的所属对象

2、箭头函数下本身是没有this的,如果写了this,那么它会往定义时的上下文寻找并捕获外层的this。