MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

JavaScript中的严格模式与this的影响

2022-08-291.4k 阅读

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();

严格模式下的语法变化

  1. 禁止使用未声明的变量 在正常模式下,给未声明的变量赋值会在全局作用域中创建一个新的全局变量(这可能会导致意外的全局变量污染)。但在严格模式下,这种行为会抛出 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();
  1. 禁止删除变量、函数和函数参数 在严格模式下,使用 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();
  1. 函数参数名不能重复 在正常模式下,函数可以有重复的参数名,后面的参数会覆盖前面的参数。而在严格模式下,这种情况会抛出 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);
  1. 禁止使用八进制字面量 在早期 JavaScript 中,可以使用以 0 开头的数字表示八进制数。但在严格模式下,这种写法会抛出 SyntaxError。例如:
// 正常模式
var num1 = 07;
console.log(num1); // 7

// 严格模式
function strictOctal() {
    "use strict";
    var num2 = 07; // 抛出 SyntaxError
    console.log(num2);
}
strictOctal();

严格模式下的运行时变化

  1. 全局对象的变化 在正常模式下,全局作用域中的 this 指向全局对象(在浏览器中是 window,在 Node.js 中是 global)。但在严格模式下,全局作用域中的 thisundefined。例如:
// 正常模式
console.log(this === window); // true

// 严格模式
function strictGlobalThis() {
    "use strict";
    console.log(this); // undefined
}
strictGlobalThis();
  1. 函数中的 this 绑定 在正常模式下,函数调用时如果没有明确指定 this 的值(例如通过对象方法调用、callapplybind 等方式),this 会指向全局对象。但在严格模式下,函数调用时如果没有明确指定 this 的值,this 会是 undefined。例如:
// 正常模式
function normalFunction() {
    console.log(this); // 指向全局对象(在浏览器中是 window)
}
normalFunction();

// 严格模式
function strictFunction() {
    "use strict";
    console.log(this); // undefined
}
strictFunction();
  1. 构造函数中 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 的绑定规则

  1. 默认绑定 当函数独立调用(不是作为对象的方法调用,也没有通过 callapplybind 明确指定 this)时,在非严格模式下,this 指向全局对象;在严格模式下,this 指向 undefined。例如:
function defaultBinding() {
    console.log(this);
}
// 非严格模式
defaultBinding(); // 在浏览器中指向 window

// 严格模式
function strictDefaultBinding() {
    "use strict";
    console.log(this);
}
strictDefaultBinding(); // undefined
  1. 隐式绑定 当函数作为对象的方法调用时,this 指向调用该方法的对象。例如:
var obj = {
    value: 10,
    method: function() {
        console.log(this.value);
    }
};
obj.method(); // 10,这里的 this 指向 obj
  1. 显式绑定 通过 callapplybind 方法可以显式地指定函数调用时 this 的值。callapply 方法会立即调用函数,而 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 并调用
  1. new 绑定 当使用 new 关键字调用构造函数时,会创建一个新的对象,this 指向这个新创建的对象。例如:
function Constructor() {
    this.value = 40;
}
var newObj = new Constructor();
console.log(newObj.value); // 40,这里的 this 指向 newObj

this 在不同场景下的复杂情况

  1. 函数内部的函数 在一个函数内部定义另一个函数时,内部函数的 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();
  1. 事件处理函数中的 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 的严格性

  1. 防止意外的全局变量创建 在正常模式下,函数调用时如果 this 指向全局对象,并且函数内部对 this 进行属性赋值,可能会意外地在全局对象上创建新的属性。例如:
function normalFunction() {
    this.newVar = 10;
}
normalFunction();
console.log(window.newVar); // 10,意外创建了全局变量

而在严格模式下,由于函数调用时如果没有明确指定 this 的值,thisundefined,对 this 进行属性赋值会抛出 TypeError,从而避免了意外的全局变量创建。例如:

function strictFunction() {
    "use strict";
    this.newVar = 10; // 抛出 TypeError: Cannot set property 'newVar' of undefined
}
strictFunction();
  1. 明确 this 的指向 严格模式使得函数调用时 this 的指向更加明确和可预测。在非严格模式下,函数调用时 this 的默认指向全局对象可能会导致一些难以调试的问题,特别是在大型代码库中。而在严格模式下,默认情况下 thisundefined,开发者必须更加谨慎地处理 this 的绑定,从而减少潜在的错误。例如:
// 非严格模式
function nonStrictFunc() {
    console.log(this); // 指向全局对象,可能导致误解
}
nonStrictFunc();

// 严格模式
function strictFunc() {
    "use strict";
    console.log(this); // undefined,明确 this 的指向
}
strictFunc();

构造函数中 this 的安全性

  1. 防止意外的全局对象污染 如前文所述,在正常模式下,如果构造函数没有使用 new 关键字调用,this 会指向全局对象,这可能会导致意外的全局变量创建。例如:
function NormalConstructor() {
    this.value = 10;
}
var badObj = NormalConstructor();
console.log(window.value); // 10,意外创建了全局变量

在严格模式下,如果构造函数没有使用 new 关键字调用,thisundefined,并且会抛出 TypeError,从而避免了全局对象污染。例如:

function StrictConstructor() {
    "use strict";
    this.value = 20;
}
var badObj2 = StrictConstructor(); // 抛出 TypeError: Cannot set property 'value' of undefined
  1. 强制正确使用构造函数 严格模式通过这种方式强制开发者正确使用构造函数,即使用 new 关键字来创建对象实例。这有助于提高代码的可读性和可维护性,避免因错误调用构造函数而导致的难以调试的问题。

对闭包和内部函数中 this 的影响

  1. 闭包中 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();
  1. 内部函数 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 绑定

  1. 对象方法调用 在定义对象方法时,要清楚 this 的指向。例如,在一个类中定义方法:
class MyClass {
    constructor() {
        this.value = 10;
    }
    myMethod() {
        console.log(this.value);
    }
}
var myObj = new MyClass();
myObj.myMethod(); // 10,这里 this 指向 myObj
  1. 事件处理函数 在处理 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>
  1. 回调函数 在使用回调函数时,同样要注意 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();

利用严格模式进行代码审查和调试

  1. 捕获语法错误 严格模式下的语法限制有助于在代码编写阶段捕获错误。例如,禁止删除变量、函数和函数参数,禁止使用重复的函数参数名等规则,可以让开发者在早期发现代码中的不规范之处,避免在运行时出现难以调试的问题。
  2. 追踪 this 相关的错误 由于严格模式下 this 的指向更加明确,当出现与 this 相关的错误时,更容易追踪和定位问题。例如,如果在严格模式下函数调用时 thisundefined,而期望它指向某个对象,就可以检查函数的调用方式和 this 的绑定逻辑,快速找到问题所在。

总之,深入理解 JavaScript 中的严格模式与 this 的影响对于编写高质量、可维护的代码至关重要。在实际开发中,合理应用这些知识可以提高代码的安全性、可读性和健壮性。