with
语句的格式如下:
with (object) {
statements;
}
它的作用是操作同一个对象的多个属性时,提供一些书写的方便。
// 例一
with (o) {
p1 = 1;
p2 = 2;
}
// 等同于
o.p1 = 1;
o.p2 = 2;
// 例二
with (document.links[0]){
console.log(href);
console.log(title);
console.log(style);
}
// 等同于
console.log(document.links[0].href);
console.log(document.links[0].title);
console.log(document.links[0].style);
注意,with
区块内部的变量,必须是当前对象已经存在的属性,否则会创造一个当前作用域的全局变量。这是因为with
区块没有改变作用域,它的内部依然是当前作用域。
var o = {};
with (o) {
x = "abc";
}
o.x // undefined
x // "abc"
上面代码中,对象o
没有属性x
,所以with
区块内部对x
的操作,等于创造了一个全局变量x
。正确的写法应该是,先定义对象o
的属性x
,然后在with
区块内操作它。
var o = {};
o.x = 1;
with (o) {
x = 2;
}
o.x // 2
这是with
语句的一个很大的弊病,就是绑定对象不明确。
with (o) {
console.log(x);
}
单纯从上面的代码块,根本无法判断x
到底是全局变量,还是o
对象的一个属性。这非常不利于代码的除错和模块化,编译器也无法对这段代码进行优化,只能留到运行时判断,这就拖慢了运行速度。因此,建议不要使用with
语句,可以考虑用一个临时变量代替with
。
with(o1.o2.o3) {
console.log(p1 + p2);
}
// 可以写成
var temp = o1.o2.o3;
console.log(temp.p1 + temp.p2);
with
语句少数有用场合之一,就是替换模板变量。
var str = 'Hello <%= name %>!';
上面代码是一个模板字符串。假定有一个parser
函数,可以将这个字符串解析成下面的样子。
parser(str)
// '"Hello ", name, "!"'
那么,就可以利用with
语句,进行模板变量替换。
var str = 'Hello <%= name %>!';
var o = {
name: 'Alice'
};
function tmpl(str, obj) {
str = 'var p = [];' +
'with (obj) {p.push(' + parser(str) + ')};' +
'return p;'
var r = (new Function('obj', str))(obj);
return r.join('');
}
tmpl(str, o)
// "Hello Alice!"
上面代码的核心逻辑是下面的部分。
var o = {
name: 'Alice'
};
var p = [];
with (o) {
p.push('Hello ', name, '!');
};
p.join('') // "Hello Alice!"
上面代码中,with
区块内部,模板变量name
可以被对象o
的属性替换,而p
依然是全局变量。这就是很多模板引擎的实现原理。