箭头函数

与普通函数区别

  • 箭头函数 this 不可修改(call、apply、bind),在定义时决定:指向外层第一个普通函数。
  • 不能被 new
  • 没有 prototype
  • 没有 arguments

new 一个箭头函数

  • 会报错,提示: function is not a constructor;
  • babel 编译时,会把 this 转成 (void 0);

哪些不能用箭头函数

  • arguments
  • yield
  • 构造函数的原型方法上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const Person = function (age, name) {
this.age = age;
this.name = name;

const obj = { 0: 18, 1: "luyi", 2: "teacher", 3: "esnext" };
console.log(arguments.callee);
obj.length = 4;
console.log(Array.prototype.slice.call(obj, 2));
};

// const p = new Person(18, "luyi")

// arguments / callee / caller

const fibonacci = function (num) {
if (num <= 2) return 1;
return arguments.callee.caller(num - 1) + arguments.callee.caller(num - 2);
};

console.log(fibonacci(4));

模板字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const consoleList = function (student, teacher) {
console.log(`hello ${student}, I am ${teacher}, nice 2 meet U`);
// console.log("hello " + student + ", I am " + teacher + ", nice 2 meet U")
};

const consoleString = function (stringTemplate, ...restVal) {
console.log(
stringTemplate.reduce(
(total, item, index) => total + item + (restVal[index] || ""),
""
)
);
};

const stu = "my students";
const tea = "luyi";

// consoleString(['hello', ', I am ', ', nice 2 meet U'], stu, tea);
// 复杂的模板字符串语法
consoleString`hello ${stu}, I am ${tea}, nice 2 meet U`;

数组和对象

数组和对象的细节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 数组的细节
// 需要使用 Array.from 或者 .fill(0)
const funcGenerator = (num) =>
Array.from(new Array(num)).map((item) => (params) => console.log(params));
const funcGenerator = (num) =>
new Array(num).fill(0).map((item) => (params) => console.log(params));

// funcGenerator(10).map((func, index) => func(index));

// 对象的细节
console.log(NaN === NaN); // false
console.log(Object.is(NaN, NaN)); // true

// ES next 采用了 SameValueZero() 的比较。是一个引擎内置的比较方式。

console.log([NaN].indexOf(NaN)); // -1
console.log([NaN].includes(NaN)); // true

Object.assign

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Object.assign
// 深拷贝还是浅拷贝?

let dist = { foo: "foo" };
let bar = { bar: { bar: "bar" } };
let baz = { baz: "baz" };

const res = Object.assign(dist, bar, baz);

bar.bar.bar = "newBar";
baz.baz = "newBaz";

// 第一层是深拷贝,第二层是浅拷贝;
console.log(res); // { foo: 'foo', bar: { bar: 'newBar' }, baz: 'baz' };
// res -- dist;
console.log(res === dist); // true

get / set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Person {
constructor() {}
_age = "";
get age() {
console.log(`actually I am ${this._age} years old~`);
return "17";
}

set age(val) {
console.log(" It is useless to set my age, I am 17!");
this._age = val;
}
}

// const luyi = new Person();
// luyi.age = "35";
// console.log("luyi is", luyi.age);

// java -- mumber private

// Proxy 天生的代理模式
// Vue2 Vue3

Proxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const luyi = {
age: 35,
};

const luyiProxy = new Proxy(luyi, {
get: function (target, propKey, receiver) {
console.log("GET:", target, propKey);
return Reflect.get(target, propKey, receiver);
},
set: function (target, propKey, value, receiver) {
console.log("SET:", target, propKey, value);
return Reflect.set(target, propKey, value, receiver);
},
});

console.log((luyiProxy.age = 35));

断言函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 如何去实现一个断言函数?
const assert = new Proxy(
{},
{
set(target, warning, value) {
if (!value) {
console.error(warning);
}
},
}
);

const teacher = "luyi";
// 如果断言的内容是假的,我就打印
assert["The teacher is Luyi!!!"] = teacher === "yunyin";

receiver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const luyi = {
age: 35,
};
const luyiProxy = new Proxy(luyi, {
get: function (target, propKey, receiver) {
return receiver;
},
set: function (target, propKey, value, receiver) {
console.log("SET:", target, propKey, value);
return Reflect.set(target, propKey, value, receiver);
},
});
// receiver 指向原始的读操作所在的那个对象, 一般情况下,就是 Proxy 的实例。
console.log(luyiProxy.age === luyiProxy); // true

Reflect

  1. 将 Object 上一些明显属于语言内部的方法,放到 Reflect 对象上,现在 Object 和 Reflect 一同部署;
  2. 修改某些 Object 方法的返回结果,让其更合理;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const teacher = {
age: 18,
name: "luyi",
};

Reflect.defineProperty(teacher, "lessions", {
writable: false,
enumerable: false,
configurable: false,
value: "vue",
});

