Javascript中作用域的修改以及with语句

背景

问题来自看JavaScript高级程序设计遇到了延长作用域的问题.不能理解文中解释的内容,于是网上查到一些相关文章.

首先需要了解的是,JavaScript中执行环境一共有两种:全局作用域和局部作用域.而在浏览器中.全局作用域是Windows对象.自下而上,局部作用域可以访问全局作用域(如果在子作用域中没有找到的话).反之则不可以.

但是使用with则可以起到延长作用域的作用.

摘抄相关文章

文章一

关于Javascript静态作用域的一些心得。

之前在看JS大牛BYVoid的<<NodeJS开发指南一书>>时, 学习到了最能说明Javascript静态作用域特性的代码, 如下:

var scope = "global";
function f1() {
    console.log(scope);
}
f1() // output: global
function f2() {
    scope = "f2"
    f1();
}
f2(); // output: global

第一次输出“global”,?

是因为f1()函数找到父级执行作用域下(全局作用域)有定义全局变量scope = “global“。

第二次输出”global“,

因为JS静态作用域链的特性使得作用域链是在函数定义时候被决定的,而不是在调用时候被决定的。

所以在f2()函数中执行f1()函数的时候,f1()函数的父级作用域仍然是全局作用域,所以依然会输出“global”。

那如何在不改变f1函数的结构和定义的情况下,使得在f2函数中输出的scope的变量的值可以是“f2”呢

使用with。

var scope = "global";
function f1() {
    console.log(scope);
}
f1() // output: global
function f2() {
    scope = "f2"
    with(scope) {
        f1();
    }
}
f2(); // output: f2

with会改变其范围内的作用域链。

这里with把在其范围内的f1()函数的执行作用域链的父级作用域更改为了自己,所以在执行f1()函数时,找到父级作用域链中scope定义为“f2”.

因此加入with后,第二次可以输出“f2”。

文章二

首先看看举的例子:

function buildUrl(){
     var qs="?debug=true";
     with(location){
          var url=href+qs;
     }
     return url;
}
var result=buildUrl(); 

alert(result); 

如果你没读过着本书,并且需要学习javascript,请思考并尝试运行该例子。

最后弹出的不是undefined,而是你的静态页地址+qs的值。

来看一下with语句的作用:通俗的说,就是引用对象,并对该对象上的属性进行操作,其作用是可以省略重复书写该对象名称,起到简化书写的作用。

但是有几个问题需要注意:

1、with代码块中,javascript引擎对变量的处理方式是:先查找是不是该对象的属性,如果是,则停止。如果不是继续查找是不是局部变量。(在《Javascript高级程序设计(第二版)》中提到的观点,跟这一点恰好相反,但是实例可证明其是错误的,会在接下来介绍)

2、就算在with语句中使用 var 运算符重新定义变量(该变量是with引用对象的属性),如果该属性是可写属性,那么也会给对象的属性赋值。

3、如果你想通过with语句,对引用对象添加多个属性,并为每个属性赋值,这是不可能的!也就是说,要赋值的只能是对象已经存在并且可以写入的属性(不能是只读属性)。

再来看看开头提到的那句话

“由于with语句的变量对象是只读的,结果url就成了函数执行环境的一部分,因而可以作为函数的值被返回。”

反过来:如果with语句的变量对象是可写入的…… 刚才第3点提过,不能给对象写入原来不存在的属性,先这样理解,下面还有另外的含义。

那延长作用域链又是怎么回事?

一般的,“由于with语句块中作用域的‘变量对象’是只读的,所以在他本层定义的标识符,不能存储到本层,而是存储到它的上一层作用域”。这里又要理解有一层“只读”的含义。

在Javascript的作用域中(作用域,想想就是函数块,每个函数都会有个函数名,就算是匿名函数也有个空函数名),那么创建作用域的时候,本层的标识符就可以寄托在这个作用域下,而with语句块中作用域的‘变量对象’是只读的,不能存储标识符,只能存储在其上一层,这就是延长作用域链。其实,这和上面说的不能给对象添加属性有同工之处。

其实,完全可以这样理解,在Javascript中,没有块级作用域,就是说除了函数,其他的块级代码都没有自己的作用域。

现在说一下之前提到的with代码块中变量处理方式的问题,用事实说话:

var o={href:"sssss"};
var href="1111";
function buildUrl(){
     var qs="?debug=true";
     with(o){
          href="2222";
          var url=href+qs;
     }
     return url;
}
var result=buildUrl();
alert(result);
alert(href);

结果:2222?debug=true + 1111

很明显,with语句中并没有更改变量href的值,而是更改了 o 对象的 href 属性。

就是说,with中首先查找的是相关对象的属性,如果没有,才改变变量的值。你可以将以上例子o对象的href属性去掉看看。

文章三

虽然执行环境的类型总共只有两种——全局和局部(函数),但还是有办法来延长作用域链的,这么说是因为有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。在两种情况下会发生这种现象。具体来说,就是当执行流进入下列任何语句时,作用域链就会得到加长:

try-catch语句的catch块。

with语句。

这两个语句都会在作用域链的前端添加一个变量对象。对with语句来说,会将指定的对象添加到作用域链中。对catch语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。下面看一个例子:

var mother = {
    name: ‘Rose‘,
    age: 30
}

function father(){
    var fName = ‘Jack‘;
    var name = ‘张三‘;

    function son(){
        var sName = ‘son‘;
        with(mother){
            var word = ‘my name: ‘ + sName + ‘, father name: ‘ + fName +
                ‘, mother name: ‘ + name + ", mother age: " + age;
        }
        console.log(word);//my name: son, father name: Jack, mother name: Rose, mother age: 30
    }
    son();
}
father();

