JS 中的 this
是一个相对复杂的概念, 不是简单几句能解释清楚的. 比较不负责任的说法是 this
的值取决于函数是如何调用的. 我阅读了网上很多关于 this
的文章, Arnav Aggrawal 写的比较清楚. this
取值符合以下规则:
- 通过
new
关键字调用函数,this
会是一个全新的object
- 使用
apply
,call
或者bind
来调用/创建函数,this
的值取决于传入的第一个参数 - 如果函数作为对象的方法被调用, 比如
obj.method()
,this
的值为该函数所作为属性的object
, 比如obj
- 如果函数调用时不满足上述条件, 也就是
free function
,this
的值为全局对象. 浏览器环境下是window
对象, 但是在严格模式下('use strict'
),this
的值为undefined
- ES2015(ES6) 提出的箭头函数(Arrow function)不符合上述规则, 箭头函数
this
的值是该函数被创建时
的作用域
更深入的解释可以去查看他在 Medium 上的文章
- https://codeburst.io/the-simple-rules-to-this-in-javascript-35d97f31bde3
- https://stackoverflow.com/a/3127440/1751946
当你先前没有使用 var
, let
或 const
创建一个变量, 就直接给这个标识符(identifier, 或者说变量名?)赋值时, 你就会创建一个**未定义(Undeclared)**变量. 未定义变量不会存在于当前作用域, 而是默认定义在全局作用域(globally scope). 严格模式下, 你给一个未定义变量赋值时会抛出 ReferenceError
错误. 使用未定义变量不是好习惯, 这跟尽量不使用全局变量是一个道理, 应最大化的避免. 将代码包裹在 try
/catch
中能帮你检查出未定义变量.
function foo() {
x = 1; // Throws a ReferenceError in strict mode
}
foo();
console.log(x); // 1
undefined
指的是你定义了变量, 但没有赋值. 这个变量的类型就是 undefined
. 如果函数没写返回值, 默认也会是 undefined
. 检查变量是不是 undefined
需要用到严格等于(===
)操作符或 typeof
(foo === undefined
or typeof foo === 'undefined'
). 需要注意的是你不能用抽象相等操作符(==
)来判断, 因为 null
值也会返回 true
(null == undefined
).
var foo;
console.log(foo); // undefined
console.log(foo === undefined); // true
console.log(typeof foo === 'undefined'); // true
console.log(foo == null); // true. Wrong, don't use this to check!
function bar() {}
var baz = bar();
console.log(baz); // undefined
null
值只能是被显式赋值给变量. 它代表无意义
或是空值
, 并且和被显式赋值 undefined
的变量意义不同. 检查 null
值需要使用严格相等运算符.
var foo = null;
console.log(foo === null); // true
console.log(foo == undefined); // true. Wrong, don't use this to check!
作为一个好习惯, 你应该避免使用未定义或为未声明(undeclared or unassigned)的变量. 如果定义了暂时没有用到的变量,我会在声明后明确地给它们赋值为 null
.
译者注: 这部分应借助一些工具比如代码检查
eslint
, 静态类型检查:flow
,typescript
, 而不是刀耕火种
- https://stackoverflow.com/questions/15985875/effect-of-declared-and-undeclared-variables
- https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/undefined
它们可以用于立即执行函数(IIFE
), 将代码密封到局部作用域, 使得其中声明的变量不会污染全局作用域.
(function() {
// Some code here.
})();
作为一次使用的回调,不需要在其他地方使用. 当处理程序在调用它们的代码内部进行定义时,代码看起来更具自包含性和可读性,而不必在别处搜索以查找函数体.
作为一次性回调函数使用, 并且没有其他地方引用时, 建议用匿名函数. 这使得代码更具有自包含性和可读性(self-contained and readable
).
setTimeout(function() {
console.log('Hello world!');
}, 1000);
匿名函数也可以用于函数式编程结构的参数, 或 Lodash 方法的参数(类似回调函数).
const arr = [1, 2, 3];
const double = arr.map(function(el) {
return el * 2;
});
console.log(double); // [2, 4, 6]
译者注: 其实匿名函数还有更多可细分的用处, 一般作为工程优化和最佳实践部分出现.
- https://www.quora.com/What-is-a-typical-usecase-for-anonymous-functions
- https://stackoverflow.com/questions/10273185/what-are-the-benefits-to-using-anonymous-functions-instead-of-named-functions-fo
ECMA-262
把本地对象(native object)定义为 独立于宿主环境的 ECMAScript 实现提供的对象
, 比如 String
, Math
, RegExp
, Object
, Function
等等.
所有非本地对象都是宿主对象(host object),即由 ECMAScript 实现的宿主环境(浏览器或者 nodejs)提供的对象, 比如 window
, XMLHTTPRequest
等.
译者注: 这是一个有点冷门的概念, 除了宿主对象和本地对象, 还有一个常提到的内置对象(built-in object). ECMA-262 把内置对象定义为
由 ECMAScript 实现提供的, 独立于宿主环境的所有对象, 在 ECMAScript 程序开始执行时出现
. 这意味着开发者不必明确实例化内置对象, 它已被实例化了.
document.write()
将一串文本写入由 document.open()
打开的文档流中. document.write()
在页面加载后执行, 清除整个 dom
树(包括 <head>
和 <body>
), 并使用参数替换. 这是一个很危险也容易被误用的方法.
网上有一些说法是将它用于代码分析, 或者是某些特殊情况(比如你希望只有浏览器允许 JS 脚本执行时, 才显示样式), 也可以在 HTML5 中用于并行加载脚本并保留执行顺序. 但是我怀疑这些可能性都已经过时了, 现在我们不用 document.write()
也能实现目的.
译者注: 确实, 这个问题也只有在面试时才有可能被问到了.
- https://www.quirksmode.org/blog/archives/2005/06/three_javascrip_1.html
- https://github.com/h5bp/html5-boilerplate/wiki/Script-Loading-Techniques#documentwrite-script-tag
优点
- 交互性更好. 动态更改内容, 无需重新加载整个页面
- 减少与服务器的连接, 因为
scripts
和stylesheets
只需要被请求一次 - Webapp 的状态可以维护在页面上, JavaScript 变量和 DOM 状态会一直保持
- 基本上和
SPA
的优点一致
缺点
- 动态网页不容易被收藏
- 浏览器禁用 JavaScript 后, 页面不能正常访问
- 有些网络爬虫不执行
JavaScript
, 也不会看到动态加载的内容 - 基本上和
SPA
的缺点一致
使用 var
关键字声明或初始化的变量, 会将 变量的声明
提升到当前作用域的顶部, 但是赋值(如果有变量赋值)的位置不变. 下面用几个例子说明一下.
// 用 var 声明变量会被提升
console.log(foo); // undefined
var foo = 1;
console.log(foo); // 1
// 使用 let/const 声明变量则不会被提升
console.log(bar); // ReferenceError: bar is not defined
let bar = 2;
console.log(bar); // 2
通过函数体声明的函数存在提升, 通过函数表达式(将函数声明为变量)声明的函数只有变量被提升.
// Function Declaration
console.log(foo); // [Function: foo]
foo(); // 'FOOOOO'
function foo() {
console.log('FOOOOO');
}
console.log(foo); // [Function: foo]
// Function Expression
console.log(bar); // undefined
bar(); // Uncaught TypeError: bar is not a function
var bar = function() {
console.log('BARRRR');
};
console.log(bar); // [Function: bar]
译者注: 这两句话着实难翻译, 还是看例子来的明白