MDN 阅读笔记
从大一到现在,一直在重度使用 JavaScript 相关的技术栈,却一直也没写过系统的笔记。主要是太熟悉,也因为 JavaScript 确实是一门不需要学习就能直接上手开发的语言。
最近要准备面试,想系统地看看 JS 的语法、特性,觉得是时候记录一些东西了。
这些笔记会忽略一些基础内容,仅记录遇到的问题,以及我认为值得记录的东西。
迭代器协议
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Iterators_and_Generators
可迭代对象(Iterable)具有迭代行为(for of
、...obj
、yield*
、([v1, v2]) = obj
),这种对象在 this[Symbol.iterator]
下实现了 @@iterator 方法。
@@iterator 方法被要求返回一个对象,该对象应该具有 next()
方法,该方法的返回值标识当前迭代值 value
和迭代状态 done
。
interface {
value: T;
done: boolean;
}
下面的例子是在测试时遇到的一个小坑:
function Collection(iterable) {
this[Symbol.iterator] = iterable[Symbol.iterator].bind(iterable)
}
const obj = new Collection([1, 2, 3])
for (const item of obj) {
console.log(item)
}
// out 1 2 3
需要注意的是,在 TS 中,无法直接使用 ES5 风格的 class。
@see https://github.com/microsoft/TypeScript/issues/18171
此外,生成器可以被用作实现 @@iterator 方法,其本身被调用后会返回一个迭代器(具有迭代器协议要求的 next()
方法)。
原型链
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N6NXAao5-1656773283943)(https://gaolihaiimg.oss-cn-beijing.aliyuncs.com/txwlpqbidm)]
function A() { }
function B() { }
B.prototype.__proto__ = A.prototype;
const b = new B();
// assert:
/*
b.__proto__ === B.prototype;
b.__proto__.__proto__ === A.prototype;
b.__proto__.__proto__.__proto__ === Object.prototype;
b..__proto__.__proto__.__proto__.__proto__ === null;
*/
值得注意的有两个东西,constructor
、prototype
。
二者互相引用,即 constructor
下保存了 prototype
的引用,而 prototype
又含有 constructor
的引用。
constructor
是实例化 obj 时使用的构造函数。prototype
是 obj 的原型对象。
当我们访问一个实例对象(一般意义上的)的 constructor
,实际上访问的是该对象的原型对象上的 constructor
属性,所以会有 obj.__proto__ === obj.constructor.prototype
。
但不是对所有对象都是这样,例如原型对象本身具有 constructor
属性,不会去它的原型对象上寻找,所以应该仅对实例对象有 obj.__proto__ === obj.constructor.prototype
。
此外,还要注意的是,正常情况下,原型链(以 __proto__
连接的)上总是原型对象。
constructor
构造函数也是函数(Function)
function T() {}
T.constructor === Function; // true
实例化
由于原型在构造函数上,所以实例化的行为取决于构造函数。
构造函数不同,导致原型对象(可能)不同,原型链不同。
var obj = new T();
- 创建一个空对象 obj
- obj.__proto__ = T.prototype
- obj.constructor = T
obj.constructor === T // true
this 指向
Browser
globalThis
是一个获取当前环境的顶级对象的标准途径,在 Browser
下,globalThis
是 window
。
当我们在全局作用域下声明的符号(方法和变量),该变量将作为顶级对象的一个属性:
function fn() {
console.log(this);
}
window.fn(); // window
上例中,我们在全局声明的 fn
方法被放置在了 window
对象下,作为其一个属性。
在函数作用域下声明时,则不会这样。
在我们直接调用方法,而不使用任何对象时:
function fn() {
console.log(this);
}
fn();
// strict mode: undefined
// default: window
在严格模式下,fn
引用本身不属于任何对象,其内部的 this
指向 undefined
。
而在非严格模式下,fn
内部的 this 将被替换为当前环境的顶级对象。
这与 call
的行为一致:api:Function.prototype.call
Node.js
Node.js 文件作用域实际上是一个函数作用域:
function Module() {
this.exports={}
}
function Fun(exports, require,module,__filename,__dirname) {
// Your code here
}
var module = new Module();
var exports = module.exports;
var options = [exports, require, module, filename, dirname];
Fun.apply(exports,options);
其中的 this 指向 module.exports
,这显然与浏览器中的全局作用域并不一样。所以其行为也会有所变化。
例如在文件作用域下声明的变量方法,不会被放置在顶级对象 global
下:
function fn() {
console.log(this);
}
globalThis.fn(); // error: fn is not a function
此外,其余行为与浏览器下一致。