JavaScript 中 this 是如何工作的 ?

先来看看这个题目:

1var x = 0; 2var foo = { 3 x:1, 4 bar:{ 5 x:2, 6 baz: function () { 7 console.log(this.x) 8 } 9 } 10} 11 12var a = foo.bar.baz 13foo.bar.baz() // 2 14a() //0
  • this 永远指向函数运行时所在的对象,而不是函数创建时所在的对象
  • 匿名函数和不处于任何对象中的函数,This指向window
  • call, apply, with指的This是谁就是谁。
  • 普通函数调用,函数被谁调用,This就指向谁

上面的例子中,baz被bar调用所以指向的指bar. a 运行时所在的对象是 window,所以指向的是window。

作用域链?

理解执行环境和上下文

函数调用都有与之相关的作用域和上下文。从根本上说,作用域是基于函数(function-based)而上下文是基于对象(object-based)。换句话说,作用域是和每次函数调用时变量的访问有关,并且每次调用都是独立的。上下文总是关键字 this 的值,是调用当前可执行代码的对象的引用。

执行上下文分有globalfunctioneval,一个函数可以产生无数个执行上下文,一系列的执行上下文从逻辑上形成了 执行上下文栈,栈底总是全局上下文,栈顶是当前(活动的)执行上下文。

执行上下文三属性:this指针,变量对象(数据作用域),作用域链

作用域链 即:一变量在自己的作用域中没有,那么它会寻找父级的,直到最顶层。过程如下:

  • 任何在执行上下文时刻的作用域都由作用域链来实现
  • 在一个函数被定义的时候, 会将它定义时刻的scope chain链接到这个函数对象的[[scope]]属性
  • 在一个函数对象被调用的时候,会创建一个活动对象(也就是一个对象), 然后对于每一个函数的形参,都命名为该活动对象的命名属性, 然后将这个活动对象做为此时的作用域链(scope chain)最前端, 并将这个函数对象的[[scope]]加入到scope chain中.

上面的文字大家可以好好琢磨一下,可以更好的理解函数作用域。

函数声明提升和变量声明提升(Hoisting) ?

我们先来了解js编译器在执行代码的过程:
以执行一段function代码为例:
第一步:创建可执行上下文(以下简称为EC),压入当前的EC栈中。EC中包括了以下信息:

  • 词法环境(=环境记录项(保存变量、函数声明和形参)+ 外部词法环境(function的[[scope]]属性,作用域链的本质))
  • this的指针
  • 变量环境(与环境记录项的值相同,但不再发生变动。)

第二步:收集函数声明变量声明形参,保存在环境记录项内。这个收集的过程,就是一般所谓的声明提升现象的本质。如果发现了重复的标识符,则优先级函数声明形参变量声明(优先级低的会被无视)。

第三步:开始执行代码,环境记录项内没有的标识符会根据作用域链查找标识符对应的值,环境记录项亦有可能因赋值语句而被修改。

第四步:函数执行完毕,EC栈被弹出、销毁。

好了,第二步说的很清楚了 声明提升(Hoisting)现象就是在收集函数、变量声明和形参的过程会根据函数声明、形参、变量声明的顺序优先级来收集。

例子:

1var a = 1; 2function b() { 3 a = 10; 4 return; 5 function a() {} } b(); console.log(a); // 输出1 由于函数声明提升,b内的实际是这样: // function b() { // function a() {}; 这里是函数声明提升 // a = 10; // return; // function a() {} // }

理解了吗?

勘误:谢谢github上有同学的指正 关于博客中的一个问题 · Issue #1 · stephenzhao/hexo-theme-damon,上面的正确执行应该为先进行预编译,所以先执行function a(){},然后会进行对a的赋值操作。

1//正确的顺序应该为: 2// function b() { 3// function a() {} // a = 10; // return; // }

什么是闭包,如何使用它,为什么要使用它?

还是上面的题目,做个变形。

1var x = 0; 2var foo = { 3 x:1, 4 bar:function () { 5 console.log(this.x); 6 var that = this; 7 return function () { console.log(this.x) console.log(that.x) } } } foo.bar() // 1 foo.bar()() // this: 0, that: 1

上面的例子中ba'r里面返回了一个匿名函数,这个匿名函数可以在外部被调用即:foo.bar()() 读取到了bar的执行上下文的变量对象 that,这个函数就形成了一个闭包。

好了,我们理解了上面的套路,下面来解释闭包就好理解了。

闭包就是能够读取其它函数内部变量的函数

在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”

1var x = 0; 2var bar:function () { 3 var n = 999; 4 return function () { return n; } } var outer = bar(); outer() // 999

用途:

  1. 读取函数内部的变量
  2. 让这些变量的值始终保持在内存中

我们修改一下上面的代码

1var add; 2var bar = function () { 3 var n = 999; 4 add = function () { 5 n += 1; 6 } 7 return function () { return n; } } var outer = bar(); outer() // 999 add(); outer(); // 1000

说明,n一直保存在内存当中,而没有在bar()执行完成之后被销毁;
原因:
bar里面的匿名函数被赋值给了outer,这个导致在outer没有被销毁的时候,该匿名函数一直存在内存当中,而匿名函数的存在依赖于bar,所以bar需要使用都在内存当中,所以bar并不会在调用结束后呗垃圾回收机制给收回。

而上面的add接受的也是一个匿名函数,该匿名函数本身也是闭包,所以也可以在外部操作里面的变量。

注意点

  1. 会导致内存泄漏,慎用
  2. 闭包会修改内部变量的值,所以在使用闭包作为对象的公用方法时要谨慎。
    闭包的一个应用,单例模式

单例模式的定义是产生一个类的唯一实例

单例模式在js中经常会遇到,比如 var a = {}; 其实就是一个单例子。

但是我们写一个更有意义的单例:

1var singleton = function( fn ){ 2 var result; 3 return function(){ 4 return result || ( result = fn .apply( this, arguments ) ); } }

更简洁一点的:

1var singleton = (function () { 2 var instance; 3 return function (object) { 4 if(!instance){ instance = new object(); } return instance; } })();

我的前端博客http://www.gaoyang521.top

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )