作用域变量

var 的问题

  1. 可以重复声明
  2. 不能定义常量
  3. 不支持块级作用域

块级作用域

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
/*
* 不会污染全局对象: 作用域外拿不到
*/
if (true) { let c = 1 }
console.log(c); // c is not defined

/*
* for 循环中也可以使用 i
*/
for (var i = 0; i < 3; i++) {
setTimeout(() => { console.log(i) }, 1000)
} // 输出 2

for (let i = 0; i < 3; i++) {
setTimeout(() => { console.log(i) }, 1000)
} // 输出 0, 1, 2

// var 可以依靠函数来生成作用域
for (var i = 0; i < 3; i++) {
(function (i) {
setTimeout(() => { console.log(i) }, 1000)
})(i)
} // 输出 0, 1, 2

/*
* 重复定义会报错: 变量定义不能重复声明
*/
let a = 1;
let a = 2; // Identifier 'a' has already been declared

/*
* 实现块级作用域 & 闭包的新写法
*/
// 以前 JS 只有两个作用域, 一个全局, 一个函数
;(function () {})();

// 现在直接这样写即可
let a = 1;
{
// let 没有预解释 (不存在变量的域解释)
console.log(a); // a is not defined
// 不会污染全局对象
let a = 10;
}

常量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
* 常量不能重新赋值
*/
const b = 3;
b = 4; // Assignment to constant variable

/*
* 变量值可以改变
* 虽然说常量不能再引用别的对象了, 但是它的值如果是一个引用类型的话, 引用对象的属性还是可以修改的
*/
const SAY = { sth: 'hi' }
SAY.sth = 'bye'

/*
* 不同的块级作用域可以多次定义
*/
const PI = 3.14;
const PI = 3.15; // Identifier 'a' has already been declared
{
const PI = 3.14; // ok
}

解构

分解一个对象的结构

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
/*
* 解析数据
*/
let arr = [1, 2, 3];
// 解构的时候, 等号的两边结构类似, 右边还必须是一个真实的值
let [a, b, c] = arr;

/*
* 嵌套赋值
*/
let arr2 = [{ name: 'eds', age: '18' }, [1, 2], 3];
let [{ name, age }, [d, e], f] = arr2;
let [obj, arr3, num] = arr;

/*
* 省略赋值
*/
let arr4 = [1, 2, 3];
let [,,j] = arr4;

/*
* 解构对象 & 默认值
*/
let obj = { name: 'eds', age: 18 }
let { name: myname, age: myage } = obj;
let { name, age = 18 } = obj;

字符串

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
/*
* 模版字符串
*/
let name = 'eds', age = 18;
let desc = name + ' 今年 ' + age + ' 岁了';
let strDesc = `${name} 今年 ${age} 岁了`;
// 模版的实现
function replace(desc) {
return desc.replace(/\$\{([^}]+)\}/g, function (matched, key) {
console.log(arguments);
return eval(key)
})
}
replace(strDesc);

/*
* 带标签的模版字符串(标签模版 Tagged template)
* 模版字符串可以紧跟在一个函数后面, 该函数将被调用来处理这个模版字符串
*/
let name = 'eds', age = 18;
function desc(strings, ...values) {
console.log(strings); // ["", " 今年 ", " 岁", raw: ["", " 今年 ", " 岁"]]
console.log(values); // ["eds", 18]
let result = '';
for (let i = 0; i < values.length; i++) {
result += (strings[i] + values[i]);
}
result += strings[strings.length - 1];
// 在这儿可以处理结果
return result.toUpperCase();
}
desc`${name} 今年 ${age} 岁`;

alert('hi') // 等价于 alert`hi`


/*
* includes
*/
let content = 'abc';
content.includes('b');
// 原理
content.indexOf('b') !== -1;

函数

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/*
* 默认参数
* 1. 必填项不填报错
* 2. 有些参数没有给传参的话可以有默认值
*/
function ajax(url = new Error('不能为空'), method = 'GET', dataType = 'json') {
console.log(url, method, dataType)
}
// 本质为
function ajax() {
var url = arguments.lengtΩh > 0 && arguments[0] !== undefined ? arguments[0] : new Error('不能为空');
var method = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'GET';
var dataType = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'json';
// ...
}