首先声明一个对象mother,包含两个属性:name和age,然后定义一个函数father,里面声明两个变量:fName和name,又声明一个函数son,son中定义了一个变量sName和一个with语句块。我们来看打印的结果,mother name为对象mother中的Rose,而不是函数father中的张三,这就是with语句在起作用。

在Chrome中调试可以发现,如下图:

代码执行到19行with语句块中时,作用域链的前端增加了一个With Block作用域,根据作用域链搜索机制,此时的name应该是Rose。再看下图:

代码执行到22行,with语句块执行结束,with作用域从作用域链中移除,with语句块中声明的变量绑定在with所在的函数中,所以console.log(word)可以正常输出word。

参考

原文地址:https://www.cnblogs.com/gtscool/p/12585822.html

时间: 2024-10-10 05:45:08

Javascript中作用域的修改以及with语句的相关文章

JavaScript中作用域和预解析

作用域以及预解析 在javascript中作用域是非常重要的,本文章将会说明作用域以及我们在工作,以及面试中的一些面试题,如果有不足的地方希望大家可以评论指出来,自己一定会及时的改正错误,避免大家走入一些误区. 谈及作用域先就必须要说明预解析和词法作用域. 下面我们先说明一下: 预解析 代码在正常执行操作之前会对文档进行一次解析,这个操作就是将声明提升, 声明包括全局范围内 1.带有var的变量, 2.函数 文档预解析后会把文档中在全局函数中的内容储存起来,将全局中带有var的变量(var和变量

JavaScript中作用域链和闭包

一.匿名函数 1.1 匿名函数的概念 ? 声明一个没有函数名的函数,就是匿名函数. ? 有函数名的函数就是具名函数. 看下面的代码: <script type="text/javascript"> /* //这里定义了一个函数,而且没有函数名.这样写语法是错误的,如果允许这样定义,那么根本就没有办法调用. //所以,我们可以用一个变量来存储一下 function(){ } */ // 声明了一个匿名函数,并把匿名函数赋值给变量f. 注意这个时候这个匿名函数并没有执行. va

分析javascript中作用域

1.javascript中的作用域 正如其他语言一样,在javascript中,变量的作用域也有全局和局部作用域的划分. 2.全局作用域 1)所有在最外层定义(非函数体内定义)的变量拥有全局作用域 2)直接赋值的变量,自动默认为全局作用域 <span style="font-size:18px;"> <script> //函数体外 var name = "第一个name"; //全局变量 sex = "女"; //全局变量

JavaScript 中 申明变量的方式--let 语句

let 语句 - 声明一个块范围变量. 语法 1 let 变量名 = 初始化值; 例子 1 "use strict"; 2 let name = '赵敏'; 3 (function opt(){ 4 let name = '张无忌'; 5 console.log(name); 6 })(); 7 console.log(name+'1a'); 案例结果 1 张无忌 2 赵敏1a 总结 - let 语句创建的变量,作用域在于声明它的块中. - let 语句声明的变量,在声明之前不能使用.

JavaScript中---作用域

作用域: 变量还有函数作用的范围. 浏览器的内核主要有两大功能,一个是渲染页面,另一个就是我们的JavaScript的解释器了. 我们主要来说说JavaScript解释器,在解析时是怎么样的工作原理. 在解析的时候,首先会发生预解析,就是说不会直接一行一行去执行我们所写的代码,它会先去找有哪些声明的变量,遇见函数体的时候会把函数放到最上面的位置,当变量名和函数名重名的时候函数会替代变量声明. 在解析的时候碰到的是个函数体,那么会预先处理函数体中的变量或者函数,知道有哪些声明的事项. 如果是变量,

javascript中作用域

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <script type="text/javascript"> // 什么叫作用域 ?? // 从字面意思来理解: 起作用的那块区域 //

理解JavaScript中作用域链的关系

javascript里的关系又多又乱.作用域链是一种单向的链式关系,还算简单清晰:this机制的调用关系,稍微有些复杂:而关于原型,则是prototype.proto和constructor的三角关系.本文先用一张图开宗明义,然后详细解释原型的三角关系 概念 上图中的复杂关系,实际上来源就两行代码 function Foo(){}; var f1 = new Foo; [构造函数] 用来初始化新创建的对象的函数是构造函数.在例子中,Foo()函数是构造函数 [实例对象] 通过构造函数的new操作

JavaScript中作用域和作用域链解析

学习js,肯定要学习作用域,js作用域和其他的主流语言的作用域还存在很大的区别. 一.js没有块级作用域. js没有块级作用域,就像这样: if(){ var a = 100: console.log(a) //输出100 } console.log(a) //输出100 js中像if,for,switch之类的语句,他们包含的代码块里面的变量,在代码块外面也能被读取,所以说,js没有块级作用域. 二.js的全局变量 js中规定,全局变量都可以看作是window的属性,而且全局变量能够被所有的代

JavaScript中作用域和作用域链的简单理解(变量提升)

通过阅读<JS高级程序设计>这本书,对js中的作用域和作用域链知识有了初步的了解和认识,准备成笔记供大家参考,笔记中字数比较多,但个人认为叙述的挺详细的,所以希望读者耐心看.再者,本人了解的比较基础,不足的地方希望大家一起交流,共同学习. 1.执行环境(execution context) 执行环境定义了变量和函数有权访问的其他数据,决定了他们各自的行为.每个执行环境都有与之对应的变量对象(variable object),保存着该环境中定义的所有变量和函数.我们无法通过代码来访问变量对象,但