js词法作用域与this实例详解

目录
  • 前言
  • 实践
  • 总结

前言

静态作用域又叫做词法作用域,采用词法作用域的变量叫词法变量。词法变量有一个在编译时静态确定的作用域。词法变量的作用域可以是一个函数或一段代码,该变量在这段代码区域内可见(visibility);在这段区域以外该变量不可见(或无法访问)。词法作用域里,取变量的值时,会检查函数定义时的文本环境,捕捉函数定义时对该变量的绑定。大多数现在程序设计语言都是采用静态作用域规则,如 C/C++ 、 C# 、 Python 、 Java 、 JavaScript …… 相反,采用动态作用域的变量叫做动态变量。只要程序正在执行定义了动态变量的代码段,那么在这段时间内,该变量一直存在;代码段执行结束,该变量便消失。这意味着如果有个函数 f,里面调用了函数 g,那么在执行 g 的时候,f 里的所有局部变量都会被 g 访问到。而在静态作用域的情况下,g 不能访问 f 的变量。动态作用域里,取变量的值时,会由内向外逐层检查函数的调用链,并打印第一次遇到的那个绑定的值。显然,最外层的绑定即是全局状态下的那个值。采用动态作用域的语言有 Pascal 、 Emacs Lisp 、 Common Lisp (兼有静态作用域)、 Perl (兼有静态作用域)。C/C++ 是静态作用域语言,但在宏中用到的名字,也是动态作用域。

实践

思考这么一段代码:

function fun1() {
  var a = 2
  console.log(a)
}

function fun2() {
  var a = 3
  console.log(a)
  fun1()
}
 fun2()

答案会是多少呢?


2

当然这个很好理解,js 是函数作用域,fun1 内有 a,当然会打印 fun1 内的 a 的值,并没有特殊之处,可是这个代码呢:

var a = 2
function fun1() {
  console.log(a)
}

function fun2() {
  var a = 3
  console.log(a)
  fun1()
}
 fun2()

答案和之前依旧一样,是不是这样就有点反直觉了? fun1 内没有 a 的情况下不是应该读取 fan2 的 a 吗?为什么会读取 全局作用域的 a 呢?说好的作用域是一层一层向上的呢?

当然,作用域确实是向上查找,可是 js 是静态作用域(词法作用域),并不是动态作用域,所以他不会看函数的调用位置,而是定义位置,并且沿着定义位置向上查找。词法作用域和动态作用域的区别如下:

  • 词法作用域是在 代码解析(定义) 的时候确定的,关注的是函数在 何处定义 ,并从定义处向上查找作用域。
  • 动态作用域是在 代码运行 的时候确定的,关注的是代码在 何处调用 ,并从调用栈向上查找作用域。

所以现在很好理解,为什么 fun1 内没有 a 他会先去读取全局的 a,而不是 fun2 的 a 了吧?不信可以看这个代码:

function fun1() {
  console.log(a) // a is not defined
}

function fun2() {
  var a = 3
  console.log(a)

  fun1()
}
fun2()

当然,js 有个特殊之处,就是 this,思考这段代码:

this.a =  2

function fun1() {
  console.log(this.a) // 1
}

function fun2() {
  this.a = 1
  console.log(this.a) // 1

  fun1()
}
fun2()

是不是疑惑了,说好的从定义的地方向上查找呢,为什么会打印出执行的作用域的值?

这里可以先说答案:因为 this

this.a =  2
// this 指向 window

function fun1() {
  // 这里 this 还是指向 window
  console.log(this) // window
  console.log(this.a) // 1
}

function fun2() {
  // this 依旧指向 window,不信可以打印看看那
  console.log(this.a) // 2

  // 这里修改了外边的 this.a
  this.a = 1
  // 打印修改后的值
  console.log(this.a) // 1

  fun1()
}
fun2()

所以明白了吧? 作用域依旧是在定义的地方向上查找,只不过是两个函数都指向了同一个 this 而已。

这里插一嘴,虽然我认为 js 的 this 是一个设计的非常糟糕的东西(他完全不符合正常人的思维逻辑),我也非常非常久都不再使用 this,但是我认为这个东西还是必须得理解的,不然早晚会搞出大麻烦,你可以不用,但是你必须要懂。