/*
* 展开操作符 & 剩余操作符
* 展开运算符: 相当于把数组中的每个元素依次取出放在数组中
*/
function sum(prefix, ...rest) {
// 1. 循环求和
let result = 0;
// 循环数组中的每个元素, 把它们依次传入对应的函数
rest.forEach((item) => {
result += item;
})
return prefix + result;
}
sum('$', 1, 2, 3, 4)
// 2. reduce 计算/汇总/收敛: 把一个数组中的一堆值计算出来一个值
// 第二个参数表是初始值, 上一次执行结果会成为下一次的初始值
// 如果没有给初始值的话, 第一次执行函数的时候, val = 第一个元素, item = 第二个元素
let arr = [1, 2, 3, 4];
let result = arr.reduce(function (val, item, index, origin) {
let sum = val + item; // 返回值会成为下一次函数执行的时候的 val
// 求平均值
if (index === origin.length - 1) {
return sum / origin.length
} else {
return sum
}
}, 0)
// reduce 实现
Array.prototype.reduce = function (reducer, initialVal) {
for (let i = 0; i < this.length; i++) {
initialVal = reducer(initialVal, this[i]);
}
return initialVal
}

let arr1 = [4, 5, 6];
let max = Math.max(...arr1);
// 实现
let max = Math.max.apply(Math, arr1)

let name = { name: 'eds' }
let age = { age: 18 }
let desc = { name: 'eds', age: 18 } // 怎么实现?
// 1. 循环赋值
for (let key in name) { desc[key] = name[key] }
for (let key in age) { desc[key] = age[key] }
// 2. assign: 第一个参数是 target, 后面都是来源对象
desc = Object.assign({}, name, age)
// 3. 对象解构
desc = {...name, ...age}

// 深拷贝
JSON.parse(JSON.stringify())
// 优化
function deepClone(origin) {
let newObj = {};
for (let key in origin) {
if (typeof origin[key] === 'object') {
newObj[key] = deepClone(origin[key]);
} else {
newObj[key] = origin[key];
}
return newObj;
}
}

/*
* 箭头函数
* 声明函数更简单的方法
* 如果有且只有一个参数, 可以省略小括号
* 如果只有返回值, 没有的函数体代码, 则可以省略 {}
* 箭头函数没有自己的 this, 会使用上层的 this
* 箭头函数的 this 是定死的, 指向外层 this
*/
let sum = (a, b) => {
return a + b
}
let double = num => num * 2
let obj = {
name: 'eds',
say() {
setTimeout(() => {
alert(`${name}: hello~`)
}, 1000)
}
}
// 本质实现
let obj = {
name: 'eds',
say() {
let _this = this;
setTimeout(() => {
alert(`${_this.name}: hello~`) // : hello~
}, 1000)
}
}
obj.say()

// 但是不能应用到所有的情况, 下面的 this 指向了最外层 this, 所以什么都没有打印出来
let obj1 = {
name: 'eds',
say: () => {
console.log(this.name);
}
}
let obj2 = {
name: 'mars',
gN: obj1.say
}
obj1.say(); // ''
obj2.gN(); // ''

数组

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
/*
* filter 实现
*/
Array.prototype.filter = function (fn) {
let newArr = [];
for (let i = 0, i < this.length; i++) {
let flag = fn(this[i]);
flag && newArr.push(this[i]);
}
return newArr;
}

/*
* find & findIndex 实现
*/
Array.prototype.find = function (fn) {
for (let i = 0; i < this.length; i++) {
let flag = fn(this[i]);
if (flag) {
return this[i];
}
}
}
Array.prototype.findIndex = function (fn) {
for (let i = 0; i < this.length; i++) {
let flag = fn(this[i]);
if (flag) {
return i;
}
}
}

/*
* some & every 实现
*/
Array.prototype.some = function (fn) {
for (let i = 0; i < this.length; i++) {
let flag = fn(this[i]);
if (flag) {
return flag;
}
return false;
}
}
Array.prototype.every = function (fn) {
let flag = true;
for (let i = 0; i < this.length; i++) {
let flag = fn(this[i]);
if (!flag) {
return false;
}
return flag;
}
}

/*
* from
* 将类数组转成数组
*/
function example() {
Array.from(arguments).forEach(function (item) {
console.log(item);
})
}

对象

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
/*
* 如果对象的属性名和变量名一样的话可以二合一
*/
let name = 'eds';
let age = 18;
let obj = {name, age}

/*
* 设置一个对象的原型为另一个对象
*/
let obj1 = {
age: 1,
getFood() { return '面包' }
}
// 以下三种等价
let obj2 = Object.setPrototypeOf(obj2, obj1);
obj2.__proto__ = obj1;
let obj2 = {
__proto__: obj1,
getFood() { return '牛奶' },
// super 可以调用父亲的方法
getParentFood() { return super.getFood() }
}
obj2.getFood() // '牛奶'
obj2.getParentFood() // '面包'

