一、函数的本质
函数的本质是对象,函数名是指向函数对象的指针 。基于此,函数有以下特征。
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。