const res = Reflect.defineProperty(teacher, "lessions", {
writable: true,
enumerable: true,
configurable: true,
value: ["es6", "esnext"],
});

console.log(res);

// Object.defineProperty直接报错: Cannot redefine property: lessions
// Reflect.defineProperty 给 true or false

Map、Set、WeakMap、WeakSet

  • Weak 表示作为唯一的部分,必须是一个对象;
  • Weak 是一个弱引用,不用考虑 GC;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const foos = new WeakSet();

class Foo {
constructor() {
foos.add(this);
}
method() {
if (!foos.has(this)) {
throw new TypeError(" Foo.prototype.method 只能在实例上调用");
} else {
console.log("using methods");
}
}
}

let f = new Foo();
let b = {};
Foo.prototype.method.call(b);

迭代器,Iterator

  • 迭代器是一个接口,为各种不同的数据提供统一的访问机制。任何数据结构只要部署了 Iterator 接口,就可以完成遍历操作:
    • 本质:指针。
    • 该 接口主要供 for...of 消费。
    • for...of普通原始对象会报错:1. 类数组对象转成数组(…/Array.from/Array.prototype.slice.apply(arguments));2. 如果不是类数组对象,就给对象添加一个[Symbol.iterator]属性,并指向一个迭代器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var obj = {
a: 1,
b: 2,
c: 3,
};

obj[Symbol.iterator] = function* () {
var keys = Object.keys(this);
for (var k of keys) {
yield [k, obj[k]];
}
};

