Alan Storm对我关于with声明的回答的评论引起了我的思考。我很少找到使用这个特殊语言特性的理由,也从来没有想过它可能会带来什么麻烦。现在,我很好奇如何有效地利用with,同时避免它的陷阱。
你觉得with语句在哪里有用?
Alan Storm对我关于with声明的回答的评论引起了我的思考。我很少找到使用这个特殊语言特性的理由,也从来没有想过它可能会带来什么麻烦。现在,我很好奇如何有效地利用with,同时避免它的陷阱。
你觉得with语句在哪里有用?
当前回答
我创建了一个“merge”函数,它消除了with语句的一些歧义:
if (typeof Object.merge !== 'function') {
Object.merge = function (o1, o2) { // Function to merge all of the properties from one object into another
for(var i in o2) { o1[i] = o2[i]; }
return o1;
};
}
我可以类似地使用它,但我可以知道它不会影响任何我不想让它影响的范围。
用法:
var eDiv = document.createElement("div");
var eHeader = Object.merge(eDiv.cloneNode(false), {className: "header", onclick: function(){ alert("Click!"); }});
function NewObj() {
Object.merge(this, {size: 4096, initDate: new Date()});
}
其他回答
正如Andy E在Shog9回答的评论中指出的那样,当with与对象文字一起使用时,会发生这种潜在的意想不到的行为:
for (var i = 0; i < 3; i++) {
function toString() {
return 'a';
}
with ({num: i}) {
setTimeout(function() { console.log(num); }, 10);
console.log(toString()); // prints "[object Object]"
}
}
并不是说意想不到的行为不是他的标志。
如果您仍然想使用这种技术,至少使用一个具有空原型的对象。
function scope(o) {
var ret = Object.create(null);
if (typeof o !== 'object') return ret;
Object.keys(o).forEach(function (key) {
ret[key] = o[key];
});
return ret;
}
for (var i = 0; i < 3; i++) {
function toString() {
return 'a';
}
with (scope({num: i})) {
setTimeout(function() { console.log(num); }, 10);
console.log(toString()); // prints "a"
}
}
但这只适用于ES5+。也不要用with。
正如我之前的评论所指出的,我不认为你可以安全地使用它,无论它在任何给定的情况下有多么诱人。由于这里没有直接涉及到这个问题,我将重复一遍。考虑以下代码
user = {};
someFunctionThatDoesStuffToUser(user);
someOtherFunction(user);
with(user){
name = 'Bob';
age = 20;
}
如果不仔细研究这些函数调用,就无法知道这段代码运行后程序的状态是什么。如果user.name已经设置,它现在将是Bob。如果没有设置,全局名称将被初始化或更改为Bob,用户对象将保持没有name属性。
错误发生。如果你使用with,你最终会这样做,增加你的程序失败的机会。更糟糕的是,您可能会遇到在with块中设置全局变量的工作代码,或者是故意的,或者是作者不知道这个构造的怪处。这很像在开关上遇到了故障,你不知道作者是否有意这样做,也没有办法知道“修复”代码是否会引入回归。
现代编程语言充满了特性。一些特性,在使用多年后,被发现是不好的,应该避免使用。Javascript的with就是其中之一。
你可以在W3schools http://www.w3schools.com/js/js_form_validation.asp上看到javascript表单的验证,其中对象表单被“扫描”,以找到名称为“email”的输入
但我已经修改了它,从任何形式的所有字段验证为不空,无论名称或数量的字段在一个形式。我只测试了文本字段。
但是with()使事情变得更简单。代码如下:
function validate_required(field)
{
with (field)
{
if (value==null||value=="")
{
alert('All fields are mandtory');return false;
}
else
{
return true;
}
}
}
function validate_form(thisform)
{
with (thisform)
{
for(fiie in elements){
if (validate_required(elements[fiie])==false){
elements[fiie].focus();
elements[fiie].style.border='1px solid red';
return false;
} else {elements[fiie].style.border='1px solid #7F9DB9';}
}
}
return false;
}
对代理对象使用“with”语句
我最近想为babel写一个支持宏的插件。我想有一个单独的变量名称空间来保存我的宏变量,我可以在这个空间中运行我的宏代码。此外,我想检测宏代码中定义的新变量(因为它们是新的宏)。
首先,我选择了vm模块,但我发现vm模块中的全局变量,如Array, Object等与主程序不同,我无法实现模块并要求与该全局对象完全兼容(因为我无法重构核心模块)。最后,我找到了“with”语句。
const runInContext = function(code, context) {
context.global = context;
const proxyOfContext = new Proxy(context, { has: () => true });
let run = new Function(
"proxyOfContext",
`
with(proxyOfContext){
with(global){
${code}
}
}
`
);
return run(proxyOfContext);
};
这个代理对象捕获所有变量的搜索,并说:“yes, I have that variable.”如果代理对象实际上没有该变量,则将其值显示为undefined。
这样,如果在宏代码中使用var语句定义了任何变量,我可以在上下文对象中找到它(如vm模块)。但是用let或const定义的变量只在该时间内可用,不会保存在context对象中(vm模块保存它们,但不公开它们)。
性能:该方法性能优于vm.runInContext。
安全:如果你想在沙箱中运行代码,这在任何方面都不安全,你必须使用vm模块。它只提供一个新的名称空间。
似乎不值得,因为你可以做到以下几点:
var o = incrediblyLongObjectNameThatNoOneWouldUse;
o.name = "Bob";
o.age = "50";