JavaScript 基础
February 2, 2023
数据类型 #
Symbol #
符号类型,跟其他语言(ruby, racket 等)里的 Symbol 不一样
表示 唯一 标识符,创建即唯一
可用作对象的属性 key
对象的属性 key 只能是 string 或 symbol 类型,其他类型的值会被转为 string 类型
// id is a symbol with the description "id"
let id = Symbol("id");
let id1 = Symbol("id")
id !== id1
Symbol 不会被自动转为字符串,可以通过 toString() 方法进行转换
通常用于为第三方库的对象添加 隐藏 属性,而不对其他使用者造成影响
for in 遍历不会遍历 Symbol,Object.keys() 亦如是
Object.assign() 会 copy string 和 symbol 属性
Global Symbol
用途:全局唯一标识符
name 一样,即为同一个 Symbol
// read from the global registry
let id = Symbol.for("id"); // if the symbol did not exist, it is created
// read it again (maybe from another part of the code)
let idAgain = Symbol.for("id");
// the same symbol
alert( id === idAgain ); // true
System Symbol
例如: Symbol.iterator
Number #
-
二进制
以 0b 或 0B 开头的数字,ECMA2015 新增,有兼容性问题
-
八进制
0o 或 0O 开头的数字,ECMA2015 新增,有兼容性问题
也可以直接以 0 开头,如果以 0 开头的后面的所有的数字都比 8 小,则按 8 进制解析,否则按 10 进制解析
-
十六进制
以 0x 或 0X 开头的数字
一位 16 进制数,可以用 4 位二进制数表示,例如:\( (1110 0101)_2 = (E5)_{16}\)
-
BigInt
表示任意精度的整数,以 n 结尾,例如:123456789123456789n,0o777777777777n
WeakMap1 #
相比较Map 而言, WeakMap 不会阻塞垃圾收集,对内存友好
key 必须是对象,用于给对象添加额外的数据,当对象无引用时,添加的额外数据也应该同步被删掉
let john = { name: "John" };
let weakMap = new WeakMap();
weakMap.set(john, "...");
john = null; // overwrite the reference
// john is removed from memory!
// weakMap 为空
// 若为 Map, Map 不会为空
Map #
- key 的顺序不会自动排序,始终保留其插入时的相对顺序
- key 可以是任意数据类型
WeakSet #
只能向其中添加对象,而不是原始值
WeakMap 和 WeakSet 都不支持迭代,不支持获取当前的所有值
be an “additional” storage of data for objects which are stored/managed at another place
Proxy 1 #
proxy 是一个特殊的对象,(a transparent wrapper around target)
用于拦截对已有对象的访问和操作
internal method
引擎层面的实现,仅在 specification 中使用,无法在 js 中直接调用的方法
proxy trap
拦截引擎 (e.g. v8) 层面对 internal method 的调用
示例
internal method | handler | triggers when |
---|---|---|
[ [Get] ] | get | 读属性 |
[ [DefineOwnProperty] ] | defineProperty | Object.defineProperty |
[ [OwnPropertyKeys ] ] | ownKeys | for..in, Object.keys 等 |
let numbers = [0, 1, 2];
numbers = new Proxy(numbers, {
/**
* target 被代理的对象
* prop 被访问的属性
* receiver 仅在访问 getter 属性时候用到
*/
get(target, prop, receiver?) {
if (prop in target) {
return target[prop];
} else {
return 0; // default value
}
}
});
console.log( numbers[1] ); // 1
console.log( numbers[123] ); // 0 (no such item)
Reflect #
minimal wrappers around internal methods
每一个被 proxy 代理的内部方法,都有一个对应的 Reflect 方法,跟 proxy trap 一样的名字和参数
用于简化转发操作,简化 proxy handler 的写法,跟 Proxy 配合使用
示例
Operation | Reflect Call | internal method |
---|---|---|
obj[prop] | Reflect.get(obj, prop) | [ [Get] ] |
obj[prop] = value | Reflect.set(obj, prop, value) | [ [Set] ] |
let user = {
_name: "Guest",
get name() {
return this._name;
}
};
let userProxy = new Proxy(user, {
get(target, prop, receiver) { // receiver = admin
return Reflect.get(target, prop, receiver); // (*)
}
});
let admin = {
__proto__: userProxy,
_name: "Admin"
};
console.log(admin.name); // Admin
限制
Proxy 不能代理其没有的 slot,例如 Map 的 [ [ MapData ] ], private class fields, \(===\) 操作符等
e.g.
let map = new Map();
let proxy = new Proxy(map, {});
proxy.set('test', 1); // Error
Fix:
let map = new Map();
let proxy = new Proxy(map, {
get(target, prop, receiver) {
let value = Reflect.get(...arguments);
return typeof value == 'function' ? value.bind(target) : value;
}
});
proxy.set('test', 1);
alert(proxy.get('test')); // 1 (works!)
Array has no internal slots, for historical reasons
Var #
only a variable’s declaration is hoisted, not its initialization
var declarations are processed when the function starts (or script starts for globals)
var 的变量声明会被提升,变量赋值及初始化不会
var 没有块级作用域,只有函数和全局作用域
without use strict, an assignment to a non-existing variable creates a new global variable
Lexical Environment1 #
运行中的函数,代码块,脚本全局都有与之对应的 LE
词法环境对象,包括两部分:
- Environment Record 环境记录对象,保存局部变量,this 信息
- 指向外层词法环境的指针
A variable is a property of a special internal object, associated with the currently executing block/function/script
执行上下文分全局上下文、函数上下文和块级上下文
代码执行流每进入一个新上下文,都会创建一个作用域链,用于搜索变量和函数。
Function #
函数声明会在词法环境创建时,立刻初始化,所以我们可以在函数声明前调用函数
函数表达式不会
// 函数声明
function foo() {}
// 函数表达式
let a = function () {}
函数在每次调用时,都会创建一个与之关联的 LE
参数,局部变量,都是 ER 的一个属性
闭包 #
闭包即函数,能记住其外层作用域变量并使用
每个函数都有个隐藏的 [ [Environment] ] 属性,指向其被创建的词法环境
箭头函数 #
-
this 为外层词法环境的 this,没有 arguments, 没有 super,不能作为对象的方法,没有 prototype 属性
"use strict"; let obj = { i: 10, b: () => console.log(this.i, this), c() { console.log(this.i, this); }, }; obj.b(); // logs undefined, Window { /* … */ } (or the global object) obj.c(); // logs 10, Object { /* … */ } obj = { a: 10, }; Object.defineProperty(obj, "b", { get: () => { console.log(this.a, typeof this.a, this); // undefined 'undefined' Window { /* … */ } (or the global object) return this.a + 10; // represents global object 'Window', therefore 'this.a' returns 'undefined' }, });
不能通过 call, apply 给箭头函数设置 this 值,也不能用 bind。this 从词法环境中绑定,不会随着调用方式的不同而改变
-
不能用作 constructor, 不能被 new,不能访问 new.targe
因为,对于 constructor 而言,函数对象必须要有 [ [ Construct ] ] 内部方法,而箭头函数没有
-
不能在函数体内使用 yield,不能作为 generator 函数
PROTOTYPE #
[ [Prototype] ] #
Js 引擎层面的隐藏属性,决定继承关系,用户侧代码不可直接访问这个属性
__proto__ #
历史遗留的 getter/setter ,不建议使用,用于设置原型关系
建议使用这俩: Object.getPrototypeOf/Object.setPrototypeOf
ES5 的继承 #
// 定义一个父类
function Animal(name) {
this.name = name;
}
Animal.prototype.sayName = function() {
console.log('My name is ' + this.name);
};
// 定义一个子类
function Dog(name, breed) {
Animal.call(this, name); // 调用父类构造函数
this.breed = breed;
}
// 子类的原型对象为父类原型的实例
Dog.prototype = Object.create(Animal.prototype); // 继承父类的原型
Dog.prototype.constructor = Dog; // 修正子类构造函数
// 在子类上添加自己的方法
Dog.prototype.sayBreed = function() {
console.log('My breed is ' + this.breed);
};
// 创建一个实例并调用方法
var dog = new Dog('Tom', 'Husky');
dog.sayName(); // 输出 'My name is Tom'
dog.sayBreed(); // 输出 'My breed is Husky'
Class #
class 继承,子类会继承父类的 static 属性和方法
类字段定义在实例上,而不是原型对象上,对于函数字段而言,类的每个实例的创建都会新建一个函数,然后创建一个闭包,保存此函数绑定的变量,比较耗内存
类体有 this 上下文
class C {
a = 1;
autoBoundMethod = () => {
console.log(this.a);
};
}
const c = new C();
c.autoBoundMethod(); // 1
const { autoBoundMethod } = c;
autoBoundMethod(); // 1, If it were a normal method, it should be undefined in this case
class C {
a = 1;
constructor() {
this.method = this.method.bind(this);
}
method() {
console.log(this.a);
}
}
class Rabbit extends Animal {}
Static #
class Triple {
static customName = "Tripler";
static description = "I triple any number you provide";
static calculate(n = 1) {
return n * 3;
}
}
class SquaredTriple extends Triple {
static longDescription;
static description = "I square the triple of any number you provide";
static calculate(n) {
return super.calculate(n) * super.calculate(n);
}
}
console.log(Triple.description); // 'I triple any number you provide'
console.log(Triple.calculate()); // 3
console.log(Triple.calculate(6)); // 18
const tp = new Triple();
console.log(SquaredTriple.calculate(3)); // 81 (not affected by parent's instantiation)
console.log(SquaredTriple.description); // 'I square the triple of any number you provide'
console.log(SquaredTriple.longDescription); // undefined
console.log(SquaredTriple.customName); // 'Tripler'
// This throws because calculate() is a static member, not an instance member.
console.log(tp.calculate()); // 'tp.calculate is not a function'
值比较 #
当且仅当 x 为 NaN 时, x !== x 成立
-
===
isStrictlyEqual 算法
不会做类型转换
-
==
isLooselyEqual 算法
比较值时会做类型转换
-
Object.is
-
SameValue 算法
判断两个值是否一样,跟
===
一致,除了(NaN, +0, -0) 的比较console.log(Object.is('1', 1)); // Expected output: false console.log(Object.is(NaN, NaN)); // Expected output: true console.log(Object.is(-0, 0)); // Expected output: false const obj = {}; console.log(Object.is(obj, {})); // Expected output: false
-
SameValueZero
function sameValueZero(x, y) { if (typeof x === "number" && typeof y === "number") { // x and y are equal (may be -0 and 0) or they are both NaN return x === y || (x !== x && y !== y); } return x === y; }
JS 语句 #
for…in #
可枚举属性,不包括 symbol 属性,包括原型链上的继承属性
for (const prop in obj) {
if (Object.hasOwn(obj, prop)) {
// 自有可枚举属性
console.log(`obj.${prop} = ${obj[prop]}`);
}
}
for…of #
枚举 iterable
Object.keys #
自有可枚举属性
with #
常用 API #
func.call && func.apply #
func.apply(this, ['eat', 'bananas'])
func.call(this, 'eat', 'bananas')
isNaN && Number.isNaN #
Number.isNaN 比 isNaN 更健壮
isNaN:
参数转换成 Number,转换后为 NaN 时, 返回 true
Number.isNaN:
参数不会转换,当参数为 Number,且为 NaN 时返回 true
Object.hasOwn(obj, prop) #
Array.prototype.reduce() #
const array1 = [1, 2, 3, 4];
// 0 + 1 + 2 + 3 + 4
const initialValue = 0;
const sumWithInitial = array1.reduce(
(accumulator, currentValue) => accumulator + currentValue,
initialValue
);
console.log(sumWithInitial);
垃圾收集2 #
标记清除 #
Reachability:
当对象有指向其的引用,当前对象是不是能垃圾回收的
roots:
-
当前正在执行的函数,其局部变量和参数
-
当前调用链上的其他函数
-
全局变量
Any other value is considered reachable if it’s reachable from a root by a reference or by a chain of references.
从 roots 开始打标记,沿 root 的引用链继续打标记,没有被打上标记的对象会被 GC 掉
引擎层面的优化
引用计数 #
generational garbage collection #
V8 垃圾收集3 #
模块化 #
-
IIFE
-
AMD
Asynchronous Module Definition
依赖前置、提前执行, require.js
-
CMD4
Common Module Definition
依赖就近、延迟执行, sea.js
-
UMD
Universal Module Definition
-
CommonJS
缩写为 CJS, Node.js 的模块规范
-
ESM
语言层面的规范
CommonJS 的 require() 机制是完全同步的,而 ECMAScript module 的 import 机制则是异步的
严格模式 #
- with 不能用