如何避免意外地隐式引用全局对象的属性?
How to avoid accidentally implicitly referring to properties on the global object?
是否可以在没有所有脚本似乎默认具有的隐式 with(global)
上下文的情况下执行代码块?例如,在浏览器中,是否有任何方法可以设置一个脚本,以便像
这样的行
const foo = location;
投掷
Uncaught ReferenceError: location is not defined
而不是访问 window.location
,当 location
还没有首先声明时?缺少它,有没有一种方法可以使这种隐式引用导致某种警告?它可能是编写代码时出现错误的来源(见下文),因此有一种方法可以防止它出现。
(当然,由于普通的作用域规则,可以使用 const
或 let
或在内部块中声明另一个具有相同名称的变量,以确保使用该变量名称引用新变量而不是全局变量 属性,但这不是一回事。)
这可能类似于询问是否可以从 actual with
语句中停止引用 属性:
const obj = { prop: 'prop' };
with (obj) {
// how to make referencing "prop" from somewhere within this block throw a ReferenceError
}
众所周知 with
一开始就不应该使用,但不幸的是,当涉及到 with(global)
时,我们似乎别无选择,偶尔会以牺牲几个字符为代价经常出现的令人困惑的错误: 3 4 5 6。例如:
var status = false;
if (status) {
console.log('status is actually truthy!');
}
(这里的问题:window.status
是保留的 属性 - 当赋值给它时,它会将赋值的表达式强制转换为字符串)
这些类型的错误与不鼓励或禁止显式使用 with
的原因相同,但隐式 with(global)
继续导致问题,即使在严格模式下也是如此,因此找出一种方法周围会有用。
如果您不处于严格模式,一种可能是遍历全局(或 with
ed)对象的 属性 名称,并从这些属性创建另一个对象,其setter 和 getter 都抛出 ReferenceErrors
,然后将您的代码嵌套在该对象上的 另一个 with
中。请参阅下面代码中的注释。
这不是好的解决方案,但这是我能想到的唯一解决方案:
const makeObjWhosePropsThrow = inputObj => Object.getOwnPropertyNames(inputObj)
.reduce((a, propName) => {
const doThrow = () => { throw new ReferenceError(propName + ' is not defined!'); };
Object.defineProperty(a, propName, { get: doThrow, set: doThrow });
return a;
}, {});
// (using setTimeout so that console shows both this and the next error)
setTimeout(() => {
const windowWhichThrows = makeObjWhosePropsThrow(window);
with (windowWhichThrows) {
/* Use an IIFE
* so that variables with the same name declared with "var" inside
* create a locally scoped variable
* rather than try to reference the property, which would throw
*/
(() => {
// Declaring any variable name will not throw:
var alert = true; // window.alert
const open = true; // window.open
// Referencing a property name without declaring it first will throw:
const foo = location;
})();
}
});
const obj = { prop1: 'prop1' };
with (obj) {
const inner = makeObjWhosePropsThrow(obj);
with (inner) {
// Referencing a property name without declaring it first will throw:
console.log(prop1);
}
}
.as-console-wrapper {
max-height: 100% !important;
}
注意事项:
- 这显式使用了
with
,这在严格模式下是被禁止的
- 这并不完全逃避隐含的
with(global)
作用域,或with(obj)
作用域:外部作用域中的变量与a同名属性 将无法引用。
window
有一个属性window
,指的是window
。 window.window === window
。因此,在 with
中引用 window
将会抛出。要么明确排除 window
属性,要么首先保存对 window
的另一个引用。
也许 稍微 清洁器 (YMMV) 是设置 getter 陷阱(就像你所做的那样),但在一个工人中,这样你就不会污染你的主要全局范围。虽然我不需要使用 with
,所以也许这是一个改进。
工人"Thread"
//worker; foo.js
addEventListener('message', function ({ data }) {
try {
eval(`
for (k in self) {
Object.defineProperty(self, k, {
get: function () {
throw new ReferenceError(':(');
}
});
}
// code to execute
${data}
`);
postMessage('no error thrown ');
} catch (e) {
postMessage(`error thrown: ${e.message}`);
}
});
主要"Thread"
var w = new Worker('./foo.js');
w.addEventListener('message', ({data}) => console.log(`response: ${data}`));
w.postMessage('const foo = location');
另一个可能值得探讨的选项是 Puppeteer。
只需使用"use strict"
。更多关于 Strict Mode.
在尝试回答这个问题之前,您需要考虑一些事项。
例如,取 Object
constructor. It is a "Standard built-in object".
window.status
is part of the Window
interface.
很明显,你不想让status
引用window.status
,但是你想让Object
引用window.Object
吗?
解决无法重新定义问题的方法是使用 IIFE 或模块,这应该是您正在做的事情。
(() => {
var status = false;
if (!status) {
console.log('status is now false.');
}
})();
并且为了防止意外使用全局变量,我将设置您的 linter 以发出警告。强制使用像 with (fake_global)
这样的解决方案不仅会在 运行 时间出现错误,这可能不会被捕获,而且速度也会变慢。
特别是对于 ESLint,我似乎找不到 "good" 解决方案。启用浏览器全局变量允许隐式读取。
我建议 no-implicit-globals(因为无论如何你都不应该污染全局范围,并且它可以防止 var status
没有定义任何问题),并且也不会启用所有浏览器全局变量,仅,比如说,window
、document
、console
、setInterval
,等等,就像你在评论中说的那样。
查看 ESLint environments 以了解您要启用哪些。默认情况下,Object
和 Array
之类的东西在全局范围内,但上面列出的那些和 atob
之类的东西不在全局范围内。
要查看全局变量的确切列表,它们由 this file in ESLint and the globals
NPM package 定义。我会选择 "es6"、"worker" 或 "shared-node-browser".
(组合)
eslintrc 文件将包含:
{
"rules": {
"no-implicit-globals": "error"
},
"globals": {
"window": "readonly",
"document": "readonly"
},
"env": {
"browser": false,
"es6": [true/false],
"worker": [true/false],
"shared-node-browser": [true/false]
}
}
比@CertainPerformance 的答案更容易实现,您可以使用 Proxy
来捕获对 window
以外所有内容的隐式访问。唯一的警告是你不能 运行 在严格模式下这样做:
const strictWindow = Object.create(
new Proxy(window, {
get (target, property) {
if (typeof property !== 'string') return undefined
console.log(`implicit access to ${property}`)
throw new ReferenceError(`${property} is not defined`)
}
}),
Object.getOwnPropertyDescriptors({ window })
)
with (strictWindow) {
try {
const foo = location
} catch (error) {
window.console.log(error.toString())
}
// doesn't throw error
const foo = window.location
}
请注意,即使 console
也必须有明确的引用才能不抛出。如果您想将其添加为另一个例外,只需使用
将 strictWindow
修改为另一个自己的 属性
Object.getOwnPropertyDescriptors({ window, console })
事实上,a lot of standard built-in objects 您可能想为其添加例外,但这超出了本答案的范围(无双关语意)。
在我看来,这提供的好处不及在严格模式下 运行ning 的好处。一个更好的解决方案是使用正确配置的 linter,在开发期间捕获隐式引用,而不是在非严格模式下 运行 时间。
是否可以在没有所有脚本似乎默认具有的隐式 with(global)
上下文的情况下执行代码块?例如,在浏览器中,是否有任何方法可以设置一个脚本,以便像
const foo = location;
投掷
Uncaught ReferenceError: location is not defined
而不是访问 window.location
,当 location
还没有首先声明时?缺少它,有没有一种方法可以使这种隐式引用导致某种警告?它可能是编写代码时出现错误的来源(见下文),因此有一种方法可以防止它出现。
(当然,由于普通的作用域规则,可以使用 const
或 let
或在内部块中声明另一个具有相同名称的变量,以确保使用该变量名称引用新变量而不是全局变量 属性,但这不是一回事。)
这可能类似于询问是否可以从 actual with
语句中停止引用 属性:
const obj = { prop: 'prop' };
with (obj) {
// how to make referencing "prop" from somewhere within this block throw a ReferenceError
}
众所周知 with
一开始就不应该使用,但不幸的是,当涉及到 with(global)
时,我们似乎别无选择,偶尔会以牺牲几个字符为代价经常出现的令人困惑的错误:
var status = false;
if (status) {
console.log('status is actually truthy!');
}
(这里的问题:window.status
是保留的 属性 - 当赋值给它时,它会将赋值的表达式强制转换为字符串)
这些类型的错误与不鼓励或禁止显式使用 with
的原因相同,但隐式 with(global)
继续导致问题,即使在严格模式下也是如此,因此找出一种方法周围会有用。
如果您不处于严格模式,一种可能是遍历全局(或 with
ed)对象的 属性 名称,并从这些属性创建另一个对象,其setter 和 getter 都抛出 ReferenceErrors
,然后将您的代码嵌套在该对象上的 另一个 with
中。请参阅下面代码中的注释。
这不是好的解决方案,但这是我能想到的唯一解决方案:
const makeObjWhosePropsThrow = inputObj => Object.getOwnPropertyNames(inputObj)
.reduce((a, propName) => {
const doThrow = () => { throw new ReferenceError(propName + ' is not defined!'); };
Object.defineProperty(a, propName, { get: doThrow, set: doThrow });
return a;
}, {});
// (using setTimeout so that console shows both this and the next error)
setTimeout(() => {
const windowWhichThrows = makeObjWhosePropsThrow(window);
with (windowWhichThrows) {
/* Use an IIFE
* so that variables with the same name declared with "var" inside
* create a locally scoped variable
* rather than try to reference the property, which would throw
*/
(() => {
// Declaring any variable name will not throw:
var alert = true; // window.alert
const open = true; // window.open
// Referencing a property name without declaring it first will throw:
const foo = location;
})();
}
});
const obj = { prop1: 'prop1' };
with (obj) {
const inner = makeObjWhosePropsThrow(obj);
with (inner) {
// Referencing a property name without declaring it first will throw:
console.log(prop1);
}
}
.as-console-wrapper {
max-height: 100% !important;
}
注意事项:
- 这显式使用了
with
,这在严格模式下是被禁止的 - 这并不完全逃避隐含的
with(global)
作用域,或with(obj)
作用域:外部作用域中的变量与a同名属性 将无法引用。 window
有一个属性window
,指的是window
。window.window === window
。因此,在with
中引用window
将会抛出。要么明确排除window
属性,要么首先保存对window
的另一个引用。
也许 稍微 清洁器 (YMMV) 是设置 getter 陷阱(就像你所做的那样),但在一个工人中,这样你就不会污染你的主要全局范围。虽然我不需要使用 with
,所以也许这是一个改进。
工人"Thread"
//worker; foo.js
addEventListener('message', function ({ data }) {
try {
eval(`
for (k in self) {
Object.defineProperty(self, k, {
get: function () {
throw new ReferenceError(':(');
}
});
}
// code to execute
${data}
`);
postMessage('no error thrown ');
} catch (e) {
postMessage(`error thrown: ${e.message}`);
}
});
主要"Thread"
var w = new Worker('./foo.js');
w.addEventListener('message', ({data}) => console.log(`response: ${data}`));
w.postMessage('const foo = location');
另一个可能值得探讨的选项是 Puppeteer。
只需使用"use strict"
。更多关于 Strict Mode.
在尝试回答这个问题之前,您需要考虑一些事项。
例如,取 Object
constructor. It is a "Standard built-in object".
window.status
is part of the Window
interface.
很明显,你不想让status
引用window.status
,但是你想让Object
引用window.Object
吗?
解决无法重新定义问题的方法是使用 IIFE 或模块,这应该是您正在做的事情。
(() => {
var status = false;
if (!status) {
console.log('status is now false.');
}
})();
并且为了防止意外使用全局变量,我将设置您的 linter 以发出警告。强制使用像 with (fake_global)
这样的解决方案不仅会在 运行 时间出现错误,这可能不会被捕获,而且速度也会变慢。
特别是对于 ESLint,我似乎找不到 "good" 解决方案。启用浏览器全局变量允许隐式读取。
我建议 no-implicit-globals(因为无论如何你都不应该污染全局范围,并且它可以防止 var status
没有定义任何问题),并且也不会启用所有浏览器全局变量,仅,比如说,window
、document
、console
、setInterval
,等等,就像你在评论中说的那样。
查看 ESLint environments 以了解您要启用哪些。默认情况下,Object
和 Array
之类的东西在全局范围内,但上面列出的那些和 atob
之类的东西不在全局范围内。
要查看全局变量的确切列表,它们由 this file in ESLint and the globals
NPM package 定义。我会选择 "es6"、"worker" 或 "shared-node-browser".
eslintrc 文件将包含:
{
"rules": {
"no-implicit-globals": "error"
},
"globals": {
"window": "readonly",
"document": "readonly"
},
"env": {
"browser": false,
"es6": [true/false],
"worker": [true/false],
"shared-node-browser": [true/false]
}
}
比@CertainPerformance 的答案更容易实现,您可以使用 Proxy
来捕获对 window
以外所有内容的隐式访问。唯一的警告是你不能 运行 在严格模式下这样做:
const strictWindow = Object.create(
new Proxy(window, {
get (target, property) {
if (typeof property !== 'string') return undefined
console.log(`implicit access to ${property}`)
throw new ReferenceError(`${property} is not defined`)
}
}),
Object.getOwnPropertyDescriptors({ window })
)
with (strictWindow) {
try {
const foo = location
} catch (error) {
window.console.log(error.toString())
}
// doesn't throw error
const foo = window.location
}
请注意,即使 console
也必须有明确的引用才能不抛出。如果您想将其添加为另一个例外,只需使用
strictWindow
修改为另一个自己的 属性
Object.getOwnPropertyDescriptors({ window, console })
事实上,a lot of standard built-in objects 您可能想为其添加例外,但这超出了本答案的范围(无双关语意)。
在我看来,这提供的好处不及在严格模式下 运行ning 的好处。一个更好的解决方案是使用正确配置的 linter,在开发期间捕获隐式引用,而不是在非严格模式下 运行 时间。