- 浏览: 964892 次
- 性别:
- 来自: 上海
文章分类
最新评论
-
sscsacdsadcsd:
mike8625 写道react还要自己的一些标签 还得编译 ...
对于React体系的一点想法 -
mike8625:
说的都是给大公司听的,国内很多还是小公司,做个小项目, 说实话 ...
关于国内前端和JS技术发展的乱想 -
mike8625:
react还要自己的一些标签 还得编译 编译吧浏览器端说还慢 ...
对于React体系的一点想法 -
u012814086:
下意识想到了Golang
JavaScript语句后应该加分号么? -
xueduanyang:
我是水羊,年轻的时候觉得只要有好斧子就能做成好产品,各种产品都 ...
关于国内前端和JS技术发展的乱想
本文是对如何将let结构转换到ES3代码的补充。
首先,原文所说的将let转换为with的方法有几个缺陷需要说明:
1. with虽然可附加一个新的scope,但是由于引入的是一个JS对象,所以Object.prototype上的属性也被引入了该scope。比方说你无法在with里访问外部的toString()方法,因为你访问到的实际上变成了Object.prototype.toString。
再来一个例子:
如果这段代码里的 login 函数被转换为:
就会存在一些隐患。
例如:
如果在login执行之前,注入了这样的代码:
Object.prototype.user = 'hacker'
那只要输入hacker用户对应的密码就可以通过检查。
更简单的方式是:
Object.prototype.securityCheck = function() { return true }
【注意上述只是示例,不代表真实世界里会有这样的写法。】
另一个隐藏问题是,如果with(scope)引用了一个函数,则该函数在with块内被调用是以scope作为this对象的。这造成采用with时行为不一致,并可能影响其他reference。
比如:
这段代码执行的结果应该是返回1,并且global上的x=2。
但是
这段代码的结果是返回2,global上并不会产生x。
2. with语句在ES5的strict模式下被禁用。
with实际上是在lexical scope上开了一个后门,这对依赖静态代码分析的辅助工具(如IDE、压缩器等)和性能优化(如新的支持JIT的引擎)都造成了障碍。
另外从第1条可以看到with的设计天生残疾,易造成语句的二义性,并且这种二义性可以由完全无关的代码引入。
因此ES5的strict模式直接禁用了该特性。
上述两点注定了block scope转换到with是一个不太好的做法。
那么还有什么其他方式么?
幸运的是,确实存在一个我以前遗忘的特性,try catch语句中,catch语句也会增加一层新的scope!
Traceur是一个Google开源项目,可以将ES5和Harmony的一些新特性编译为传统的ES3代码。让我们看看它是怎样转换上述代码的:
不需要更多解释了。
总结,用try/catch进行let转换方式如下:
转换为
此外,原文中的let语句形式也可以容易的转换:
let语句
可转换为:
不过对比之前的转换公式,上述转换有一点小小的不严谨——虽然在这个case里并不会出现问题。这就作为一个习题留给读者了。
【7月13日的进一步补充】
不过,使用catch也有一些实践上的问题:
1. 在各浏览器脚本引擎的较早版本中,try/catch方案中也有一些类似with的副作用。比如catch块内的变量访问会优先搜索Object.prototype,或者如果catch到的是一个函数,执行该函数的this上下文会是一个隐含的scope对象。幸好这些问题现在已经都被修复了。
2. 直到JScript 5.8(也就是直到IE8),对catch的处理是与其他引擎不一致的,那就是在JScript中catch不会产生新的scope。(感谢Winter指出此点)
所以如果考虑转换目标必须包括IE6到IE8,则就只能用原文中的with方案。
为了克服本文所指出的with的问题,可以采用防御性方法——将with块中所有用到的references都照抄一遍:
注意 with({pwd:temp, securityCheck:securityCheck, user:user, alert:alert}) 这里。
不过这个方式是有局限性的,即其只适用于读取的情形,但with块内可能出现 x = exp 或 delete x ,也就是出现对于用到的外部reference的修改或删除操作。这时就无能为力了。除非with代码中所有对外部reference的修改都是立即执行的(即不会出现在闭包中),那么可以在with结束后同步。此外,若with块内出现eval语句,也就无法静态确定会用到哪些references。不过在实践中通常较少出现上述代码形式。
另一种思路是在创建scope对象时覆盖所有Object.prototype上的名称,如:
不过这个方式存在漏洞。【为什么呢?请读者自行思考。】
尽管如此,这个思路仍然揭示了一种可能,那就是如果能创建一个不带有prototype的“干净”的对象,则就克服了with的许多问题。
恰好,IE中的DOM对象正是这样一朵奇葩。我们希望挑选的DOM对象是,可任意产生新对象(因为我们需要许多的scope对象),可添加属性(理论上若该对象可作为其他对象的prototype也可,不过JScript禁止用非native JS对象作为prototype),并且本身固有属性越少越好,最好是光秃秃的(这样就避免覆盖固有属性造成的问题)。
经过我的研究,符合上述条件的最佳对象是一些collection对象(还有更好的选择么?欢迎读者提供线索)。比如document.createStyleSheet().rules 。这个对象上只有3个属性:constructor、length、item。
所以只剩下两个问题,一个是如果要用到的外部变量名为constructor/length/item,另一个是用到的外部变量是一个内部访问this引用的函数。
对于问题一,可以在转换时,对这三个名称做特殊处理(好在只有3个特例)。对于问题二,我们可以将with内的函数调用显式的绑定this。
以上。
首先,原文所说的将let转换为with的方法有几个缺陷需要说明:
1. with虽然可附加一个新的scope,但是由于引入的是一个JS对象,所以Object.prototype上的属性也被引入了该scope。比方说你无法在with里访问外部的toString()方法,因为你访问到的实际上变成了Object.prototype.toString。
再来一个例子:
function login() { var user = getUserNameFromCookie() for (let i = 0; i < 3; i++) { let pwd = prompt('Please enter your password:') if (securityCheck(user, pwd)) { return true } else { alert('Wrong password! Please try again...') } } return false } if (login()) { alert('Welcome!') } else { alert('Failed to login!') } function securityCheck(user, pwd) { //...do some security checking } function getUserNameFromCookie() { //...read user name from cookie }
如果这段代码里的 login 函数被转换为:
function login() { var user = getUserNameFromCookie() for (var i = 0; i < 3; i++) { var temp = prompt('Please enter your password:') with({pwd:temp}) { if (securityCheck(user, pwd)) { return true } else { alert('Wrong password! Please try again...') } } } return false }
就会存在一些隐患。
例如:
如果在login执行之前,注入了这样的代码:
Object.prototype.user = 'hacker'
那只要输入hacker用户对应的密码就可以通过检查。
更简单的方式是:
Object.prototype.securityCheck = function() { return true }
【注意上述只是示例,不代表真实世界里会有这样的写法。】
另一个隐藏问题是,如果with(scope)引用了一个函数,则该函数在with块内被调用是以scope作为this对象的。这造成采用with时行为不一致,并可能影响其他reference。
比如:
function test() { let x = 1 { let f = function(){ this.x = 2 } f() return x } } test()
这段代码执行的结果应该是返回1,并且global上的x=2。
但是
function test() { with(x:1) { with({f: function(){ this.x = 2 }}) { f() return x } } } test()
这段代码的结果是返回2,global上并不会产生x。
2. with语句在ES5的strict模式下被禁用。
with实际上是在lexical scope上开了一个后门,这对依赖静态代码分析的辅助工具(如IDE、压缩器等)和性能优化(如新的支持JIT的引擎)都造成了障碍。
另外从第1条可以看到with的设计天生残疾,易造成语句的二义性,并且这种二义性可以由完全无关的代码引入。
因此ES5的strict模式直接禁用了该特性。
上述两点注定了block scope转换到with是一个不太好的做法。
那么还有什么其他方式么?
幸运的是,确实存在一个我以前遗忘的特性,try catch语句中,catch语句也会增加一层新的scope!
Traceur是一个Google开源项目,可以将ES5和Harmony的一些新特性编译为传统的ES3代码。让我们看看它是怎样转换上述代码的:
function login() { var user = getUserNameFromCookie(); for(var i = 0; i < 3; i ++) { try { throw undefined; } catch(pwd) { pwd = prompt('Please enter your password:'); if(securityCheck(user, pwd)) { return true; } else { alert('Wrong password! Please try again...'); } } } return false; }
不需要更多解释了。
总结,用try/catch进行let转换方式如下:
statement { ... let n1 = exp1 ... let n2 = exp2 ... let n3 = exp3, n4 = exp4, ... ... }
转换为
statement { try { throw undefined } catch (n1) { try { throw undefined } catch (n2) { try { throw undefined } catch (n3) { try { throw undefined } catch (n4) { ... n1 = exp1 ... n2 = exp2 ... n3 = exp3; n4 = exp4; ... }}}} }
此外,原文中的let语句形式也可以容易的转换:
let语句
var x = 5; var y = 0; let (x = x+10, y = 12) { print(x+y + "\n"); } print((x + y) + "\n");
可转换为:
var x = 5; var y = 0; try { throw x+10 } catch(x) { try { throw 12 } catch(y) { print(x+y + "\n"); }} print((x + y) + "\n");
不过对比之前的转换公式,上述转换有一点小小的不严谨——虽然在这个case里并不会出现问题。这就作为一个习题留给读者了。
【7月13日的进一步补充】
不过,使用catch也有一些实践上的问题:
1. 在各浏览器脚本引擎的较早版本中,try/catch方案中也有一些类似with的副作用。比如catch块内的变量访问会优先搜索Object.prototype,或者如果catch到的是一个函数,执行该函数的this上下文会是一个隐含的scope对象。幸好这些问题现在已经都被修复了。
2. 直到JScript 5.8(也就是直到IE8),对catch的处理是与其他引擎不一致的,那就是在JScript中catch不会产生新的scope。(感谢Winter指出此点)
所以如果考虑转换目标必须包括IE6到IE8,则就只能用原文中的with方案。
为了克服本文所指出的with的问题,可以采用防御性方法——将with块中所有用到的references都照抄一遍:
function login() { var user = getUserNameFromCookie() for (var i = 0; i < 3; i++) { var temp = prompt('Please enter your password:') with({pwd:temp, securityCheck:securityCheck, user:user, alert:alert}) { if (securityCheck(user, pwd)) { return true } else { alert('Wrong password! Please try again...') } } } return false }
注意 with({pwd:temp, securityCheck:securityCheck, user:user, alert:alert}) 这里。
不过这个方式是有局限性的,即其只适用于读取的情形,但with块内可能出现 x = exp 或 delete x ,也就是出现对于用到的外部reference的修改或删除操作。这时就无能为力了。除非with代码中所有对外部reference的修改都是立即执行的(即不会出现在闭包中),那么可以在with结束后同步。此外,若with块内出现eval语句,也就无法静态确定会用到哪些references。不过在实践中通常较少出现上述代码形式。
另一种思路是在创建scope对象时覆盖所有Object.prototype上的名称,如:
// create a "clean" scope object var scope = {} // override props from Object.prototype scope.constructor = constructor scope.toString = toString scope.toLocaleString = toLocaleString scope.valueOf = valueOf scope.hasOwnProperty = hasOwnProperty scope.isPrototypeOf = isPrototypeOf scope.propertyIsEnumerable = propertyIsEnumerable for (var k in Object.prototype) { scope[k] = eval(k) } // add bindings scope.x = ... scope.y = ... ... with(scope) { alert(x + y) }
不过这个方式存在漏洞。【为什么呢?请读者自行思考。】
尽管如此,这个思路仍然揭示了一种可能,那就是如果能创建一个不带有prototype的“干净”的对象,则就克服了with的许多问题。
恰好,IE中的DOM对象正是这样一朵奇葩。我们希望挑选的DOM对象是,可任意产生新对象(因为我们需要许多的scope对象),可添加属性(理论上若该对象可作为其他对象的prototype也可,不过JScript禁止用非native JS对象作为prototype),并且本身固有属性越少越好,最好是光秃秃的(这样就避免覆盖固有属性造成的问题)。
经过我的研究,符合上述条件的最佳对象是一些collection对象(还有更好的选择么?欢迎读者提供线索)。比如document.createStyleSheet().rules 。这个对象上只有3个属性:constructor、length、item。
所以只剩下两个问题,一个是如果要用到的外部变量名为constructor/length/item,另一个是用到的外部变量是一个内部访问this引用的函数。
对于问题一,可以在转换时,对这三个名称做特殊处理(好在只有3个特例)。对于问题二,我们可以将with内的函数调用显式的绑定this。
以上。
发表评论
-
论ES6模块系统的静态解析
2013-03-14 04:56 15950本文是Dave Herman的《Stati ... -
如何创建一个JavaScript裸对象
2012-08-27 02:11 8118所谓裸对象,即 naked object ,是指没有原型(sp ... -
JavaScript语句后应该加分号么?
2012-06-19 03:10 14469这是一个老生常谈的问 ... -
shim是应该抛异常还是应该fail silently?
2011-08-11 17:26 5671玉伯发布了es5-safe模块 ... -
7月30日的广州演讲视频和Slides
2011-08-01 23:38 31997月30日在W3CTech广州站活动上的演讲,题目是:ECMA ... -
如何判断一个函数的意图是被用作构造器,也就是可视为“类”
2011-07-21 13:55 2974前提是不要求做什么特殊标记。只是最大可能的猜测函数的作用大概是 ... -
关于国内前端和JS技术发展的乱想
2011-07-19 18:53 33354玉伯在我的一条微博后面写了一些(和主题不是很相关但)非常值得思 ... -
Module与Trait的比较
2011-08-12 12:50 4054最近我多次提及module和trait。 粗看,我们可以发现 ... -
JavaScript的未来方向之观察
2011-07-12 02:53 8385最近每次去杭州,都有 ... -
我为什么力挺NodeJS
2011-07-04 00:27 0之前在参加CNodeJS社区在 ... -
JS之父再谈JS历史(续完)
2010-12-31 04:20 3460又到年底,我觉得是时候还债了。自开blog来,我出了不少“太监 ... -
我为什么是DC黑续,兼答Tin
2010-04-27 14:29 0我同意安全是一个重要问题。我不同意的是把所谓安全放到凌驾于其他 ... -
我为什么是DC黑─Why I disagree with Douglas Crockford
2010-04-26 17:51 11048参加完了QCon北京大会, ... -
写对正则:一行代码,速度差50倍
2009-05-12 03:43 60232009-05-11 A lesson of ... -
JavaScript的EOS(分号)问题
2009-05-08 16:24 5793在http://bbs.51js.com/viewthre ... -
JavaScript五大关键字
2009-05-06 17:53 4776近期做语法高亮项目的副产品,是统计了一下几个主流JS工具包中各 ... -
curry和partial的差别
2009-03-28 00:15 372551js上asfman翻译了http://ejohn.org/ ... -
Eval is Evil , With evil too
2009-03-27 18:39 0with的问题: 2009-3-27 17:12:40 ... -
IE全局变量的Dissociative Identity Disorder(人格分裂症)
2009-03-16 02:47 14747最近,小麦提出了一个疑惑: 小麦 写道最后介绍一个我也搞不明白 ... -
JScript下Array对象的性能问题
2009-02-14 02:51 4005今天看了微软JScript官方blog上去年的两篇文章: ht ...
相关推荐
这些新特性对于理解和编写更健壮的JavaScript代码至关重要。 1. **块级作用域(Block Scope)** 在 ES5 中,只有全局作用域和函数作用域,而没有块级作用域。这意味着在函数内部或 `if`、`for` 等语句中定义的变量...
在实际开发中,合理利用不同scope可以创建更加安全和高效的代码结构。例如,在模块化开发中,我们可以使用闭包来封装私有方法和数据,确保模块内部的状态不被外部随意修改。 通过分析`webproject6_1`这个项目文件,...
ES6(ECMAScript 2015)中引入了块级作用域(block scope),通过let和const关键字声明的变量具有块级作用域,而var声明的变量则具有函数作用域。 #### 3. 对象和数组 - **对象**:在JavaScript中对象是一个包含...
作用域方面,const和let都提供了块级作用域(Block Scope),它们只在声明它们的块内有效,而var声明的变量则为函数级作用域(Function Scope)或全局作用域(Global Scope)。块级作用域有助于减少变量的意外覆盖,...
4. **作用域(Scope)与块级作用域(Block Scope)** JavaScript有函数作用域和全局作用域,ES6引入了块级作用域,如`let`和`const`关键字,理解这些可以帮助避免变量冲突和意外的全局变量。 5. **模块(Modules)...
### Scope(作用域) #### 一、什么是作用域 作用域是编程语言中用来定义变量可见性和可访问性的概念。简单来说,作用域决定了变量在何处可以被引用和使用。在JavaScript中,作用域主要分为两种类型:全局作用域和...
`let` allows reassigning the variable within its scope: ```javascript let ninja = "Yoshi"; ninja = "Hanzo"; console.log(ninja); // Hanzo ``` **Const:** `const` creates a read-only reference to a ...
在JavaScript中,let和const是ES6(ECMAScript 2015)引入的新关键字,它们提供了块级作用域(block scope),这意味着变量只在声明所在的块(通常是花括号内的代码块)中有效。let不会进行变量提升(hoisting),这...
它们可以将ES6代码转换为ES5或者更早版本的JavaScript代码,使得新的ES6特性可以在不支持ES6的旧环境中运行。Source Maps则是转译过程中用于映射源代码与转译后代码的工具,它帮助开发者在调试转译后的代码时,能够...
JavaScript 是一种广泛使用的编程语言,它在早期版本中没有实现块级作用域(block scope),这是它与其他许多编程语言的一个显著区别。在 JavaScript 中,变量的作用域是由函数定义的,即使在块级结构比如循环或条件...
此外,当JavaScript代码在浏览器环境中执行时,如果未声明的变量被赋值,默认是将其挂在到全局对象window上,这意味着该变量变成了全局变量。在浏览器JavaScript中,全局对象window通常代表当前浏览器窗口的一个对象...
理解 `let` 和 `const` 的这些特性对于编写更安全、可维护的JavaScript代码至关重要。在处理循环、嵌套作用域以及需要确保变量不被意外更改的情况时,这两个关键字提供了更好的工具。同时,使用 `const` 有助于创建...
同时,了解变量声明的历史变迁和不同作用域规则,对于编写高效且兼容的JavaScript代码有重要作用。随着新标准的更新,开发者们也需要适时调整自己的编程习惯,使用新特性来提高代码质量和开发效率。
var声明的变量存在变量提升(hoisting)的特性,而let和const则没有这种特性,它们存在块级作用域(block scope)。 ### 变量提升(hoisting) 变量提升是指在JavaScript中,变量声明会在其所在作用域的所有代码执行...
- **块级作用域(Block scope)**:`let`和`const`关键字引入了块级作用域,防止变量污染全局。 ```javascript if (true) { let x = 1; } console.log(x); // ReferenceError: x is not defined ``` 理解并熟练...
`let`和`const`是ES6引入的新关键字,提供了块级作用域(block scope)的支持,而`let`可以声明块级作用域变量并且可以重新赋值,`const`则声明块级作用域常量,一旦赋值后不可更改。使用这些关键字可以限制变量的...
JavaScript有两种主要的作用域类型:函数级作用域(function-level scope)和块级作用域(block-level scope)。在JavaScript中,只有函数会创建新的作用域。这意味着像if语句这样的代码块不会创建新的作用域。此外...
- 块级作用域(Block Scope):引入了 `let` 和 `const` 关键字。 - 类(Classes):提供更面向对象的语法糖,但本质仍是函数。 - 解构赋值(Destructuring Assignment):方便地从数组或对象中提取值。 - 模板...
在编写JavaScript代码时,应尽量使用`let`和`const`来替代`var`,因为ES6引入的`let`和`const`提供了块级作用域,有助于减少因作用域问题引起的错误。同时,合理利用闭包(closure)和立即执行函数表达式(IIFE)也...