for (var [k, v] of obj) {
console.log(k, v);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// let m = new Map();
// m.set('a', 'foo');
// m.set('b', 'bar');
// m.set('c', 'baz');

// let k = m.keys();

// console.log(k.next());
// console.log(k.next());
// console.log(k.next());

let arr = [1, 2, 3, 4, 5];
let k = arr[Symbol.iterator]();

console.log(k.next());
console.log(k.next());
console.log(k.next());
console.log(k.next());
console.log(k.next());
console.log(k.next());

// generator
  • 原生具备 Iterator 的数据结构有:
    Array Map Set String TypedArray arguments NodeList

Object.entries

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const obj = { a: 11, b: 22, c: 33 };

console.log(Object.entries(obj)); // [ [ 'a', 11 ], [ 'b', 22 ], [ 'c', 33 ] ]
console.log(Object.keys(obj)); // [ 'a', 'b', 'c' ]
console.log(Object.values(obj)); // [ 11, 22, 33 ]

// 非 generator 的方法
function entries(obj) {
let arr = [];
for (let key of Object.keys(obj)) {
arr.push([key, obj[key]]);
}
return arr;
}

// generator 的方法
function* entires(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}

const k = entires(obj);

// console.log(k.next())
// console.log(k.next())
// console.log(k.next())
// console.log(k.next())

for (let item of k) {
console.log(item);
}

promise.allSettled

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function allSettled(array) {
return new Promise((resolve, reject) => {
if (!(array instanceof Array)) return reject(new Error(" not Array!"));
const res = [];
let count = 0;
array.forEach((func, index) => {
Promise.resolve(func)
.then(
(value) => {
res[index] = {
status: "fulfilled",
value,
};
},
(reason) => {
res[index] = {
status: "rejected",
reason,
};
}
)
.finally(() => {
++count === array.length && resolve(res);
});
});
});
}

let/const vs var

let/const 和 var 的区别

  1. var 污染全局
  2. 块级作用域
  3. 重复声明
  4. 暂时性死区(变量提升)

块级作用域

1
2
3
4
5
6
7
8
9
10
if (true) {
var arg1 = "云隐";
}
console.log("arg1", arg1);

if (true) {
const arg2 = "云隐";
}
console.log("arg2", arg2);
// 变量提升 + 块级作用域

变量提升

执行上下文

全局执行上下文、函数执行上下文、eval 函数执行上下文。可理解为一个抽象的对象

  • Variable object: 变量对象,存储被定义在执行上下文中的变量 (variables) 和函数声明 (function declarations)
  • Scope chain: 作用域链,对象列表 (list of objects),检索上下文代码中出现的标识符 (identifiers)
  • thisValue: this 指针,上下文对象

理解变量提升

执行上下文的生命周期可以分为三个阶段:创建、执行、释放

  1. var

    所有使用 var 声明的变量都会在执行上下文的创建阶段时作为变量对象的属性被创建并初始化,从而保证在执行阶段能通过标识符在变量对象里找到对应变量进行赋值操作等

    • 由名称和对应值 (undefined) 组成一个变量对象的属性被创建 (创建并初始化)
    • 如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性
  2. let

    其在执行上下文的创建阶段,只会创建变量而不会被初始化,并且 ES6 规定了其初始化过程是在执行上下文的执行阶段 (即直到它们的定义被执行时才初始化),使用未被初始化的变量将会报错 (ReferenceError)

  3. const

    const 与 let 类似,都具有上面提到的 let 的特性,唯一区别就在于 const 声明的是一个只读变量,声明之后不允许改变其值。因此,const 一旦声明必须初始化,否则会报错

    • 所谓的不可变不是指变量的值不变,而是保证变量指向的内存地址所保存的数据不允许改动 (即栈内存在的值和地址)

dead zone

在变量初始化前访问该变量会导致 ReferenceError ,因此从进入作用域创建变量,到变量开始可被访问的一段时间 (过程),称为暂存死区(Temporal Dead Zone)

1
2
3
4
if (true) {
console.log(arg1);
var arg1 = "云隐";
}

let or const

  • 引用型 const
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const obj = {
teacher: "云隐",
leader: "小可",
};
obj.leader = "部部";

const arr = ["云隐", "小可"];
arr[0] = "aaaa";

// 引用类型的原理 - 指向地址
// 追问 破局 - object.freeze()
object.freeze(obj);

const obj2 = {
teacher: "云隐",
leader: "小可",
zhuawa: ["黄小杨", "部部"],
};
object.freeze(obj2);
obj2.zhuawa[0] = "云隐";

// freeze只能冻结根层,嵌套引用类型需要遍历递归
// 引申
function deepFreeze() {
// 2. 确定主执行步骤
Object.freeze(obj);
// 3. 逐级深入
(Object.keys(obj) || []).forEach((key) => {
// for in - hasOwnProperty
let innerObj = obj[key];

if (typeof innerObj === "object") {
// 1. 递归模式确定
deepFreeze(innerObj);
}
});
}
// lodash: clone deepclone equal deepequal

class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 传统对象 - function
function Course(teacher, course) {
this.teacher = teacher;
this.course = course;
}

Course.prototype.getCourse = function () {
return `teacher is:${this.teacher}, course: ${this.course}`;
};

const course = new Course("云隐", "ES6");
course.getCourse();

// Es6
class Course {
// init 实例会默认执行
constructor(teacher, course) {
this.teacher = teacher;
this.course = course;
}

// 拓展方法
getCourse() {
return `teacher is:${this.teacher}, course: ${this.course}`;
}
}
const course = new Course("云隐", "ES6");
course.getCourse();

class 的类型

1
console.log(typeOf Course); // function

class 的 prototype

1
console.log(Course.prototype); // 有区分,但本质类型相同

class & 函数对象 属性

1
console.log(course.hasOwnProperty("teacher")); // true

属性定义 构造器 & 顶层定义 两种定义方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
class Course {
// init 实例会默认执行
constructor(teacher, course) {
this.teacher = teacher;
this.course = course;
}

// 拓展方法
getCourse() {
return `teacher is:${this.teacher}, course: ${this.course}`;
}

get teacher() {
// 留有空间
return this.teacher;
}

set teacher(val) {
// 留有空间
this.teacher = val;
}
}

// 意义何在?
// 1. js如何建立只读变量
class Course {
// init 实例会默认执行
constructor(teacher, course) {
this._teacher = teacher;
this.course = course;
}

// 拓展方法
getCourse() {
return `teacher is:${this.teacher}, course: ${this.course}`;
}

get teacher() {
// 留有空间
return this._teacher;
}
}
// 修改只读变量,会报错么 - 无法改变但是不会报错

// 2. js如何建立一个私有属性
class Course {
constructor(teacher, course) {
this._teacher = teacher;

// 在constructor作用域内定义一个局部变量
let _course = "es6";
// 内部通过闭包的形式去暴露该变量
this.getCourse = () => {
return _course;
};
}
}

class Course {
#course = "es6";
constructor(teacher, course) {
this._teacher = teacher;
}
get course() {
return this.#course;
}
set course(val) {
if (val) {
this.#course = val;
}
}
}

// 3. 封装核心 - 适配器模式
// 底层封装中台业务core
class utils {
constructor(core) {
this._main = core;
this._name = "my-utils";
this._id = "zhaowa专有";
}

// fullName: {firstName: '', lastName: '', name: ''}
get name() {
return {
...this._main.fullName,
...{
name: `utils is ${this._name}`,
},
};
}
get id() {
return {
...this._main.id,
id: this._id,
};
}
set name(val) {
// valid saftey
this._name = val;
}
}

静态方法

直接挂载在类上的方法无需实例化获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ES5
function Course() {
// ……
}
Course.ring = function () {
// ……
};

// ES6
class Course {
constructor() {
//……
}

static ring() {
//……
}
}

Course.ring();
// 全局对象变量问题

继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// es5继承
function Course() {
// ……
}
Course.ring = function () {
// ……
};
Course.prototype.send = function () {
// ……
};

function Child() {
Course.call(this, "云隐", "ES6");
this.run = function () {
// ……
};
}
Child.prototype = Course.prototype;

// es6
class Course {
constructor() {
//……
}
static ring() {}
send() {}
}
// => 工厂模式
class Child extends Course {
constructor() {
super("云隐", "ES6");
}
run() {}
}