/*
* create 实现
*/
Object.create = function (prototype) {
// 创建一个空的函数
function Fn() {};
Fn.prototype = prototype;
// 返回这个函数的实例
return new Fn(); // __proto__
}

类的定义

以前 JS 中类和构造函数是一体的, 类中可以定义构造函数, 当创建一个类的实例时就会调用构造函数

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
class Parent {
constructor(name) {
// 实例的私有属性
this.name = name;
}
// 静态属性是类的属性
static hello() {
console.log('hello');
}
// 属于实例的公有属性, 也就是相当于原型上的属性
getName() {
console.log(this.name);
}
}

////////////// ES5 会编译为以下内容 //////////////

function _instanceof(left, right) {
if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
return !!right[Symbol.hasInstance](left);
} else {
return left instanceof right;
}
}
// 类的调用检查
// 1. 参数是类的实例
// 2. 参数构造函数
function _classCallCheck(instance, Constructor) {
// 如果这个实例不是这个构造函数的实例, 就会报错
if (!_instanceof(instance, Constructor)) {
// 不能把一个类当作普通函数来调用
throw new TypeError("Cannot call a class as a function");
}
}
// 1. 目标
// 2. 属性对象数组
function _defineProperties(target, props) {
// 循环每个元素
for (var i = 0; i < props.length; i++) {
// 取出每个属性描述器
var descriptor = props[i];
// 可枚举: for in 可以循环出来
descriptor.enumerable = descriptor.enumerable || false;
// 可配置: 可通过 delete 删除此属性
descriptor.configurable = true;
// 可修改值
if ("value" in descriptor) descriptor.writable = true;
// 真正的向 Parent 类的原型对象上增加属性
Object.defineProperty(target, descriptor.key, descriptor);
}
}
// 创建类
// 1. 构造函数
// 2. 原型上的属性
// 3. 静态属性(类上的属性)
function _createClass(Constructor, protoProps, staticProps) {
// 如果有原型属性的话
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
// 如果有静态属性的话
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}

var Parent = /*#__PURE__*/function () {
// 就是 constructor
function Parent(name) {
// 为了保证这个类只能用来 new 对象
_classCallCheck(this, Parent);
this.name = name;
}
_createClass(Parent, [{
key: "getName",
value: function getName() {
console.log(this.name);
}
}], [{
key: "hello",
value: function hello() {
console.log('hello');
}
}]);
return Parent;
}();

///////////////////////////////////////////////

// Class constructor Parent cannot be invoked without 'new'
// 类的构造函数 Parent 不能在不通过 new 的情况下调用
Parent('eds');

let p = new Parent('eds');

类的继承

继承是通过 __proto__ 来关联的

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
class Parent {
constructor(name) {
this.name = name;
}
static hello() {
console.log('hello');
}
getName() {
console.log(this.name);
}
}
class Child extends Parent {
constructor(name, age) {
// super 指的是父类的函数
super(name);
this.age = age;
}
getAge() {
console.log(this.age);
}
}

////////////// ES5 会编译为以下内容 //////////////