Ok, 接着上面所说,为什么两个函数指向了同一个 this(window)?这里就要深入的了解一下 this 的指向问题:this 究竟指向哪里,是都指向 window 么?显然不是,看一下代码:

this.n = 1

function fun2() {
  console.log(this.n) // 2
}
var a = {
  n: 2,
  fun1() {
    console.log(this) // {n: 2, fun1: function}
    console.log(this.n) // 2
    a.fun2()
  },
  fun2
}

a.fun1()

这里的 fun1 的 this 明显指向了 a 本身,并不是 this,同样 fun2 虽然定义在外部,但是也依然指向了 a ,是不是和之前想的不太一样?fun2 定义在外边,那么他的 this 应该是 window 才对,打印的应该是 1 才对啊,可能这个时候你就在想了,是不是 this 就是动态作用域呢?并不! This 依旧是静态作用域,参考这个代码:

this.n = 1

function fun2() {
  console.log(this.n) // 1
}
var a = {
  n: 2,
  fun1() {
    fun2()
  },
  fun2
}

a.fun1()

发现区别了吗?this 依旧是指向 window,这就说明 this 只是在定义的时候强行绑定了执行他的环境,所以我们通过 a.fun2 调用,this 就指向 a,通过直接调用 fun2(实际等于 window.fun2),指向的则是 window

当然也有例外,比如箭头函数:

this.n = 1

const fun2 = () =>  {
  console.log(this.n)
}
var a = {
  n: 2,
  fun1() {
    // console.log(this)  // {n: 2, fun1: function}
    // console.log(this.n) // 2
    a.fun2()
    // fun2()
  },
  fun2
}

a.fun1()

箭头函数中,不管你是 a.fun2 还是直接 fun2,指向的都是window,因为箭头函数的 this 固定指向他的父作用域,而根据静态作用域的原则,他父作用域是定义时的作用域,也就是 window,所以不管怎么调用,他都是 window。通过以下这个例子更能看出来这一点,箭头函数的 this 固定指向他定义的作用域:

var n = 1
var a = () => {
    console.log(this.n)
}

var b={
    n: 2,
    fun2: {
        n: 3,
        fun1:a,
        fun() {
            a() // 1
            console.log(this) // {n: 3,     fun1:function, fun: function}
            console.log(this.n) // 3
            this.fun1() // 1

        }
    }
}
b.fun2.fun()

通过这个你就能发现,箭头函数的 this 并不指向调用他的对象,也不是指向调用他的对象的父作用域,而是指向他定义的位置的父作用域,不管你在哪里调用,都是同一个指向。

总结

总结一下,对于this,你只需要记住这几点:

  • 正常情况下 this 指向调用他的上下文
  • 箭头函数的 this 指向他的父作用域的 this(静态作用域、静态作用域、静态作用域)
  • new 会创建一个新的对象,this 指向这个对象,详情可以自行了解 new
  • callbindapply 会改变 this 的指向,详情自行了解
a.xx()
xx 内的 this 就是 a
a.b.xx()
xx 内的 this 就是 b

.xxx,. 之前的上下文就是他的 this。 而在非严格模式的全局环境中(严格模式会报错),实际我们定义的变量都是挂载在 window 下,所以 this 指向的是 window

