关于this
this关键字作为JavaScript开发者必须理解的概念之一,其实并没有那么高深。
what
Q:什么是this? A:this代表的是函数运行的上下文环境。 Detail:
在所有语言中,都有一个调用栈的概念,即函数被哪个对象所调用(注意JavaScript中对象的概念,几乎所有的方法,参数都有一个宿主对象,其中顶层对象在浏览器中window,在node中则为global)。所以我们再执行一个函数或调用一个变量时,默认是带有一个顶层对象前缀的,不过由于我们所有的代码都在该对象中,则可以省略,不信可以试一下:
var num = 0;
console.log(num);
console.log(window.num);
没错,它们的结果都是0。
回归正题,所以粗略的来说,函数被调用的对象即为他的this指向(先不谈硬绑定),最简单的例子:
var obj = {
num : 0,
func: function(){
console.log(this.num);
}
}
obj.func();
执行结果是什么呢? 没错,是0; 因为这里的func函数由obj调用,所以this指向obj。 OK,那么再看一下下面这段代码:
var num = 0;
var obj = {
num : 1,
func: function(){
console.log(this.num);
}
}
var outterFunc = obj.func;
outterFunc();
执行结果又是什么呢?have a try! 可能出乎你的意料,结果是0. So,why?思考一下我们上面的解释。
没错,也许你想通了,因为这时候的func函数并不是通过obj来调用的,所以this默认指向window,但window中num变量为0,所以结果是0.
在这之中,我们需要了解的是:在JavaScript中,函数,对象,数组,或者说所有对象即对象的子类(因为包括函数,数组皆为对象的子类)都是通过地址的形式存储,类似与C语言中的指针形式存储
思考下面代码:
var func = function(){console.log(123)}
在JavaScript引擎中时如何运行完这条语句的呢? (你需要了解的是JavaScript不是一门预编译语言,而是一门解释执行的语言(即执行一句,编译一句,当然这是不完全正确的,从变量提升即可以看出来))
- 查询是否存在func这个变量---否
- 声明这个变量
- 定义函数
function(){console.log(123)} - 将该函数存储于内存中,并取得其地址
- 将该地址赋值给func变量
所以func变量实际存储的是该函数的地址。 所以函数实际上是没有存储作用域链中的任何信息,它总是一个独立存在的个体。 这也就解释了为什么func虽然定义在obj中,但是通过某种方式提取出来后直接调用其this就指向了window。
所以你大概已经明白了了吧,思考一下下面的代码:
var num = 0;
function inner()
{
console.log(this.num)
}
function outter()
{
var num = 1;
inner();
}
outter();
所以,结果是什么呢?
我猜你肯定答对了,是0;
同样的道理,inner函数虽然在outter函数中被调用,但任然是直接调用,没有任何前缀对象,所以其this指向任然是window。
最后一个例子:
var name = "clever coder";
var person = {
name : "foocoder",
hello : function(sth){
var that = this;
var sayhello = function(sth) {
console.log(that.name + " says " + sth);
};
sayhello(sth);
}
}
person.hello("hello world");//foocoder says hello world
这里内层函数this指向的任然是window,有人认为是JavaScript的设计错误,但是从上面内存的角度去分析,会发现这是正确的。
why & where
说了这么多this,那么为什么要用this呢?又在哪里用呢?
Q:why?
A:this的使用使调用上下文对象变得更加简洁,否则,每次调用函数必须传递上下文对象,编码将及其复杂。
Q:where?
A:相信接触过OOP(面向对象编程)的同学应该熟悉这个结构(伪代码):
Class Example{
constructor(name,age)
{
this.name = name;
this.age = age;
}
}
declare instan = new Example('tom',23);
没错,这是一个基本类的构造,只包括了一个constructor构造器方法,其中的this指向的即是这个被实例化的对象(instan),则instan的name属性为'tom',age属性为23。试想没有this的话,构造器该如何为实例属性赋值呢?只有显式的将instan传递给constructor函数,这将变得无比繁杂。
在js中,不仅是在类,即使是用行为委托方式编码,任然离不开this,因为总是存在对上下文对象的应用。
值得注意的是:JavaScript语言基础中并没有class的概念,即使ES6推出了class关键字,但它任然是使用原型链对类的模拟,任然是ES5部分框架class实现的语法糖
how
说了那么多,还是要详细讲一下this的绑定问题:
- 默认绑定
独立函数调用执行默认绑定。
var a = 0;
function func()
{
console.log('a:'this.a);
}
func();
//a:0
如上面我们所说,这里的func是直接的函数调用,所以执行默认绑定,this指向了window对象。
值得注意的是:在strict模式下,默认绑定this为undefined
var a = 0;
function func()
{
"use strict"
console.log('a:'+this.a);
}
func();
//Uncaught TypeError: Cannot read property 'a' of undefined
- 隐式绑定
这就是我们之前熟悉的用对象来调用函数:
var obj = {
num : 0,
func: function(){
console.log('num:'+this.num);
}
}
obj.func();
//num:0
当含有多层对象引用的时候,只有距函数最近的一个对象为上下文对象
var obj0 = {
num : 0,
func: function(){
console.log('num:'+this.num);
}
}
var obj1 = {
num:1,
obj0:obj0
}
obj1.obj0.func()
//num:0
正如我们之前所说的,函数的存储与上下文对象毫无关系,所以,当我们将对象中的函数通过某种方法提取出来时,它就与原来的对象毫无关系了,其this指向则为window了(这种现象一般被称为隐式丢失)。具体可以看上面那个例子。
- 显示绑定
所谓显示绑定,即通过call(),apply(),以及ES6的bind()函数直接指定this的指向。
var a = 0;
var obj = {
a: 1
}
function func()
{
console.log(this.a);
}
func.call(obj);
//1
值得注意的是:call(),apply()函数的绑定是软绑定,即只在绑定这一次起作用,下一次调用时this任然执行原有绑定规则。
所以就衍生出了硬绑定,ES6之前需要手动封装硬绑定方法:
function bind(fn,obj)
{
return function(){
return fn.apply(obj,arguments)
}
}
//执行此方法后,函数的this指向将被永久绑定在指定对象上,无法修改。
由于这个方法需求太广泛了,所以ES6推出了官方的bind()方法,直接调用即可。
- new绑定
与其他语言中构造函数的特殊性不同,在JavaScript中,构造函数是一个普通的函数,唯一的特殊点是它在执行new操作符后自动调用,并且开始执行一系列操作:
- 创建一个新的对象
- 这个新对象会被执行[[prototype]]连接(即将
__proto__指定为函数的prototype)。 - 这个对象将会被绑定到对应的函数的this。
- 如果函数没有返回其他对象,那么new表达式中函数调用会自动返回这个新对象。
第四步解释:
function Fun()
{
return {
b:2
}
}
var instan = new Fun()
//{b:2}
优先级
- 如果是new绑定,则this按上面的规则绑定对象。
- 如果是显示绑定,则this指向显示绑定的对象。
- 如果有隐式绑定,则this绑定在调用对象上。
- 否则执行默认绑定,非严格模式下为window,严格模式下位undefined。
箭头函数 =>
在ES6中,新加了一种声明函数的方式,箭头函数(=>)
()=>{} 等价于 function(){}
关于箭头函数的特性就不具体细讲,他与this相关的就是: 箭头函数的this决定于定义函数时的外层作用域来决定:
//arrayFunc
var a = 0;
//定义全局变量a
function fun() {
return (arrayFunc = ()=>{
console.log(this.a);
})()//这是一个立即执行函数,也可以在外围多调用一次
}
var obj = {
a : 1,
func: fun()
}
fun.call(obj);//将fun的this指向obj
//1
由于fun的this指向obj,而箭头函数的this根据外围函数的this决定,所以arrayFunc的this也指向obj,则a为1。
//normal
var a = 0;
function fun() {
return (function normalFunc(){
console.log(this.a);
})()
}
var obj = {
a : 1,
func: fun()
}
fun.call(obj);
//0
这里普通函数的this根据调用的对象来确定,由于它是单独调用的,所以this指向window,则a为0。
Last
this的用法相当重要,不管是自己原生开发,或是用框架,特别是使用框架时,由于一般框架会有一个App实例,我们的操作都在这个实例之中进行,所以会无数次用到this,所以我们必须学通。下一期写一下Protype原型链,也是JavaScript中相当重要的一个内容。
看看这
更多有趣文章都在公众号-全站学习小师兄