阅读 106

JavaScript引用类型之函数和this指向

一、函数的本质

函数的本质是对象,函数名是指向函数对象的指针 。基于此,函数有以下特征。

1、函数没有重载,声明同名函数只是覆盖了函数的值 。

2、函数可以作为值传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。

二、函数调用方式

函数定义后,被调用时才执行,函数共有4种调用方式

1、作为函数

2、作为方法

3、作为构造函数

4、通过函数的call()和apply()方法调用

三、this的指向

this是函数的内部属性,this指向函数的调用上下文。函数的调用方式不同,this指向也会不同。

1、作为函数被调用(很重要)

非严格模式下,this指定window(全局对象),严格模式下,this指向undefined。

在react框架事件绑定中,事件触发后回调函数的执行不是实例调用的,而是作为函数调用的,而且开启了严格模式,默认回调函数中的this是undefined。

而且,无论函数作为值传递给另一个函数的参数,还是是作为另一个函数的结果返回,抑或是在函数(或方法)内嵌套调用,都可以认为this指向全局对象,如下所示。

var obj={
  m:function(){
		console.log('m',this);
		f()
		function f(){
			console.log('f',this);
		}
	}
}
obj.m()
输出的this分别是:
{m: ƒ}
Window {0: global, window: Window, self: Window, document: document, name: "", location: Location, …}
复制代码

要想在内嵌函数种能访问到对象,必须将this保存到和内嵌函数同一个作用域内,譬如这样:

var obj={
  m:function(){
		console.log('m',this);
		var self=this;
		f()
		function f(){
			console.log('f',self);
		}
	}
}
复制代码

2、作为方法

方法调用一般是在对象定义中定义了方法,对象实例调用方法,类似o.m()的形式。所以调用上下文(this指向)就是对象实例。

3、作为构造函数调用

函数作为构造函数调用时,this指向新创建的对象。

function Person(name,age){
	this.name=name;
	this.age=age;
}
var p1=new Person("Alice",20)
复制代码

以上创建对象的代码经历4个步骤:

(1)创建一个新对象

(2)让这个新对象来调用构造函数,即将构造函数的调用上下文赋给新对象(因此this就指向这个新对象)

(3)执行构造函数中的代码(为这个新对象添加属性)

(4)返回新对象

3、通过函数的call()和apply()方法调用

(1)Function.prototype.apply()

apply() 方法指定函数运行时使用的 this ,并以数组形式提供参数。

(2)Function.prototype.call()

call() 方法指定函数运行时使用的 this ,单独给出的一个或多个参数作为函数参数。

这两个方法作用效果是一样的,都是显示指定函数调用时的this值,强大之处在于能够扩充函数赖以运行的作用域,使得对象不需要与方法有任何耦合关系。任何函数都可以作为任何对象的方法调用,哪怕这个函数不是那个对象的方法。

var color="yellow";
var obj={color:"blue"}
sayColor()  //输出yellow
sayColor.bind(obj);  //输出blue
复制代码

除此之外,还有Function.prototype.bind()方法,这个方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。像apply方法和call方法,就像是一次性改变this工作,而bind,可以一次改变一直使用,不过就是返回了个新函数。

var color="yellow";
var obj={color:"blue"}
let sayColor1=sayColor.bind(obj)
sayColor1()  //输出blues
复制代码

四、箭头函数的this指向

箭头函数没有this这个内部属性,于是乎,箭头函数调用时,是沿着作用域链去寻找this属性,也就是说一直去找外层函数的this,直到找到为止。

developer.mozilla.org/zh-CN/docs/…

var id=21
function foo(){
	var id=12;
	console.log("foo ",this.id);
	setTimeout(()=>{
		console.log("setTimeout ",this.id)
	})
}
foo()
输出:
foo  21
setTimeout  21
复制代码

上面代码中,定时器中箭头函数执行环境的作用域链是,箭头函数->setTimout->foo->全局。所以箭头函数的this沿着作用域链找到setTimeout函数的变量对象,this指向的正是window。所以输出的id值是21。又比如:

var obj = {
  i: 10,
  b: () => console.log(this),
  c: function() {
    console.log(this)
  }
}
obj.b()
输出Window全局对象
复制代码

上面代码中,箭头函数执行环境的作用域链是,箭头函数->全局,所以输出的是window。

再看下面的代码,是使用class定义的类,类中的方法是可以使用箭头函数的

class Animal {
    constructor(type) {
        this.type = type
    }
    walk() {
        console.log( 'walk ',this )
    }
    say=()=>{
	console.log('say',this)
    }
}
let a=new Animal("cat")
a.walk()
输出a这个实例对象 Animal {type: "cat", say: ƒ}
a.say()
输出a这个实例对象 Animal {type: "cat", say: ƒ}

var walk1=a.walk
walk1()
输出this是undedined
var say1=a.say;
say1()
输出this是a这个实例对象 Animal {type: "cat", say: ƒ}
复制代码

类中的方法默认开启了严格模式,this指向在严格模式下是undefined。

对象中walk()方法是非箭头函数定义的,say()方法是箭头函数定义的。

1、首先非箭头函数的this指向,是很容易理解的,a.walk()是对象实例调用的,指向对象实例,而walk1()是在全局执行环境中调用的,是作为普通函数调用,指向全局对象也没毛病。

2、但是箭头函数的this指向,就很让人费解。为什么a.say()和say1()输出的this都是指向实例对象?而这点广泛用于react组件实例的简写中。

且看a实例对象,输出如下所示,非箭头函数walk()是定义在原型上的,而箭头函数say()是定义在对象上的实例属性。

Animal {type: "cat", say: ƒ}
 1.say: ()=>{ 	console.log('say',this)     }
 2.type: "cat"
 3.[[Prototype]]: Object
    constructor: class Animal
    walk: ƒ walk()
    [[Prototype]]: Object
复制代码

凭借现有知识实在不明白class定义类时t箭头函数方法的指向。姑且认为在class定义类时,实例方法定义为箭头函数,this总是指向定义时所在的对象吧。而其他场景,则按照MDN上关于箭头函数this指向的说明,箭头函数没有this,沿着作用链查找this。

文章分类
前端
文章标签