JavaScript中的严格模式与this的影响
JavaScript 中的严格模式
严格模式的引入
JavaScript 在发展过程中,逐渐暴露出一些设计上的缺陷和不一致性。为了让开发者编写更安全、更规范的代码,ECMAScript 5(ES5)引入了严格模式(strict mode)。严格模式并不是一种全新的语言,而是对 JavaScript 语法和运行时行为的一种增强和约束,让代码在更严格的条件下运行。
开启严格模式
在 JavaScript 中,可以通过在脚本或函数的开头添加 "use strict";
来开启严格模式。对于整个脚本开启严格模式,代码如下:
"use strict";
// 这里的所有代码都运行在严格模式下
console.log("This is strict mode.");
如果只想对某个函数开启严格模式,可以这样写:
function strictFunction() {
"use strict";
// 此函数内的代码运行在严格模式下
console.log("This function is in strict mode.");
}
strictFunction();
严格模式下的语法变化
- 禁止使用未声明的变量
在正常模式下,给未声明的变量赋值会在全局作用域中创建一个新的全局变量(这可能会导致意外的全局变量污染)。但在严格模式下,这种行为会抛出
ReferenceError
。例如:
// 正常模式
function normalMode() {
x = 10;
console.log(x); // 10,在全局作用域创建了变量 x
}
normalMode();
console.log(x); // 10
// 严格模式
function strictMode() {
"use strict";
y = 20;
console.log(y); // 抛出 ReferenceError: y is not defined
}
strictMode();
- 禁止删除变量、函数和函数参数
在严格模式下,使用
delete
操作符删除变量、函数或函数参数会抛出SyntaxError
。例如:
// 正常模式
var num = 10;
function func() {}
function normalDelete() {
delete num;
console.log(num); // 10,删除操作在正常模式下无效但不报错
delete func;
func(); // 函数仍可调用,删除操作在正常模式下无效但不报错
}
normalDelete();
// 严格模式
function strictDelete() {
"use strict";
var num = 10;
function func() {}
delete num; // 抛出 SyntaxError
delete func; // 抛出 SyntaxError
}
strictDelete();
- 函数参数名不能重复
在正常模式下,函数可以有重复的参数名,后面的参数会覆盖前面的参数。而在严格模式下,这种情况会抛出
SyntaxError
。例如:
// 正常模式
function normalFunction(a, a) {
console.log(a); // 后面的 a 会覆盖前面的 a
}
normalFunction(1, 2); // 2
// 严格模式
function strictFunction(a, a) {
"use strict";
console.log(a); // 抛出 SyntaxError
}
strictFunction(1, 2);
- 禁止使用八进制字面量
在早期 JavaScript 中,可以使用以 0 开头的数字表示八进制数。但在严格模式下,这种写法会抛出
SyntaxError
。例如:
// 正常模式
var num1 = 07;
console.log(num1); // 7
// 严格模式
function strictOctal() {
"use strict";
var num2 = 07; // 抛出 SyntaxError
console.log(num2);
}
strictOctal();
严格模式下的运行时变化
- 全局对象的变化
在正常模式下,全局作用域中的
this
指向全局对象(在浏览器中是window
,在 Node.js 中是global
)。但在严格模式下,全局作用域中的this
是undefined
。例如:
// 正常模式
console.log(this === window); // true
// 严格模式
function strictGlobalThis() {
"use strict";
console.log(this); // undefined
}
strictGlobalThis();
- 函数中的
this
绑定 在正常模式下,函数调用时如果没有明确指定this
的值(例如通过对象方法调用、call
、apply
或bind
等方式),this
会指向全局对象。但在严格模式下,函数调用时如果没有明确指定this
的值,this
会是undefined
。例如:
// 正常模式
function normalFunction() {
console.log(this); // 指向全局对象(在浏览器中是 window)
}
normalFunction();
// 严格模式
function strictFunction() {
"use strict";
console.log(this); // undefined
}
strictFunction();
- 构造函数中
this
的变化 在正常模式下,如果构造函数没有使用new
关键字调用,this
会指向全局对象,这可能会导致意外的全局变量创建。而在严格模式下,如果构造函数没有使用new
关键字调用,this
会是undefined
,并且会抛出TypeError
。例如:
// 正常模式
function NormalConstructor() {
this.value = 10;
}
var obj1 = new NormalConstructor();
console.log(obj1.value); // 10
var badObj1 = NormalConstructor();
console.log(window.value); // 10,意外创建了全局变量
// 严格模式
function StrictConstructor() {
"use strict";
this.value = 20;
}
var obj2 = new StrictConstructor();
console.log(obj2.value); // 20
var badObj2 = StrictConstructor(); // 抛出 TypeError: Cannot set property 'value' of undefined
this
在 JavaScript 中的本质
this
的绑定规则
- 默认绑定
当函数独立调用(不是作为对象的方法调用,也没有通过
call
、apply
或bind
明确指定this
)时,在非严格模式下,this
指向全局对象;在严格模式下,this
指向undefined
。例如:
function defaultBinding() {
console.log(this);
}
// 非严格模式
defaultBinding(); // 在浏览器中指向 window
// 严格模式
function strictDefaultBinding() {
"use strict";
console.log(this);
}
strictDefaultBinding(); // undefined
- 隐式绑定
当函数作为对象的方法调用时,
this
指向调用该方法的对象。例如:
var obj = {
value: 10,
method: function() {
console.log(this.value);
}
};
obj.method(); // 10,这里的 this 指向 obj
- 显式绑定
通过
call
、apply
或bind
方法可以显式地指定函数调用时this
的值。call
和apply
方法会立即调用函数,而bind
方法会返回一个新的函数,新函数的this
被绑定到指定的值。例如:
function explicitBinding() {
console.log(this.value);
}
var obj1 = {
value: 20
};
var obj2 = {
value: 30
};
explicitBinding.call(obj1); // 20,通过 call 显式指定 this 为 obj1
explicitBinding.apply(obj2); // 30,通过 apply 显式指定 this 为 obj2
var boundFunction = explicitBinding.bind(obj1);
boundFunction(); // 20,通过 bind 绑定 this 为 obj1 并调用
- new 绑定
当使用
new
关键字调用构造函数时,会创建一个新的对象,this
指向这个新创建的对象。例如:
function Constructor() {
this.value = 40;
}
var newObj = new Constructor();
console.log(newObj.value); // 40,这里的 this 指向 newObj
this
在不同场景下的复杂情况
- 函数内部的函数
在一个函数内部定义另一个函数时,内部函数的
this
绑定规则与外部函数是相互独立的。例如:
var outerObj = {
value: 10,
outerMethod: function() {
console.log(this.value); // 10,this 指向 outerObj
function innerFunction() {
console.log(this.value); // 在非严格模式下指向全局对象,在严格模式下是 undefined
}
innerFunction();
}
};
outerObj.outerMethod();
如果想让内部函数的 this
指向外部函数的 this
,可以使用 var that = this;
或箭头函数。例如:
var outerObj = {
value: 10,
outerMethod: function() {
var that = this;
function innerFunction() {
console.log(that.value); // 10
}
innerFunction();
}
};
outerObj.outerMethod();
使用箭头函数:
var outerObj = {
value: 10,
outerMethod: function() {
var innerFunction = () => {
console.log(this.value); // 10,箭头函数的 this 继承自外层作用域
};
innerFunction();
}
};
outerObj.outerMethod();
- 事件处理函数中的
this
在 DOM 事件处理函数中,this
通常指向触发事件的 DOM 元素。例如:
<button id="myButton">Click me</button>
<script>
var button = document.getElementById('myButton');
button.addEventListener('click', function() {
console.log(this === button); // true,this 指向 button 元素
});
</script>
但如果使用箭头函数作为事件处理函数,this
的指向会有所不同。箭头函数没有自己的 this
,它的 this
继承自外层作用域。例如:
<button id="myButton2">Click me 2</button>
<script>
var button2 = document.getElementById('myButton2');
var obj = {
value: 10,
handleClick: function() {
button2.addEventListener('click', () => {
console.log(this.value); // 10,这里的 this 指向 obj
});
}
};
obj.handleClick();
</script>
严格模式对 this
的影响深入分析
函数调用时 this
的严格性
- 防止意外的全局变量创建
在正常模式下,函数调用时如果
this
指向全局对象,并且函数内部对this
进行属性赋值,可能会意外地在全局对象上创建新的属性。例如:
function normalFunction() {
this.newVar = 10;
}
normalFunction();
console.log(window.newVar); // 10,意外创建了全局变量
而在严格模式下,由于函数调用时如果没有明确指定 this
的值,this
是 undefined
,对 this
进行属性赋值会抛出 TypeError
,从而避免了意外的全局变量创建。例如:
function strictFunction() {
"use strict";
this.newVar = 10; // 抛出 TypeError: Cannot set property 'newVar' of undefined
}
strictFunction();
- 明确
this
的指向 严格模式使得函数调用时this
的指向更加明确和可预测。在非严格模式下,函数调用时this
的默认指向全局对象可能会导致一些难以调试的问题,特别是在大型代码库中。而在严格模式下,默认情况下this
是undefined
,开发者必须更加谨慎地处理this
的绑定,从而减少潜在的错误。例如:
// 非严格模式
function nonStrictFunc() {
console.log(this); // 指向全局对象,可能导致误解
}
nonStrictFunc();
// 严格模式
function strictFunc() {
"use strict";
console.log(this); // undefined,明确 this 的指向
}
strictFunc();
构造函数中 this
的安全性
- 防止意外的全局对象污染
如前文所述,在正常模式下,如果构造函数没有使用
new
关键字调用,this
会指向全局对象,这可能会导致意外的全局变量创建。例如:
function NormalConstructor() {
this.value = 10;
}
var badObj = NormalConstructor();
console.log(window.value); // 10,意外创建了全局变量
在严格模式下,如果构造函数没有使用 new
关键字调用,this
是 undefined
,并且会抛出 TypeError
,从而避免了全局对象污染。例如:
function StrictConstructor() {
"use strict";
this.value = 20;
}
var badObj2 = StrictConstructor(); // 抛出 TypeError: Cannot set property 'value' of undefined
- 强制正确使用构造函数
严格模式通过这种方式强制开发者正确使用构造函数,即使用
new
关键字来创建对象实例。这有助于提高代码的可读性和可维护性,避免因错误调用构造函数而导致的难以调试的问题。
对闭包和内部函数中 this
的影响
- 闭包中
this
的变化 在闭包中,正常模式下内部函数的this
可能会因为不同的调用方式而指向不同的对象,这可能会导致一些难以理解的行为。例如:
var outerObj = {
value: 10,
outerMethod: function() {
function innerFunction() {
console.log(this.value); // 在非严格模式下指向全局对象,在严格模式下是 undefined
}
return innerFunction;
}
};
var innerFunc = outerObj.outerMethod();
innerFunc();
在严格模式下,内部函数的 this
更加明确地是 undefined
,这就要求开发者更加注意 this
的绑定问题。通常可以通过 var that = this;
或箭头函数来解决这个问题。例如:
var outerObj = {
value: 10,
outerMethod: function() {
var that = this;
function innerFunction() {
console.log(that.value); // 10
}
return innerFunction;
}
};
var innerFunc = outerObj.outerMethod();
innerFunc();
使用箭头函数:
var outerObj = {
value: 10,
outerMethod: function() {
var innerFunction = () => {
console.log(this.value); // 10,箭头函数的 this 继承自外层作用域
};
return innerFunction;
}
};
var innerFunc = outerObj.outerMethod();
innerFunc();
- 内部函数
this
与严格模式的协同 严格模式下内部函数this
的行为促使开发者更加规范地处理this
的绑定,避免因this
指向不明确而导致的错误。同时,箭头函数在严格模式下对于处理内部函数this
的继承问题提供了一种简洁而可靠的方式,使得代码在处理复杂的闭包和内部函数场景时更加健壮。
如何在实际开发中应用严格模式与 this
的知识
项目初始化时开启严格模式
在项目开发中,建议在项目的入口文件或模块中开启严格模式,这样整个项目的代码都能在严格模式下运行,有助于捕获潜在的错误。例如,在一个 Node.js 项目的 main.js
文件开头添加 "use strict";
:
"use strict";
// 项目的主要代码逻辑
const express = require('express');
const app = express();
//...
在前端项目中,可以在主 JavaScript 文件或模块的开头添加:
"use strict";
// 前端页面交互逻辑等代码
谨慎处理 this
绑定
- 对象方法调用
在定义对象方法时,要清楚
this
的指向。例如,在一个类中定义方法:
class MyClass {
constructor() {
this.value = 10;
}
myMethod() {
console.log(this.value);
}
}
var myObj = new MyClass();
myObj.myMethod(); // 10,这里 this 指向 myObj
- 事件处理函数
在处理 DOM 事件时,如果使用普通函数作为事件处理函数,要注意
this
指向触发事件的 DOM 元素。如果需要访问外部对象的属性,可以使用var that = this;
或箭头函数。例如:
<button id="myButton3">Click me 3</button>
<script>
var obj = {
value: 20,
handleClick: function() {
var button3 = document.getElementById('myButton3');
// 使用 var that = this;
var that = this;
button3.addEventListener('click', function() {
console.log(that.value); // 20
});
// 使用箭头函数
button3.addEventListener('click', () => {
console.log(this.value); // 20
});
}
};
obj.handleClick();
</script>
- 回调函数
在使用回调函数时,同样要注意
this
的绑定。例如,在数组的forEach
方法中:
var arr = [1, 2, 3];
var obj = {
value: 10,
printValue: function() {
arr.forEach(function(num) {
console.log(this.value); // 在非严格模式下指向全局对象,在严格模式下是 undefined
});
}
};
obj.printValue();
// 使用箭头函数解决 this 绑定问题
var obj2 = {
value: 10,
printValue: function() {
arr.forEach((num) => {
console.log(this.value); // 10
});
}
};
obj2.printValue();
利用严格模式进行代码审查和调试
- 捕获语法错误 严格模式下的语法限制有助于在代码编写阶段捕获错误。例如,禁止删除变量、函数和函数参数,禁止使用重复的函数参数名等规则,可以让开发者在早期发现代码中的不规范之处,避免在运行时出现难以调试的问题。
- 追踪
this
相关的错误 由于严格模式下this
的指向更加明确,当出现与this
相关的错误时,更容易追踪和定位问题。例如,如果在严格模式下函数调用时this
是undefined
,而期望它指向某个对象,就可以检查函数的调用方式和this
的绑定逻辑,快速找到问题所在。
总之,深入理解 JavaScript 中的严格模式与 this
的影响对于编写高质量、可维护的代码至关重要。在实际开发中,合理应用这些知识可以提高代码的安全性、可读性和健壮性。