转载博主:点击打开链接
现如今JavaScript早已不仅仅是网页特效脚本了,更多是用来构建大规模的Web应用,所以语言规范的制定者们也逐渐意识到要对JS进行语法方面的规范,并且有意地引导开发者编程习惯,消除一些不规范和不安全的语法,进而更好的满足以后大规模开发的要求,ES5规范中的严格模式就是其中重要的一环,今天我们就来详细介绍一下严格模式的使用以及它对语法的种种限制和规范。
启用严格模式只需使用'use strict';这条语句即可,对于支持严格模式的浏览器,在声明'use strict';语句之后的代码都将会在严格模式的限制下执行,对于不支持严格模式的浏览器,这个声明只是一个简单的字符串语句,不会产生任何影响,而我们在严格模式下编写的代码是规范的,也可以很好的运行,所以无需担心兼容性问题,下面是严格模式的一个简单声明:
- 'use strict';
- console.log("I'm running in strict mode");
严格模式应用在两种作用域,一种是整个脚本作用域,一种是单个函数作用域,下面先介绍脚本作用域:
- <script type="text/javascript">
- 'use strict';
- console.log('strict mode comes into effect.');
- </script>
- <script type="text/javascript">
- console.log("strict mode doesn't take control of me.");
- </script>
严格模式也可以在单个函数内声明,这种情况下,严格模式只对函数内代码起作用:
- function a() {
- 'use strict';
- console.log("I'm running in strict mode");
- function b() {
- console.log("I'm also in strict mode");
- }
- }
- console.log('strict mode takes no effect here');
- ;(function() {
- 'use strict';
- console.log("I'm running in strict mode");
- function b() {
- console.log("I'm also in strict mode");
- }
- })();
- var basePlus = function(baseNumber) {
- return function(number) {
- console.log(baseNumber + number);
- }
- }
- (function(name) {
- console.log('hello ' + name);
- })('Scott');
- var basePlus = function(baseNumber) {
- return function(number) {
- console.log(baseNumber + number);
- }
- }(function(name) {
- console.log('hello ' + name);
- })('Scott');
所以,这里我们要在立即执行函数前面加上分号,即使像下面这样被拼接在一起也不会出问题:
- var basePlus = function(baseNumber) {
- return function(number) {
- console.log(baseNumber + number);
- }
- };(function(name) {
- console.log('hello ' + name);
- })('Scott');
1. 变量必须使用var声明,杜绝不小心将本地变量声明成一个全局变量
在常规模式下,如果我们声明一个变量时省略了var关键字,解析引擎会自动将其声明为全局变量,但在严格模式下,会直接抛出异常,不会为我们转为全局变量:
- 'use strict';
- myVariable = 3; //Uncaught ReferenceError: myVariable is not defined
2. 对超出权限的操作显示报错,不再做静默失败处理
常规模式下,我们可以做很多不合法的操作,比如给NaN赋值,NaN是不可写的变量,但我们尝试更新它时收不到任何的错误反馈信息,严格模式就不同了,它会显示的抛出异常,下面是一些非法的操作:
- 'use strict';
- NaN = 3; //Uncaught TypeError: ...
- var person = {};
- Object.defineProperty(person, 'name', {
- writable: false,
- value: 'Scott'
- });
- person.name = 'John'; //Uncaught TypeError: ...
- var person2 = {
- get name() {
- return 'Scott'
- }
- };
- person2.name = 'John'; //Uncaught TypeError: ...
- var person3 = {
- name: 'Scott'
- };
- Object.preventExtensions(person3);
- person3.age = 20; //Uncaught TypeError: ...
3. 禁止删除变量和对象中不可删除的属性,显示报错
我们都知道,通过var声明的变量是不可删除的,在常规模式下,试图删除会静默失败,但在严格模式下会显式抛出异常;同样的,试图删除对象中不可删除的属性也会显式报错:
- 'use strict';
- var myVariable = 3;
- delete myVariable; //Uncaught SyntaxError: ...
- delete Object.prototype; //Uncaught TypeError: ...
- var person = {};
- Object.defineProperty(person, 'name', {
- configurable: false,
- value: 'Scott'
- });
- delete person.name; //Uncaught TypeError: ...
常规模式下,如果我们在对象中定义重复的属性,后定义的值会覆盖先定义的那个,ES5的严格模式规定,对象中不允许定义重复的属性,否则会显式报错。但博主在测试时发现,严格模式下的代码和常规模式下并无两样,原因可能在于Chrome支持一部分的ES6新功能,而ES6中存在相关的一个bug:Bug 1041128。
- 'use strict';
- //it should throw a SyntaxError in ES5 strict mode
- var person = {
- name: 'Scott'
- name: 'John'
- };
- console.log(person.name);
5. 禁止函数参数重名
常规模式下,如果在定义函数不小心声明了重复的参数名,后一个重名参数会覆盖前一个重名参数,虽然arguments里是可以访问到每个参数值的,但有时候也会遇到意想不到的结果:
- var b = 0;
- function sum(a, a, c) {
- return a + b + c;
- }
- console.log(sum(1, 2, 3)); //5
- 'use strict';
- var b = 0;
- function sum(a, a, c) { //Uncaught SyntaxError: ...
- return a + b + c;
- }
- console.log(sum(1, 2, 3));
6. 禁止使用八进制数字
以0开头的八进制数字常常会让开发者迷惑,严格模式禁止以0开头的八机制表示法,另外,ES6已经支持新的语法标准,八进制以0o来表示,这样一来就与16进制的0x形成统一的语法格式:
- 'use strict';
- var a = 017; //Uncaught SyntaxError: ...
- var b = 0o17; //ES6 Octal syntax: 8 + 7 = 15
7. 禁止使用with语句
最开始使用with语句时的心情是激动的,因为with语句将指定对象作为当前的作用域,可以很直接地存取对象的属性,使我们的代码变得更简洁。但同时它又是存在问题的,因为解析器在执行里面的代码时,会去检查对象中是否存在参与运算的属性,如果有则使用,没有的话则向上查找,这个过程在一定程度上降低了代码的执行性能,并且很难优化,另外,在可读性方面也表现极差,我们来看如下代码:
- var name = 'Scott';
- var person = getPerson();
- with(person) {
- name = newName;
- }
- 'use strict';
- var name = 'Scott';
- var person = getPerson();
- with(person) { //Uncaught SyntaxError: ...
- name = newName;
- }
常规模式下,使用eval函数可能会影响当前作用域或全局作用域,给程序的运行结果带来不确定性,严格模式为JavaScript程序创建了第三种作用域:eval作用域。eval函数中的字符串只能在eval作用域内运行,其结果不会影响外层作用域,下面这两种形式都可以使eval在严格模式下运行:
- 'use strict';
- eval("var a = 1;");
- console.log(a); //Uncaught ReferenceError: a is not defined
- //or
- eval("'use strict'; var b = 3;");
- console.log(b); //Uncaught ReferenceError: b is not defined
- 'use strict';
- ('' || eval)("var a = 1;"); //or ('', eval)
- console.log(a); //1
- var evl = eval;
- evl("var b = 3;");
- console.log(b); //3
- function exec(evl) {
- evl('var c = 5;');
- console.log(c); //5
- }
- exec(eval);
最后,大家可能会问,如果非要在严格模式下使用eval函数处理一个字符串,该如何将结果反映到当前作用域呢,我们需要像下面这样:
- 'use strict';
- var result = eval("var sum = 1 + 3 + 5; sum;");
- console.log(result); //9
9. 禁止对eval和arguments做非法操作
常规模式下的JavaScript的随意性较大,eval和arguments可以有很多稀奇古怪的用法,程序虽然可以运行,但这给代码的可读性、可维护性也带来了一些问题,下面这些代码段应尽量避免:
- function eval() {
- console.log('define a function called eval');
- };
- console.log(eval);
- function evalX(eval) {
- console.log('define a function with the eval keyword as parameter name');
- }
- console.log(evalX);
- function arguments() {
- console.log('define a function called arguments');
- };
- console.log(arguments);
- var func = new Function('arguments', 'return 3;');
- console.log(func);
- var eval = 1;
- console.log(++eval); //2
- var arguments = 3;
- console.log(++arguments); //4
- var person = {
- set name(arguments) {
- console.log(arguments); //Scott
- }
- };
- person.name = 'Scott';
- try {
- console.log(unknownVariable);
- } catch(arguments) {
- console.log(arguments); //ReferenceError: unknownVariable is not defined
- }
- 'use strict'; //strict mode is beyond the control
- var func = new Function('arguments', "return 3;");
- console.log(func);
- //declaring the 'use strict' in function body is a must
- var func = new Function('arguments', "'use strict'; return 3;");
- console.log(func);
常规模式下,在执行函数时如果我们更改参数的值,操作结果会立即反映到arguments对象中,反之,更改arguments对象中的值,结果也会立即反映到参数上:
- var fn = function(a, b, c) {
- a = 10;
- console.log(arguments[0]); // 10
- arguments[1] = 20;
- console.log(b); // 20
- };
- fn(1, 2, 3);
- 'use strict';
- var fn = function(a, b, c) {
- a = 10;
- console.log(arguments[0]); // 1
- arguments[1] = 20;
- console.log(b); // 2
- };
- fn(1, 2, 3);
11. 禁止使用arguments.callee
callee作为arguments对象的一个属性,我们可以在函数内部调用它来获取当前正在执行的函数,这在某些场景下特别有用,尤其是在匿名的递归函数中。假如我们有一个数组,现在需要对数组内的每个元素求阶乘,可能像下面代码这样:
- var factorialArray = [1, 2, 3, 4, 5].map(function(n) {
- return (n < 2) ? 1 : arguments.callee(n - 1) * n;
- });
- console.log(factorialArray); // [1, 2, 6, 24, 120]
那么为什么严格模式要禁止arguments.callee呢,其中一个原因是不能进行内联和尾递归的优化。下面这段代码我们使用了一个for循环来求循环中每个数值的阶乘:
- function getFactorial(n) {
- return (n < 2) ? 1 : arguments.callee(n - 1) * n;
- }
- function calculate() {
- for (var i = 0; i < 100; i++) {
- console.log(getFactorial(i));
- }
- }
由于每次调用getFactorial函数,在它的内部都会查找当前正在调用的函数,所以原本解析器可以对getFactorial函数做内联处理来提高性能的,现在使用了callee,大大影响了解析器的优化策略。下面这段代码是一个尾递归的例子:
- function factorial(n, result) {
- if (n < 2) return result;
- return factorial(n - 1, n * result);
- }
- console.log(factorial(5, 1));
但是如果我们在上面的递归中使用了arguments.callee,情况就不同了:
- function factorial(n, result) {
- if (n < 2) return result;
- return arguments.callee(n - 1, n * result);
- }
- console.log(factorial(5, 1));
以上就是内联和尾递归的优化问题,其实还有一个很重要的原因,那就是使用arguments.callee会更改函数中this的指向:
- function factorial(n, result) {
- console.log(this);
- if (n < 2) return result;
- return arguments.callee(n - 1, n * result);
- }
- console.log(factorial(5, 1));
可以看到,除了第一次外部调用外,使用arguments.callee的调用中,this都是arguments对象,如果函数中引用到了当前上下文,这也会对程序的结果造成不确定性。
所以,严格模式中禁止使用arguments.callee调用,如果声明了严格模式,遇到arguments.callee会抛出异常。
最后,如果不能使用arguments.callee,又如何在匿名函数中调用自身呢?从ES3开始命名函数表达式被引入语言特性中,所以我们可以像下面代码一样使用:
- var factorialArray = [1, 2, 3, 4, 5].map(function factorial(n) {
- return (n < 2) ? 1 : factorial(n - 1) * n;
- });
- console.log(factorialArray); // [1, 2, 6, 24, 120]
12. 禁止this指向全局
常规模式下,JavaScript太过于灵活,如果对语言特性了解的不够深入,常常会因为失误的的调用,造成不一致的结果,下面这段程序演示了this的指向问题:
- var name = 'Global';
- function Person() {
- this.name = 'Scott';
- }
- Person();
- console.log(name); //Scott
- var person = {
- name: 'John',
- getName: function() {
- return this.name;
- }
- };
- var getName = person.getName;
- console.log(getName()); //Scott
在严格模式中,this不被允许指向全局,如果运行时试图指向全局,this将会变为undefined,上面代码在严格模式中调用Person函数将会抛出异常,因为不能为undefined设置属性。我们可以用下面代码测试:
- 'use strict';
- var func = function() {
- return this;
- };
- console.log(func() === undefined); //true
- var func = function() {
- return this;
- };
- console.log(func.call('hello')); // Boxed String
- console.log(func.call(1)); // Boxed Number
- console.log(func.call(true)); // Boxed Boolean
- console.log(func.apply(null)); // Window
- console.log(func.apply(undefined)); // Window
- 'use strict';
- var func = function() {
- return this;
- };
- console.log(func.call('hello') === 'hello'); // true
- console.log(func.call(1) === 1); // true
- console.log(func.bind(true)() === true); // true
- console.log(func.apply(null) === null); // true
- console.log(func.call(undefined) === undefined); // true
13. 禁止使用function直接引用caller和arguments
上面我们刚刚介绍过,严格模式下禁止arguments.callee的用法,同样地,出于对性能和安全方面的考量,严格模式禁止通过函数名直接访问函数的调用栈相关信息,因此下面这段代码会直接抛出异常:
- function testFunc() {
- 'use strict';
- console.log(testFunc.caller); //Uncaught TypeError: ...
- console.log(testFunc.arguments); //Uncaught TypeError: ...
- }
- function main() {
- testFunc();
- }
- main();
14. 函数必须声明在整个脚本或函数层面
常规模式下,在语句块中声明函数会使得程序的结果不可预料,也会使可读性变得很糟糕,下面这段代码在不同浏览器中会有不同的结果:
- var condition = true;
- if (condition) {
- function doSomething() {
- console.log('1');
- }
- } else {
- function doSomething() {
- console.log('2');
- }
- }
- doSomething();
严格模式禁止在语句块中声明函数,下面代码在Chrome测试时,不会抛出语法异常,但会忽略doSomething函数的声明,其结果是,调用doSomething时抛出异常,提示函数未定义:
- 'use strict';
- if (true) {
- function doSomething() {
- console.log('1');
- }
- } else {
- function doSomething() {
- console.log('2');
- }
- }
- for (var i = 0; i < 5; i++) {
- function doSomething() {
- console.log('3');
- }
- }
- doSomething(); //Uncaught ReferenceError: doSomething is not defined
所以不应该在块语句中进行函数声明,而是要放在脚本和函数层面上声明。
15. 新增一些保留字ES5本质上来讲只是一个语言优化的过渡,约束了晦涩和不安全的语法,为以后的高级语法铺平道路。所以在ES5中新增了一些保留字,严格模式下,不能使用他们作为变量名或参数名:
implements, interface, let, package, private, protected, public, static, yield.
以上就是ES5严格模式的全部内容,如有遗漏或不一致的情况,再加以补充。