到此这篇关于js词法作用域与this的文章就介绍到这了,更多相关js词法作用域与this内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • javascript中this的四种用法

    this 在函数执行时,this 总是指向调用该函数的对象.要判断 this 的指向,其实就是判断 this 所在的函数属于谁. 在<javaScript语言精粹>这本书中,把 this 出现的场景分为四类,简单的说就是: 有对象就指向调用对象 没调用对象就指向全局对象 用new构造就指向新对象 通过 apply 或 call 或 bind 来改变 this 的所指. 1) 函数有所属对象时:指向所属对象 函数有所属对象时,通常通过 . 表达式调用,这时 this 自然指向所属对象.比如下面的

  • javascript this用法小结

    this是面向对象语言中的一个重要概念,在JAVA,C#等大型语言中,this固定指向运行时的当前对象.但是在javascript中,由于 javascript的动态性(解释执行,当然也有简单的预编译过程),this的指向在运行时才确定.这个特性在给我们带来迷惑的同时也带来了编程上的 自由和灵活,结合apply(call)方法,可以使JS变得异常强大.2.变化的this 在JavaScript中,this通常指向的是我们正在执行的函数本身,或者是指向该函数所属的对象(运行时).当我们在页面中定义

  • javascript 词法作用域和闭包分析说明

    复制代码 代码如下: var classA = function(){ this.prop1 = 1; } classA.prototype.func1 = function(){ var that = this, var1 = 2; function a(){ return function(){ alert(var1); alert(this.prop1); }.apply(that); }; a(); } var objA = new ClassA(); objA.func1(); 大家应

  • JavaScript进阶(二)词法作用域与作用域链实例分析

    本文实例讲述了JavaScript词法作用域与作用域链.分享给大家供大家参考,具体如下: 一.作用域 域表示的就是范围,即作用域,就是一个名字在什么地方可以使用,什么时候不能使用.想了解更多关于作用域的问题推荐阅读<你不知道的JavaScript上卷>第一章(或第一部分),从编译原理的角度说明什么是作用域.概括的说作用域就是一套设计良好的规则来存储变量,并且之后可以方便地找到这些变量. 1.1 块级作用域 在C.Java.C#等编程语言中,下面的语法报错(伪代码) { var num = 12

  • js中的this关键字详解

    this是Javascript语言的一个关键字. 它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用.比如, 复制代码 代码如下: function test(){ this.x = 1; } 随着函数使用场合的不同,this的值会发生变化.但是有一个总的原则,那就是this指的是,调用函数的那个对象. 下面分四种情况,详细讨论this的用法. 情况一:纯粹的函数调用 这是函数的最通常用法,属于全局性调用,因此this就代表全局对象Global. 请看下面这段代码,它的运行结果是1.

  • 深入理解JavaScript高级之词法作用域和作用域链

    主要内容:1.分析JavaScript的词法作用域的含义 2.解析变量的作用域链 3.变量名提升时什么 最近在传智播客讲解JavaScript的课程,有不少朋友觉得JavaScript是如此的简单,但是又如此的不知如何使用,因此我准备了一些内容给大家分享一下. 这个系列主要讲解JavaScript的高级部分的内容,包括作用域链.闭包.函数调用模式.原型以及面向对象的一些东西. 在这里不包含JavaScript的基本语法,如果需要了解基础的同学可以到http://net.itcast.cn里面去下

  • JavaScript词法作用域与调用对象深入理解

    关于 Javascript 的函数作用域.调用对象和闭包之间的关系很微妙,关于它们的文章已经有很多,但不知道为什么很多新手都难以理解.我就尝试用比较通俗的语言来表达我自己的理解吧. 作用域 Scope Javascript 中的函数属于词法作用域,也就是说函数在它被定义时的作用域中运行而不是在被执行时的作用域内运行.这是犀牛书上的说法.但"定义时"和"执行(被调用)时"这两个东西有些人搞不清楚.简单来说,一个函数A在"定义时"就是 functio

  • 深入理解javascript作用域第二篇之词法作用域和动态作用域

    前面的话 大多数时候,我们对作用域产生混乱的主要原因是分不清楚应该按照函数位置的嵌套顺序,还是按照函数的调用顺序进行变量查找.再加上this机制的干扰,使得变量查找极易出错.这实际上是由两种作用域工作模型导致的,作用域分为词法作用域和动态作用域,分清这两种作用域模型就能够对变量查找过程有清晰的认识.本文是深入理解javascript作用域系列第二篇--词法作用域和动态作用域 词法作用域 第一篇介绍过,编译器的第一个工作阶段叫作分词,就是把由字符组成的字符串分解成词法单元.这个概念是理解词法作用域

  • 网易JS面试题与Javascript词法作用域说明

    调用对象位于作用域链的前端,局部变量(在函数内部用var声明的变量).函数参数及Arguments对象都在函数内的作用域中--这意味着它们隐藏了作用域链更上层的任何同名的属性. 2010年9月14日,我去参加网易网页工程师招聘会,应聘JS工程师职位.有幸参加笔试,然后有幸栽在笔试,呵呵.废话少说,抓出音响极深的一题重新研究研究. 题目大概是:写出如下代码的输出结果并进行分析 复制代码 代码如下: var tt = 'aa'; function test(){ alert(tt); var tt

  • js词法作用域与this实例详解

    目录 前言 实践 总结 前言 静态作用域又叫做词法作用域,采用词法作用域的变量叫词法变量.词法变量有一个在编译时静态确定的作用域.词法变量的作用域可以是一个函数或一段代码,该变量在这段代码区域内可见(visibility):在这段区域以外该变量不可见(或无法访问).词法作用域里,取变量的值时,会检查函数定义时的文本环境,捕捉函数定义时对该变量的绑定.大多数现在程序设计语言都是采用静态作用域规则,如 C/C++ . C# . Python . Java . JavaScript …… 相反,采用动

  • js对象的读取速度实例详解

    1.访问字面量和局部变量最快,而访问数组元素和对象成员相对较慢.访问对象成员时,就像作用域链一样,在原型链上搜索. 2.如果找到的成员在原型链中的位置太深,访问速度就会变慢. 所以要尽量减少对象成员的搜索次数和嵌套深度. 实例 // 进行两次对象成员查找 function hasEitherClass(element, className1, className2) { return element.className === className1 || element.className ==

  • Vue.js框架路由使用方法实例详解

    Vue.js框架路由使用方法实例详解 html代码: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name='viewport' content='width=device-width,initial-

  • Vue.js进行查询操作的实例详解

    Vue.js进行查询操作的实例详解 实例代码: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <script src="../lib/vue.min.js" type="text/javascript" ></script> <title>字符转换</title> </head>

  • JS JSOP跨域请求实例详解

    在项目开发中遇到跨域的问题,一般都是通过JSONP来解决的.但是JSONP到底是个什么东西呢,实现的原理又是什么呢.在项目的空闲时间可以好好的来研究一下了. 1.什么是JSONP? 要了解JSONP,不得不提一下JSON,那么什么是JSON? JSON is a subset of the object literal notation of JavaScript. Since JSON is a subset of JavaScript, it can be used in the langu

  • 原生js中ajax访问的实例详解

    原生js中ajax访问的实例详解 form表单中 登录名: 失去光标即触发事件 function createXmlHttp() { var xmlHttp; try { // Firefox, Opera 8.0+, Safari xmlHttp = new XMLHttpRequest(); } catch (e) { try {// Internet Explorer xmlHttp = new ActiveXObject("Msxml2.XMLHTTP"); } catch (

  • jQuery.datatables.js插件用法及api实例详解

    1.DataTables的默认配置 $(document).ready(function() { $('#example').dataTable(); } ); 示例:http://www.guoxk.com/html/DataTables/Zero-configuration.html 2.DataTables的一些基础属性配置 "bPaginate": true, //翻页功能 "bLengthChange": true, //改变每页显示数据数量 "

  • 微信小程序引用公共js里的方法的实例详解

    微信小程序引用公共js里的方法的实例详解 一个小程序页面由四个文件组成,一个小程序页面的四个文件具有相同路径与文件名,由此我们可知一个小程序页面对应着一个跟页面同名的js文件.可是当有些公共方法,我们想抽离出来成为一个独立公共的js文件.我们该如何实现呢. 在根目录下有一个app.js文件.这个根目录的js 文件我们可以通过getApp()轻松调用. //app.js App({ globaData:'huangenai' }) //test.js var app = getApp(); Pag

  • Android中webview与JS交互、互调方法实例详解

    Android中webview与JS交互.互调方法实例详解 前言: 对于试水的功能,一般公司都会采用H5的方式来开发,可以用很少的资源与很短的项目工期来完成. 但许多情况下,H5页面会需要一些原生持有的一些如用户信息之类的数据,一些交互也需要调用原生的,如toast之类要保持同一个手机风格一致的交互行为.这个时候就需要能够让JS主动调用原生的方法来进行操作或者获取数据.或者是原生调用JS的方法在H5加载的时候传递一些参数. 对于原生调用JS的方法 我们需要实现一个WebViewClient,在这

  • JS三级联动代码格式实例详解

    这篇文章主要介绍了JS三级联动代码格式实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 实现js多级联动的代码格式 <head runat="server"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title></title> </h

随机推荐