function _typeof(obj) {
"@babel/helpers - typeof";
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function _typeof(obj) {
return typeof obj;
};
} else {
_typeof = function _typeof(obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype
? "symbol"
: typeof obj;
};
}
return _typeof(obj);
}
function _inherits(subClass, superClass) {
// 如果父类不是函数, 并且父类不等于 null
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
// 给子类的构造函数重写原型 prototype
subClass.prototype = Object.create(superClass && superClass.prototype, {
// 让子类的 prototype 等于父类的一个实例,
// 另外还要覆盖 constructor, 让 constructor 指向 subClass(子类),
// 否则 constructor 会指向 superClass(父类)
// 重写 constructor, 如果不重写的话, superClass.constructor = superClass
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
// subClass.__proto__ = superClass
// 让子类的 __proto__ 等于父类, 这一步是为了让子类继承父类的静态属性
if (superClass) _setPrototypeOf(subClass, superClass);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p; return o;
};
return _setPrototypeOf(o, p);
}
function _createSuper(Derived) {
var hasNativeReflectConstruct = _isNativeReflectConstruct();
return function _createSuperInternal() {
var Super = _getPrototypeOf(Derived), result;
if (hasNativeReflectConstruct) {
var NewTarget = _getPrototypeOf(this).constructor;
result = Reflect.construct(Super, arguments, NewTarget);
} else {
result = Super.apply(this, arguments);
}
return _possibleConstructorReturn(this, result);
};
}
function _possibleConstructorReturn(self, call) {
if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; }
return _assertThisInitialized(self);
}
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
function _isNativeReflectConstruct() {
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
return true;
} catch (e) {
return false;
}
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf
? Object.getPrototypeOf
: function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
function _instanceof(left, right) {
if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
return !!right[Symbol.hasInstance](left);
} else {
return left instanceof right;
}
}
function _classCallCheck(instance, Constructor) {
if (!_instanceof(instance, Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}

var Parent = /*#__PURE__*/function () {
function Parent(name) {
_classCallCheck(this, Parent);
this.name = name;
}
_createClass(Parent, [{
key: "getName",
value: function getName() {
console.log(this.name);
}
}], [{
key: "hello",
value: function hello() {
console.log('hello');
}
}]);
return Parent;
}();

var Child = /*#__PURE__*/function (_Parent) {
_inherits(Child, _Parent);
var _super = _createSuper(Child);
function Child(name, age) {
var _this;
// 进行类调用检查, 保证只能 new
_classCallCheck(this, Child);
_this = _super.call(this, name);
_this.age = age;
return _this;
}
_createClass(Child, [{
key: "getAge",
value: function getAge() {
console.log(this.age);
}
}]);
return Child;
}(Parent);

///////////////////////////////////////////////

// 简单的例子
function Parent(name) { this.name = name }
// 静态属性是属于类的, 不需要实例就能调用
// 一个属性如果放在原型上的话, 是可以通过实例来调用的
// 但是放在类上的, 不能通过实例来调用, 只能用类名来调用
Parent.hello = 'hello';
function Child() {}
Child.prototype = Parent.prototype; // 这样写会导致子类继承了父类的私有属性
Child.prototype = Object.create(Parent.prototype);
// 如果不将 constructor 指向子类, 它会指向父类
Child.prototype.constructor = Child;

Generator

生成器(Generator)和迭代器(Iterator), 是理解 koa 的基础, 另外也是现代异步解决方案 async await 的基础(它的语法糖)

Iterator 迭代器

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
function say(somethings) {
let index = 0;
return {
next() {
// 只要能取到就为 false, 娶不到值才为 true
let done = (index === somethings.length);
let value = done ? undefined : somethings[index++];
return {
value,
done
}
}
}
}
// 迭代器: 内部有很多东西, 要一步步将它迭代(不停调用 next 方法)出来, 当 done 为 true 时表示取完了
// 它不关心如何存储, 每次只取一个, 直到取完为止
// it 有一个方法 next, 每次调用 next 都会返回一个结果 {value, done}
let it = say(['hi~', 'bye~', 'wtf!'])
let r1 = it.next(); // { value: 'hi~', done: false }
let r2 = it.next(); // { value: 'bye~', done: true }
// 如果有很多参数, 需要使用 do while 循环调用
let result;
do {
result = it.next();
} while (!result.done);

Generator 生成器

生成器函数会返回迭代器

生成器函数其实是内部生成了很多个小函数

与普通函数相比

  • 形式不一样: function *fn() {} / function* fn() {} / function * fn() {}
  • 执行时也不一样: 到 yield 会停下等待产出结果, 而不是普通函数直接执行完毕
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function *say(somethings) {
for (let i = 0; i < somethings; i++) {
yield somethings[i];
}
}
let it = say(['hi~', 'bye~'])
let r1 = it.next(); // { value: 'hi~', done: false }
let r2 = it.next(); // { value: 'bye~', done: false }
let r3 = it.next(); // { value: undefined, done: true }
// 也可以使用循环
let result;
do {
result = it.next();
} while (!result.done);

集合

Set

一个 Set 是一堆东西的集合, Set 有点像数组, 但是跟数组的区别是 Set 内不能有重复的内容

1
2
3
4
5
6
7
8
9
10
11
let books = new Set();
books.add('js');
books.add('js'); // 添加重复元素集合的元素, 个数不会改变
books.add('node');
books.forEach(function (book) { // 循环集合
console.log(book);
})
books.size; // 集合中元素的个数
books.has('js'); // 判断是否有此元素
books.delete('js'); // 从集合中删除此元素
books.clear(); // 清空 set

Map

可以使用 Map 来组织键值对的数据

1
2
3
4
5
6
7
8
9
10
11
let books = new Map();
books.set('js', { name: 'js' }); // 向 map 中添加元素
books.set('html', { name: 'html' });
books.size; // 查看集合中的元素
books.get('js'); // 通过 key 获取元素
books.delete('js'); // 通过 key 删除元素
books.has('js'); // 判断 map 中是否有此元素
books.forEach((value, key) => { // forEach 可以迭代 map
console.log(key + ' = ' + value)
})
books.clear(); // 清空 map