# Class
/*
* @Author: etongfu
* @Date: 2018-10-25 16:18:22
* @Last Modified by: etongfu
* @Last Modified time: 2018-10-25 17:53:25
*/
console.warn(' ------------------------------------捋清楚ES6中的Class begin----------------------------------')
// 关键字定义
class Person {
constructor () {
this.name = '关键字定义'
}
sayName () {
console.log(this.name)
}
}
// 使用extends
class Children extends Person {
constructor (...args) {
super(...args)
// 在子类中使用this 必须要在super之后使用
this.childrenName = '使用extends关键字定义'
}
}
const boy = new Children()
boy.sayName()
// 类表达式定义
const NewPerson = class NamedPerson {
constructor () {
this.name = '表达式定义'
}
showName () {
console.log(this.name)
}
}
// console.log(NamedPerson.name) // NamedPerson is undefined
let instance = new NewPerson();
instance.showName()// "表达式定义"
// 自执行类
let AutoClass = new class Auto {
constructor (name) {
this.name = name
}
sayName () {
console.log(this.name)
}
}('自执行类')
AutoClass.sayName()
// 类其实就是函数
class Point {
}
const ponit = new Point()
console.log(typeof Point)// "function"
Point = Point.prototype.constructor // true
// 修改constructor中的this
class Foo {
constructor () {
return Object.create(null)
}
}
console.log(new Foo() instanceof Foo) //false
// 实例一个对象
class Instance {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
const single = new Instance(1,2)
single.toString()
console.log(single.hasOwnProperty('x')) // true
console.log(single.hasOwnProperty('y'))// true
console.log(single.hasOwnProperty('toString'))// false
console.log(single.__proto__.hasOwnProperty('toString')) // true
// 私有方法和属性
// 1: 简单方式
class Self {
// 公共方法
say (name) {
this._say(name)
}
// 私有方法
_say (name) {
return this.my = name
}
}
// 2: 使用symbol实现
const bar = Symbol('bar')
const name = Symbol('name')
class MyClass {
// 公共方法
foo(bar) {
this[bar](bar)
}
// 私有方法 bar和snaf都是Symbol值,导致第三方无法获取到它们,因此达到了私有方法和私有属性的效果。
[bar](bar) {
return this[name] = bar
}
}
// 私有属性提案
/* class NewSelf {
#name
constructor(){
#name = '私有属性提案'
}
} */
// ES5闭包实现实现私有属性
const SelfEs5Func = function (name) {
let temp = name
this._name = function (){
return temp
}
}
let es5 = new SelfEs5Func('闭包中的私有属性')
console.log(es5._name())
// 使用object来实现
let SelfObject = {}
Object.defineProperty(SelfObject, 'freeze', {
get () {
return '我是个只读的对象'
},
set () {
console.log('this is readonly')
}
})
console.log(SelfObject.freeze);
SelfObject.freeze = '测试'
console.warn(' ------------------------------------捋清楚ES6中的Class end ----------------------------------')
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
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
# 闭包
/*
* @Author: etongfu
* @Date: 2018-10-19 17:33:59
* @Last Modified by: etongfu
* @Last Modified time: 2018-10-23 15:07:00
* closure
*/
console.warn(' ------------------------------------我眼中的闭包begin----------------------------------')
// 执行上下文理解下的闭包
function createCounter () {
let count = 0
const myFunction = function() {
count = count + 1
return count
}
return myFunction
}
const increment = createCounter()
const c1 = increment()
const c2 = increment()
const c3 = increment()
console.log(increment,c1,c2,c3);
// 词法词法作用域下理解的闭包
function initScope () {
let name = 'init variable' // name 是一个被 init 创建的局部变量
function innerFunc () { // innerFunc是内部函数,一个闭包
console.log(name) // 使用父函数中的变量
}
innerFunc()
}
initScope()
// 改造后代码
function outerFunc () {
let name = 'outerfunc scope'
function innerFunc(){
console.log(name)
}
return innerFunc
}
let func = outerFunc()
func()
// 设置字号
function sizeConstructor (size) {
return function () {
document.body.style.fontSize = `${size}px`
}
}
// 字号
const size12 = sizeConstructor(12)
const size14 = sizeConstructor(14)
const size16 = sizeConstructor(16)
// 挂载
document.getElementById('size12').onclick = size12
document.getElementById('size14').onclick = size14
document.getElementById('size16').onclick = size16
// 模拟私有属性
function Person (name) {
let _name = name
// 通过闭包把_name字段变为私有属性,这个_name在构造函数内并不是属性,而是一个变量。在实例中无法修改_name,我们仅仅提供了读取_name的方法
this.getName = function () {
return this._name
}
// 现在外部没有办法来修改这个为构造函数传入的name了
}
// 模拟私有方法/封装命名空间
const PrivateMethods = function(){
let privateValue = 0
// 私有方法
function _private (value) {
privateValue += value
}
// 公共方法
return {
increment: function() {
_private(1)
},
decrement: function() {
_private(-1)
},
value: function() {
return privateValue
}
}
}
const method = new PrivateMethods()
// console.log(PrivateMethods.privateValue) // undefined
console.log(method.value()) // 0
method.decrement()
console.log(method.value()) // -1
method.increment()
method.increment()
console.log(method.value()) // 1
// 闭包中常见的注意点
const num = 10
let arr = []
for (var i = 0; i < num; i++) {
arr[i] = function () {
console.log(i)
}
}
for (let i = 0; i < num; i++) {
arr[i]()
}
// 修改后的代码
let funs = []
for (var j = 0; j < num; j++) {
funs[j] = (function(){
let i = j
return function () {
console.log(i)
}
})()
}
for (let i = 0; i < num; i++) {
funs[i]()
}
console.warn(' ------------------------------------我眼中的闭包end----------------------------------')
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
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
# ES6的编程风格
/**
* 关于ES6的编程风格
*/
// 'use strict'
console.log("ES6代码风格");
//1:块级作用域 (let取代var)
//ES6提出了两个新的声明变量的命令:let和const。其中,let完全可以取代var,因为两者语义相同,而且let没有副作用。
if (true) {
//var命令存在变量提升效用,let命令没有这个问题。
let x = "hello";
}
for (let i = 0; i < 10; i++) {
console.log(i);
}
//上面代码如果用var替代let,实际上就声明了两个全局变量,这显然不是本意。变量应该只在其声明的代码块内有效,var命令做不到这一点。
// 1:块级作用域 (全局变量和线程安全)
//在let和const之间,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量。
//const优于let有几个原因。一个是const可以提醒阅读程序的人,这个变量不应该改变;另一个是const比较符合函数式编程思想,运算不改变值,只是新建值,而且这样也有利于将来的分布式运算;最后一个原因是 JavaScript 编译器会对const进行优化,所以多使用const,有利于提供程序的运行效率,也就是说let和const的本质区别,其实是编译器内部的处理不同。
//bad
var a = 1,
b = 2;
//good
const a1 = 1;
const b1 = 2;
//best
const [a2, b2] = [1, 2];
//const声明常量还有两个好处,一是阅读代码的人立刻会意识到不应该修改这个值,二是防止了无意间修改变量值所导致的错误。
//所有的函数都应该设置为常量。 ???
//2:字符串
//静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号
//bad
const stra = "bad";
const strb = 'this' + stra + 'style';
//acceptable
const strc = `thisbadstyle`;
//good
const stra1 = 'bad';
const strb1 = `this${stra1}style`;
const strc1 = 'thisbadstyle';
//解构赋值
//使用数组成员对变量赋值时,优先使用解构赋值。
const arr = [1, 2, 3, 4];
//bad
const first = arr[0];
const second = arr[1];
//good
const [first1, second1] = arr;
//函数的参数如果是对象的成员,优先使用解构赋值
//bad
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;
}
//good
function getFullName1(obj) {
const { firstName, lastName } = obj;
}
//best
function getFullName2({ firstName, lastName }) {
}
//如果函数返回多个值,优先使用对象的解构赋值,而不是数组的解构赋值。这样便于以后添加返回值,以及更改返回值的顺序。
// bad
function processInput(input) {
return [left, right, top, bottom];
}
//good
function processInput1(input) {
return { left, right, top, bottom };
}
//const {left,right} = processInput1(input);
//4:对象
//单行定义的对象,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾。
//good
const obja = { k1: 'v1', k2: 'v2' };
const obj2 = {
k1: "v1",
k2: "v2",
}
//对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用Object.assign方法。
const obja1 = {};
//bad
obja1.x = 3;
//if reshape unavoidable
Object.assign(obja1, { x: 3 });
//good
const obja2 = { x: null };
obja2.x = 3;
//如果对象的属性名是动态的,可以在创造对象的时候,使用属性表达式定义。
//bad
const obja3 = {
id: 5,
name: "zhangsan",
}
//obja3[getKey('enabled')] = true;
//good
const obj4 = {
id: 5,
name: 'San Francisco',
//[getKey('enabled')]: true,
};
//另外,对象的属性和方法,尽量采用简洁表达法,这样易于描述和书写。
var ref = 'some value';
//bad
const atom = {
ref: ref,
value: 1,
addValue: function(value) {
return atom.value + value;
},
}
//good
const atomGood = {
ref,
value: 1,
addValue(value) {
return atomGood.value + value;
},
}
//数组
//使用扩展运算符(...)拷贝数组。
//bad
const items = [1, 1, 1, 1, 1, 1];
const len = items.length;
const itemCopy = [];
let i;
for (i = 0; i < len; i++) {
itemCopy[i] = items[i];
}
//good
const itemCopy1 = [...items]
//使用Array.from方法,将类似数组对象转为数组
//6:函数
//立即执行的函数可以写成箭头函数的形式
/* (() => {
console.log("自执行的函数可以使用箭头函数");
}); */
//需要使用函数表达式的场合,尽量使用箭头函数代替。因为这样更简洁,而且绑定了this
//bad
/* [1, 2, 3, 4].map(function(x) {
return x * x;
}); */
//good
/* [1, 2, 3].map((x) => {
return x * x;
}) */
//best
//[1, 2, 3].map(x => x * x);
//箭头函数取代Function.prototype.bind,不应再用self/_this/that绑定 this。
//bad
const self = this;
const bindMethod = function(...params) {
return method.apply(self, params);
}
//acceptable
//const bindMethod1 = method.bind(this);
//best
const bindMethod2 = (...params) => method.apply(this, params);
//简单的、单行的、不会复用的函数,建议采用箭头函数。如果函数体较为复杂,行数较多,还是应该采用传统的函数写法。
//所有配置项都应该集中在一个对象,放在最后一个参数,布尔值不可以直接作为参数。
//bad
function divide(a, b, option = false) {
}
//good
function divide1(a, b, { option = false } = {}) {
}
//不要在函数体内使用arguments变量,使用rest运算符(...)代替。因为rest运算符显式表明你想要获取参数,而且arguments是一个类似数组的对象,而rest运算符可以提供一个真正的数组。
//bad
function concatenateAll() {
const args = Array.prototype.slice.call(arguments);
return args.join('');
}
//good
function concatenateAll1(...args) {
return args.join('');
}
//使用默认值语法设置函数参数的默认值。
//bad
function handleThings(opts) {
opts = opts || {};
}
//good
function handleThings2(opts = {}) {
}
//7:Map结构
//注意区分Object和Map,只有模拟现实世界的实体对象时,才使用Object。如果只是需要key: value的数据结构,使用Map结构。因为Map有内建的遍历机制。
let map = new Map(arr);
for (let key of map.keys()) {
console.log(key);
}
for (let value of map.values()) {
console.log(value);
}
for (let item of map.entries()) {
console.log(item);
}
//8:class
//总是用Class,取代需要prototype的操作。因为Class的写法更简洁,更易于理解。
//bad
function Queue(contents = []) {
this._queue = [...contents];
}
Queue.prototype.po = function() {
const value = this._queue[0];
this._queue.splice(0, 1);
return value;
}
//good
class Queue1 {
constructor(contents = []) {
this._queue = [...contents];
}
pop() {
const value = this._queue[0];
this._queue.splice(0, 1);
return value;
}
}
// 使用extends实现继承,因为这样更简单,不会有破坏instanceof运算的危险。
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# 设计模式
/*
* @Author: mark
* @Date: 2017-08-30 18:09:03
* @Last Modified by: etf
* @Last Modified time: 2018-09-06 09:09:34
* @description JavaScript设计模式详解 使用ES5语法
*/
/**
* 让系统代码可重用、可扩展、可解耦、更容易被人理解且保证代码可靠性。设计模式使代码真正工程化。
*
* 设计原则:
开闭原则: 对扩展开放,对修改关闭
里氏转换原则: 子类继承父类,单独完全可以运行
依赖倒转原则: 引用一个对象,如果这个对象有底层类型,直接引用底层类型
接口隔离原则: 每一个接口应该是一种角色
合成/聚合复用原则: 新的对象应使用一些已有的对象,使之成为新对象的一部分
迪米特原则: 一个对象应对其他对象有尽可能少的了解
*/
/**
* 一
* 单例模式 保证一个类只有一个实例,实现方法是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,确保一个类只有一个实例对象。
*
* 在 JavaScript 中,单例作为一个命名空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象。
*/
console.warn("------------------------------------这个是JavaScript设计模式Demo begin------------------------------------");
let Singleton = (function() {
let instaced;
function init() {
console.log('init instance');
//这里定义单例代码
return {
publicMethord: function() {
console.log("welcome to singleton");
},
publicProperty: "test"
};
}
return {
// 为使用者提供类实例
getInstance: function() {
if (!instaced) {
console.log('instance does not exit');
//确保只有一个实例
instaced = init(); //使用init方法,是使publicMethod和publicProperty只在要使用的时候才初始化;
} else {
console.log('instance already created');
}
return instaced;
}
};
})();
/*调用公有的方法来获取实例:*/
// 第一次调用
Singleton.getInstance()
// 第二次调用
Singleton.getInstance().publicMethord();
/**
* 作用和注意事项
* 模式作用:
模块间通信
系统中某个类的对象只能存在一个
保护自己的属性和方法
注意事项:
注意this的使用
闭包容易造成内存泄露,不需要的要赶快清除
注意new的成本。(继承)
下面就是一个单例的实例
在网页上实现一个登陆弹框,无论我们点击多少次登陆按钮,界面上始终只会显示一个登陆弹框,无法再创建第二个。
*/
//(1)获取DOM对象
let $ = function(id) {
return typeof id === "string" ? document.getElementById(id) : id;
}
//(2)弹框构造函数
/**
* 构造器
* @param {*string} id
* @param {*string} html
*/
let Modal = function(id, html) {
this.html = html;
this.id = id;
this.open = false;
};
//这里我们声明了一个 Modal 作为弹框的构造函数,并且再其内部定义了公有属性 html、id 和 open。html 用来定义弹框内部的内容,id 用来给弹框定义 id 名称,open 用于判断弹框是否打开。
//(3)open方法
Modal.prototype.create = function() {
if (!this.open) {
console.log('create modal')
// 构建dom
var modal = document.createElement("div");
modal.innerHTML = this.html;
modal.id = this.id;
document.body.appendChild(modal);
setTimeout(function() {
modal.classList.add("show");
}, 0);
this.open = true;
}
};
//在 Modal 的原型链上定义了 create 方法,方法内部我们创建并向 DOM 中插入弹框,同时给弹框加上一个 class 为 “show” 的动画效果。
//(4)close方法
Modal.prototype.delete = function() {
if (this.open) {
// let modal = $(this.id);
let modal = document.getElementById(this.id)
modal.classList.add("hide");
setTimeout(function() {
document.body.removeChild(modal);
}, 200);
this.open = false;
}
};
//定义了 open 方法后我们这里定义关闭弹框的方法,在其内部给弹框对象添加 hide 类动画效果,最后在页面上移除弹框对象。
//(5)创建实例
let createInstance = (function() {
var instance;
return function() {
return instance || (instance = new Modal("modal", "这是一个单例的模态框"));
};
})();
/**
* 这是实现单例模式的重要部分:
使用闭包封装了 instance 私有变量并返回一个函数
利用 || 语法判断如果 instance 不存在则执行后者的实例化 Modal 方法,存在则直接返回 instance,确保了只存在一个弹框实例
*/
//(6)按钮操作
let operate = {
setModal: null,
open: function() {
this.setModal = createInstance();
this.setModal.create();
},
delete: function() {
this.setModal ? this.setModal.delete() : "";
}
};
//这里我们将按钮操作放在 operate 对象里,使得打开和关闭操作可以通过this获取实例setModal。
//绑定事件
$("open").onclick = function() {
operate.open();
};
$("delete").onclick = function() {
operate.delete();
};
/**
* 二
* 构造函数模式
* 构造函数用于创建特定类型的对象——不仅声明了使用过的对象,构造函数还可以接受参数以便第一次创建对象的时候设置对象的成员值。你可以自定义自己的构造函数,然后在里面声明自定义类型对象的属性或方法。
*/
/**
* 作用和注意事项
模式作用:
用于创建特定类型的对象
第一次声明的时候给对象赋值
自己声明构造函数,赋予属性和方法
注意事项:
声明函数的时候处理业务逻辑
区分和单例的区别,配合单例实现初始化
构造函数大写字母开头
注意 new 的成本 (继承)
*/
//实例 强制使用new
function ConstructionPattern(name, age, id) {
if (!(this instanceof ConstructionPattern)) {
return new ConstructionPattern(name, age, id);
}
this.name = name;
this.age = age;
this.id = id;
this.sayId = function() {
return this.id;
};
}
var constructionPattern1 = new ConstructionPattern("zhangsan", 55, "001");
var constructionPattern2 = new ConstructionPattern("zhangsan", 55, "002");
console.log(constructionPattern1.sayId());
console.log(constructionPattern2.sayId());
/**
* 三
* 建造者模式
* 建造者模式可以将一个复杂的对象的构建与其表示相分离,使同样的构建过程可以创建不同的表示。如果我们用了建造者模式,那么用户就需要指定需要建造的类型就可以得到它们,而具体建造的过程和细节就不需要知道了。建造者模式实际就是一个指挥者,一个建造者,一个使用指挥者调用具体建造者工作得出结果的客户。主要用于“分步骤构建一个复杂的对象”。
作用和注意事项
模式作用:
分步创建一个复杂的对象
解耦封装过程和具体创建组件
无需关心组件如何组装
注意事项:
一定要一个稳定的算法进行支持(“分步骤”是一个稳定的算法)
加工工艺是暴露的
实例
一个土豪需要建一个别墅,然后直接找包工头,包工头再找工人把别墅建好。这里土豪不用直接一个一个工人的去找。只需包工头知道土豪需求,然后去找工人,工人干活,土豪也不需要知道房子具体怎么建,最后能拿到房就可以了。
*/
//1.产出东西是房子
//2.包工头调用工人进行开工而且他要很清楚工人们具体的某一个大项
//3.工人是盖房子的 工人可以建厨房、卧室、建客厅
//4.包工头只是一个接口而已 他不干活 他只对外说我能建房子
function House() {
this.kitchen = "";
this.bedromm = "";
this.livingroom = "";
}
function Contractor() {
this.construct = function(worker) {
worker.construct_kitchen();
worker.construct_bedroom();
worker.construct_livingroom();
};
}
function worker() {
this.construct_kitchen = function() {
console.log("厨房建造好了");
};
this.construct_bedroom = function() {
console.log("卧室建造好了");
};
this.construct_livingroom = function() {
console.log("客厅建好了");
};
this.submit = function() {
let _house = new House();
//房子已经建好 提交时修改房屋的属性
_house.kitchen = "finished";
_house.bedromm = "finished";
_house.livingroom = "finished";
return _house;
};
}
let myworker = new worker();
var contractor = new Contractor();
contractor.construct(myworker);
// 主人要房子
var myHouse = myworker.submit();
console.log(myHouse);
/**
* 四工厂模式
工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使一个类的实例化延迟到了子类。
而子类可以重写接口方法以便创建的时候指定自己的对象类型(抽象工厂)。
* 模式作用:
对象构建十分复杂
需要依赖具体的环境创建不同的实例
处理大量具有相同属性的小对象
注意事项:
1、不能滥用工厂,有时候仅仅是给代码增加复杂度
*
*/
//简单工厂模式
var XMLHttpFactory = function() {};
XMLHttpFactory.createXMLHttp = function() {
let XMLHttp = null;
// XMLHttpFactory.createXMLHttp()这个方法根据当前环境的具体情况返回一个XHR对象
if (window.XMLHttpRequest) {
XMLHttp = new XMLHttpRequest();
} else if (window.ActiveXObject) {
XMLHttp = new ActiveXObject("Microsoft.XMLHTTP");
}
return XMLHttp;
};
var AjaxHandle = function() {
let XMLHttp = XMLHttpFactory.createXMLHttp();
/*...具体操作... */
};
//抽象工厂
var abstractXMLHttpFactory = function() {};
abstractXMLHttpFactory.prototype = {
// 如果真的要调用这个方法会抛出一个错误,它不能被实例化,只能用来派生子类
createXMLHttp: function() {
throw new error("This is an abstract class");
}
};
var XHRHandler = function() {
abstractXMLHttpFactory.call(this);
};
XHRHandler.prototype = new abstractXMLHttpFactory();
XHRHandler.prototype.constructor = XHRHandler; // 重新定义 createFactory 方法
XHRHandler.prototype.createXMLHttp = function() {
let XMLHttp = null;
// abstractXMLHttpFactory.createXMLHttp()这个方法根据当前环境的具体情况返回一个XHR对象
if (window.XMLHttpRequest) {
XMLHttp = new XMLHttpRequest();
} else if (window.ActiveXObject) {
XMLHttp = new ActiveXObject("Microsoft.XMLHTTP");
}
return XMLHttp;
};
var abstractAjaxHander = function() {
var XMLHttp = abstractXMLHttpFactory.createXMLHttp()
/*...具体操作... */
}
console.warn("------------------------------------这个是JavaScript设计模式Demo end------------------------------------")
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
# Promise
/*
* @Author: etongfu
* @Email: 13583254085@163.com
* @Date: 2019-01-25 14:53:35
* @LastEditors: etongfu
* @LastEditTime: 2019-02-25 17:37:45
* @Description: 简易版Promise实现
* @youWant: add you want info here
*/
console.warn(' ------------------------------------简单Promise begin----------------------------------')
class EasePromise {
// 调用相应方法
constructor (fn) {
this.status = 'Pending'
setTimeout (() => {
fn((data) => {
this.resolve(data)
}, (error) => {
this.reject(error)
})
})
}
/**
* reslove 处理函数
* @param {*} data
*/
resolve(data) {
if (this.status = 'Pending') {
this.success (data)
this.status = 'Fulfilled'
}
}
/**
* 错误处理页面
* @param {*} data
*/
reject(error) {
if (this.status = 'Pending') {
this.error(error)
this.status = 'Rejected'
}
}
/**
* 传递处理函数
* @param {*function} success
* @param {*function} reject
*/
then(success, reject) {
this.success = success
this.reject = reject
}
/**
* 错误捕捉
* @param {*} err
*/
catch(reject) {
return this.then(null, reject);
}
}
// 测试
let easePromoise = new EasePromise((reslove, reject) => {
setTimeout(() => {
reslove('hello ease promise')
}, 1000)
})
easePromoise.then((result) => {
console.log(result)
}, (err) => {
console.error(error)
})
console.warn(' ------------------------------------简单Promise end----------------------------------')
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
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
# 函数和对象配合的魅力
/*
* @Author: etongfu
* @Email: 13583254085@163.com
* @Description: Javascript中函数和对象配合的魅力 示例代码
* @youWant: add you want info here
* @Date: 2019-04-09 13:59:48
* @LastEditTime: 2019-04-09 16:28:46
*/
console.warn(' ------------------------------------javascript中函数和对象配合的魅力begin----------------------------------')
// 简单实验 函数作为对象的存在
let fn = function () {}
fn.prop = 'fnProp'
console.log(fn.prop) // fnProp
// 1:函数缓存示例
let store = {
nextId: 1, // id
cache: {}, // 缓存
add (fn) {
// 如果函数中没有id属性那么就缓存
if (!fn.id) {
console.log(`begin add func ${fn.name}`)
fn.id = store.nextId ++
// 设置完缓存之后返回true
return !!(store.cache[fn.id] = fn)
} else {
console.log(`${fn.name} is already in cache`)
}
}
}
function storeCache() {}
store.add(storeCache) // begin add func storeCache
store.add(storeCache) // storeCache is already in cache
// 2: 缓存记忆函数
function isPrime (value) {
if (!isPrime.anwers) isPrime.anwers = {}
// 先从缓存里面取
if (isPrime.anwers[value] != null ) {
return isPrime.anwers[value]
}
// 开始进行判断和计算
let prime = value != 1
for (let index = 2; index < value; index++) {
if (value % index == 0) {
prime = false
break;
}
}
// 保存计算出来的值
return isPrime.anwers[value] = prime
}
console.log(isPrime(5))
console.log(`从函数记忆中直接读取${isPrime.anwers[5]}`)
// 3:缓存记忆DOM元素
function getElements (name) {
if (!getElements.cache) getElements.cache = {}
return getElements.cache[name] = getElements.cache[name] || document.getElementsByTagName(name);
}
console.log(getElements('div')) // HTMLCollection
console.log(getElements.cache['div']) // HTMLCollection
// 4:伪造数组方法
// <input type="button" id="add" >
// <input type="button" id="remove" >
let elems = {
length: 0,
add (elem) {
Array.prototype.push.call(this, elem)
},
gather (id) {
this.add(document.getElementById(id))
}
}
elems.gather('add')
elems.gather('remove')
console.log(elems[0]); // <input type="button" id="add" >
console.log(elems[1]); // <input type="button" id="remove" >
console.log(elems.length); // 2
console.log(elems);
/**
0: input#add
1: input#remove
add: ƒ add(elem)
gather: ƒ gather(id)
length: 2
*/
console.warn(' ------------------------------------javascript中函数和对象配合的魅力end----------------------------------')
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
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
# 高阶函数的实现
/*
* @Author: mark
* @Date: 2018-09-01 23:02:11
* @Last Modified by: etongfu
* @Last Modified time: 2018-10-19 14:28:18
* JavaScript高阶函数的实现
*/
/**
* 织入执行前函数
* @param {*} fn
*/
Function.prototype.aopBefore = function(fn){
console.log(this)
// 第一步:保存原函数的引用
const _this = this
// 第四步:返回包括原函数和新函数的“代理”函数
return function() {
// 第二步:执行新函数,修正this
fn.apply(this, arguments)
// 第三步 执行原函数
return _this.apply(this, arguments)
}
}
/**
* 织入执行后函数
* @param {*} fn
*/
Function.prototype.aopAfter = function (fn) {
const _this = this
return function () {
let current = _this.apply(this,arguments)// 先保存原函数
fn.apply(this, arguments) // 先执行新函数
return current
}
}
/**
* 使用函数
*/
let aopFunc = function() {
console.log('aop')
}
// 注册切面
aopFunc = aopFunc.aopBefore(() => {
console.log('aop before')
}).aopAfter(() => {
console.log('aop after')
})
// 真正调用
aopFunc()
/**
* 函数柯里化(curring)
*/
// 未柯里化的函数计算开销
let totalCost = 0
const cost = function(amount, mounth = '') {
console.log(`第${mounth}月的花销是${amount}`)
totalCost += amount
console.log(`当前总共消费:${totalCost}`)
}
cost(1000, 1) // 第1个月的花销
cost(2000, 2) // 第2个月的花销
// ...
cost(3000, 12) // 第12个月的花销
// 部分柯里化完的函数
const curringPartCost = (function() {
// 参数列表
let args = []
return function (){
/**
* 区分计算求值的情况
* 有参数的情况下进行暂存
* 无参数的情况下进行计算
*/
if (arguments.length === 0) {
let totalCost = 0
args.forEach(item => {
totalCost += item[0]
})
console.log(`共消费:${totalCost}`)
return totalCost
} else {
// argumens并不是数组,是一个类数组对象
let currentArgs = Array.from(arguments)
args.push(currentArgs)
console.log(`暂存${arguments[1] ? arguments[1] : '' }月,金额${arguments[0]}`)
}
}
})()
curringPartCost(1000,1)
curringPartCost(100,2)
curringPartCost()
// 通用curring函数
const curring = function(fn) {
let args = []
return function () {
if (arguments.length === 0) {
console.log('curring完毕进行计算总值')
return fn.apply(this, args)
} else {
let currentArgs = Array.from(arguments)[0]
console.log(`暂存${arguments[1] ? arguments[1] : '' }月,金额${arguments[0]}`)
args.push(currentArgs)
// 返回正被执行的 Function 对象,也就是所指定的 Function 对象的正文,这有利于匿名函数的递归或者保证函数的封装性
// 这个很重要
// return arguments.callee
}
}
}
// 求值函数
let costCurring = (function() {
let totalCost = 0
return function () {
for (let i = 0; i < arguments.length; i++) {
totalCost += arguments[i]
}
console.log(`共消费:${totalCost}`)
return totalCost
}
})()
// 执行curring化
costCurring = curring(costCurring)
costCurring(2000, 1)
costCurring(2000, 2)
costCurring(9000, 12)
costCurring()
/**
* 节流函数
* @param {*} fn
* @param {*} interval
*/
const throttle = function (fn, interval = 500) {
let timer = null, // 计时器
isFirst = true // 是否是第一次调用
return function () {
let args = arguments, _me = this
// 首次调用直接放行
if (isFirst) {
fn.apply(_me, args)
return isFirst = false
}
// 存在计时器就拦截
if (timer) {
return false
}
// 设置timer
timer = setTimeout(function (){
console.log(timer)
window.clearTimeout(timer)
timer = null
fn.apply(_me, args)
}, interval)
}
}
// 使用节流
window.onresize = throttle(function() {
console.log('throttle')
},600)
/**
* 分时函数
* @param {*创建节点需要的数据} list
* @param {*创建节点逻辑函数} fn
* @param {*每一批节点的数量} count
*/
const timeChunk = function(list, fn, count = 1){
let insertList = [], // 需要临时插入的数据
timer = null // 计时器
const start = function(){
// 对执行函数逐个进行调用
for (let i = 0; i < Math.min(count, list.length); i++) {
insertList = list.shift()
fn(insertList)
}
}
return function(){
timer = setInterval(() => {
if (list.length === 0) {
return window.clearInterval(timer)
}
start()
},200)
}
}
// 分时函数测试
const arr = []
for (let i = 0; i < 94; i++) {
arr.push(i)
}
const renderList = timeChunk(arr, function(data){
let div =document.createElement('div')
div.innerHTML = data + 1
document.body.appendChild(div)
}, 20)
// 调用render函数
// renderList()
/**
* 惰性函数
*/
// 常用的事件兼容
const addEvent = function(el, type, handler) {
if (window.addEventListener) {
return el.addEventListener(type, handler, false)
}
// for IE
if (window.attachEvent) {
return el.attachEvent(`on${type}`, handler)
}
}
// 优化之后的事件兼容
const addEventOptimization = (function() {
if (window.addEventListener) {
return (el, type, handler) => {
el.addEventListener(type, handler, false)
}
}
// for IE
if (window.attachEvent) {
return (el, type, handler) => {
el.attachEvent(`on${type}`, handler)
}
}
})()
// 惰性加载函数
let addEventLazy = function(el, type, handler) {
if (window.addEventListener) {
// 一旦进入分支,便在函数内部修改函数的实现
addEventLazy = function(el, type, handler) {
el.addEventListener(type, handler, false)
}
} else if (window.attachEvent) {
addEventLazy = function(el, type, handler) {
el.attachEvent(`on${type}`, handler)
}
}
addEventLazy(el, type, handler)
}
addEventLazy(document.getElementById('eventLazy'), 'click', function() {
console.log('lazy ')
})
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# 闯关记DEMO
/**
* JavaScript闯关记Demo
*/
console.warn("------------------------------------这个是JavaScript闯关记Demo Begin----------------------------------");
//《JavaScript 闯关记》之 BOM
//window对象
// var age = 26;
// //var sayAge = () => console.log(this.age);
// function sayAge(){
// console.log(this.age);
// }
// console.log(window.age);
// window.sayAge();
//setTimeout 和 setInterval都是window的方法
//对于这两个方法 都会有一个超时ID标识
//设置超时id
let setTimeoutId = setTimeout(function() {
console.log("set timeout调用");
}, 100);
//清除定时执行
clearTimeout(setTimeoutId);
//
let num = 0;
let max = 10;
let setIntervalId = null;
let creasenumber = function(){
num++;
console.log(num);
if(num == max){
clearInterval(setIntervalId);
console.log("interval done");
}
};
setIntervalId = setInterval(creasenumber,500);
//《JavaScript 闯关记》之 DOM上
/**
* Node 属性概述
* Node 常用属性主要有以下10个,接下来我们会着重讲解部分属性。 折都是JS原声的属性
*
nodeType:显示节点的类型
nodeName:显示节点的名称
nodeValue:显示节点的值
attributes:获取一个属性节点
firstChild:表示某一节点的第一个节点
lastChild:表示某一节点的最后一个子节点
childNodes:表示所在节点的所有子节点
parentNode:表示所在节点的父节点
nextSibling:紧挨着当前节点的下一个节点
previousSibling:紧挨着当前节点的上一个节点
*/
//下面的例子展示了如何访问保存在 NodeList 中的节点——可以通过方括号,也可以使用 item() 方法。
let node = document.getElementById('demo');
//这种node必须是唯一的node才能使用以下的方法
let firstChild = node.childNodes[0];
let secChild = node.childNodes.item(1);
let count = node.childNodes.length;
console.log(firstChild);
console.log(secChild);
console.log(count);
//操作节点
/**
* 因为关系指针都是只读的,所以 DOM 提供了一些操作节点的方法。其中,最常用的方法是 appendChild(),用于向 childNodes 列表的末尾添加一个节点。
* 添加节点后,childNodes 的新增节点、父节点及以前的最后一个子节点的关系指针都会相应地得到更新。
* 更新完成后,appendChild() 返回新增的节点。来看下面的例子
*/
var returnNode = node.appendChild(secChild)
console.log(returnNode == secChild);
console.warn("------------------------------------这个是JavaScript闯关记Demo end------------------------------------");
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
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
# JavaScript对象
/**
* 关于JavaScript中的对象方面简单复习总结
* ⭐⭐⭐⭐⭐
*/
//对象字面量
var obj_mode = {
name: "zhangsan",
sayName: function() {
console.log(this.name);
}
}
//弊端★ 这种方法在创建多个对象时会产生大量重复的代码,在创建单个对象时的首选模式。
//工厂模式
function factory_mode(name) {
var o = new Object(); //创建新对象
o.name = "mimi";
return o;
}
//★ 这种方法没有解决对象识别的方法,一定程度上解决创建多个相似对象的问题吧,不常使用。
//构造函数模式
function constructor_mode(name) {
this.name = name;
this.sayName = function() {
console.log(this.name);
}
}
/**
* 它没有显式的创建对象也没有返回语句,但在使用new操作符调用后经历了四个步骤:
(1)创建一个对象
(2)将函数的作用域赋给新对象(this指向了新对象)
(3)执行函数中代码
(4)返回新对象
所有对象都有一个constructor属性,指向其构造函数。constructor可以用来标识对象类型,但是,要检测对象类型,instanceof操作符更可靠。
*/
//原型模式
function prototype_mode() {
}
prototype_mode.prototype.name = "mini";
prototype_mode.prototype.sayName = function() {
console.log(this.name);
}
/**
* 无论何时,创建一个函数,都会自动创建一个prototype属性,指向其原型对象,正如前面所说,每个对象都有一个constructor属性,指向其构造函数。所以Animal.prototype.constructor指向Animal。判断原型对象与实例间关系可用isPrototypeOf()方法:Animal.prototype.isPrototypeOf(animal1);
* 判断属性存在于实例中,还是存在与原型中:
属性存在于实例中时:
animal1.hasOwnProperty(name);//true
* 属性能通过对象访问:
* name in animal1;//true
*/
//用字面量来实现更简单的原型语法
function easy_prototype_mode() {
}
easy_prototype_mode.prototype = {
constructor: easy_prototype_mode, ////必须必须!因为这样相当于创建了一个新对象并赋值给Animal.prototype,
//此时这个新对象的constructor为默认的构造函数Object啊盆友们( ゚Д゚)ノ
name: 'mini',
sayName: function() {
console.log(this.name);
}
}
/**
* 另外,很重要的一点:调用构造函数是会为实例添加一个指向最初原型的[[prototype]]指针,这个连接存在与实例和构造函数的原型对象之间,而不是实例与构造函数间。
*/
//★ 这个方法的问题在于:1.它没办法传递初始化参数 2.对于引用类型值的属性(A)来说,改变一个实例的A属性会直接引起所有实例中的A属性的变化,因为实例中的A属性只是其原型中A属性的一个引用。
//组合使用构造函数模式和原型模式
//构造函数模式用于定义实例属性,原型模式用于定义方法和共享属性。
function combination_mode(name) { //添加实例属性
this.name = name;
this.color = ['red', 'blue'];
}
combination_mode.prototype = { //添加方法和共享属性
constructor: combination_mode,
sayName: function() {
console.log(this.name);
}
}
//★ 这个方法就是使用最广泛、认同度最高的创建自定义类型的方法啦。
/**
* JavaScript中的继承 以组合模式生成的对象为父类
*/
//原型链继承
function combination_mode_child() {
}
combination_mode_child.prototype = new combination_mode(); ////将父类实例赋值给子类原型。
combination_mode_child.prototype.constructor = combination_mode_child;
var combination_mode_child1 = new combination_mode_child();
//★ 此方法存在问题前面已有提及,就是包含引用类型值的原型属性会被所有实例共享,且不能传参。
//借用构造函数继承
function father(name) { //父类
this.name = name;
}
function child(name) { //子类
father.call(this, name) ///执行了一遍父类型构造函数代码
}
//★ 此方法问题在于:1.方法都在构造函数中定义,函数复用无从谈起。2.在超类的原型中定义的方法对子类型也不可见。而且用instanceof也无法判定cat1与Animal的联系。
//组合继承
// /使用原型链实现对原型属性和方法的继承,使用借用构造函数实现对实例属性的继承。
function combination_father(name) { //父类添加实例属性
this.name = name;
}
combination_father.prototype = {
constructor: combination_father,
sayName: function() {
console.log(this.name);
}
}
function combination_child(name) {
combination_father.call(this, name); //继承属性
}
combination_child.prototype = new combination_father(); //继承属性
combination_child.prototype.constructor = combination_child; //将constructor指回子类
//★ 此方法是JS中最常用的继承模式。而且用instanceof 和isPrototypeOf() 也能识别。
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
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
# ES实例
/**
* 部分demo
*/
console.warn("------------------------------------这个是demo输出的begin----------------------------------");
/**
* 交换值
*/
let a = 100;
let b = 200;
[a,b] = [b,a];
console.log(a + " + " + b);
/**
* 返回多个值的函数_返回一个数组或者对象
*/
let getArr = function () {
return [1,2,3,4];
}
let [x,y,z,q] = getArr();
console.log(x + " + " + y + " + " + z + " + " + q );
let getObj = function(){
return {
id: '007',
name:'etf',
age:21
}
}
let {id,name,age} = getObj();
console.log(id + " + " + name + " + " + age);
/**
* 解析json
*/
let jsonData = {
cid:1,
cname:"mm",
cage:21,
score :{
Chinese:98,
English:98,
Math:20
}
}
//ES6解析开始
let {cid:number,cage,cname,score} = jsonData;
console.log(number);
console.log(cage);
console.log(cname);
console.log(score);
/**
* 解析map结构
*/
let map = new Map();
map.set("user","007");
map.set("pass",'123');
for(let [key,value] of map){
console.log(key +"is" + value);
}
//获取键名
for(let [key] of map){
console.log(key);
}
//获取value
for(let [,item] of map){
console.log(item);
}
/**
* class专题
*/
class Point{
constructor(x,y){
this.x = x;
this.y = y;
}
newToString(){
return x + "," + y;
}
}
//class表达式
const MyClass = class Me{
constructor(name){
this.name = name;
};
getClassName(){
//Me只能在内部使用
return console.log(Me.name);
}
}
//如果内部没用到类的话可以省略Me
const NewClass = class {
}
//还可以写出自执行的Class 自执行的class本质上是自动创建一个object
let Person = new class{
constructor(name){
this.name = name;
}
sayName(){
console.log(this.name);
}
}('张三');
Person.sayName();
let newPoint = new Point();
//私有方法
//私有方法是常见需求,但 ES6 不提供,只能通过变通方法模拟实现。
//一种做法是在命名上加以区别。
//_bar方法前面的下划线,表示这是一个只限于内部使用的私有方法。但是,这种命名是不保险的,在类的外部,还是可以调用到这个方法。
class Widget {
//公用方法
foo(baz){
this._baz(baz);
}
//私有方法
_baz(baz){
return this.snaf = baz;
}
}
//另一种方法就是索性将私有方法移出模块,因为模块内部的所有方法都是对外可见的。
class twoWidget {
foo(baz){
bar.call(this,baz);
}
}
function bar(baz){
return this.snaf = baz;
}
//还有一种方法是利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值。
const tBar = Symbol('tBar');
const tSnaf = Symbol('tSnaf');
export default class threeClass{
// 公有方法
foo(baz) {
this[bar](baz);
}
//私有方法
[bar](baz){
return this[snaf] = baz;
}
}
//this的指向
class Logger{
printName(name = 'there'){
this.print(`hello ${name}`);
}
print(text){
console.log(text);
}
}
const logger = new Logger();
const { printName } = logger;
//printName(); // TypeError: Cannot read property 'print' of undefined this指向改变
/**
* 上面代码中,printName方法中的this,默认指向Logger类的实例。但是,如果将这个方法提取出来单独使用,this会指向该方法运行时所在的环境,因为找不到print方法而导致报错。
*/
//一个比较简单的解决方法是,在构造方法中绑定this,这样就不会找不到print方法了。
class loggerTwo {
constructor(){
//bind绑定this指向
this.printName = this.printName.bind(this);
}
}
/**
* class继承
*
*/
//Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
class EPoint {
}
class ColorPoint extends EPoint{
constructor(x,y,color){
//this.color = color; ReferenceError
//在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。
super(x,y)//调用父类 的constructor(x,y) super用来新建弗雷的this对象
this.color = color;
}
toString(){
return this.color + "" + super.toString();//调用父类方法
}
}
/**
* super关键字
* super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。
*第一种情况,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。像上面的继承那种用法
*/
/**
* 第二种情况,super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
*/
class A {
m(){
return 2;
}
}
class B extends A{
constructor(){
super();
console.log(super.m());
}
}
let newb = new B();
//ES6规定,通过super调用父类的方法时,super会绑定子类的this。
class A1 {
constructor(x){
this.x = 1;
}
print(){
console.log(this.x);
}
}
class B1 extends A1{
constructor() {
super();
this.x = 2;
}
m() {
super.print();
}
}
let b1 = new B1();
b1.m() // 2
//由于绑定子类的this,所以如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性。
class B2 extends A1 {
constructor() {
super();
this.x = 2;
super.x = 3;
console.log(super.x); // undefined
console.log(this.x); // 3
}
}
//上面代码中,super.x赋值为3,这时等同于对this.x赋值为3。而当读取super.x的时候,读的是A.prototype.x,所以返回undefined。
/**
* 修饰器 ES2017的内容
*/
console.log("{}类的修饰"); // 3
//修饰器(Decorator)是一个函数,用来修改类的行为。ES2017 引入了这项功能,目前 Babel 转码器已经支持。
//@testtable
class MyTesttableClass {}
function testtable(target){
target.isTesttable = true;
}
/**
* Module语法
*/
//除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面。
import * as cricle from './cricle.js';
//所以的模块对象的输出值都挂在cricle对象下
console.log('圆面积:' + cricle.area(4));
console.log('圆周长:' + cricle.circumference(14));
//export 与 import 的复合写法 § ⇧
//如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起。
// export { area, circumference } from './cricle.js';
// // 等同于
// import { area, circumference } from './cricle.js';
// export { area, circumference };
/**
* import()
*
* const path = './' + fileName;
* const myModual = require(path);
*
* 上面的语句就是动态加载,require到底加载哪一个模块,只有运行时才知道。import语句做不到这一点。
引入import()函数,完成动态加载。
*/
//import(specifier);
//上面代码中,import函数的参数specifier,指定所要加载的模块的位置。import命令能够接受什么参数,import()函数就能接受什么参数,两者区别主要是后者为动态加载。
//importd的使用场合
//(1)按需加载 import()可以在需要的时候,再加载某个模块。
//import()加载模块成功以后,这个模块会作为一个对象,当作then方法的参数。因此,可以使用对象解构赋值的语法,获取输出接口。
// button.addEventListener('click', event => {
// import('./cricle.js')
// .then(({export1, export2}) => {
// export1.open();
// })
// .catch(error => {
// })
// });
//(2)条件加载 import()可以放在if代码块,根据不同的情况,加载不同的模块。
const condition = false;
if(condition){
// import('./cricle.js').then().catch(error =>{});
}else{
console.log('条件不达到不加载');
}
//(3)动态的模块路径 import()允许模块路径动态生成。
//如果想同时加载多个模块,可以采用下面的写法。
Promise.all([
/* import('./cricle.js'),
import('./person.js'), */
])
.then(([cricle, demo, person]) => {
});
/**
* 箭头函数专题
*/
console.info("=>箭头函数专题");
//有参数
console.info("=> ES5 写法");
let curf1 = function(v) {
return v;
};
console.info("=> ES6 写法");
let curf2 = v => v;
//无参数
//es5
let f1 = function() {
return "etf";
};
//es6
let f2 = () => "etf";
//如果箭头函数直接返回一个对象,必须在对象外面加上括号
//es5
let objf1 = function(){
return{
real_name: "etf",
nick_name: "handsome"
}
}
//es6
let objf2 = () => ({real_name: "etf",nick_name: "handsome"});
/**
* 关于解构
* 我们还可以使用到 ES6 解构赋值特性
*/
//es5
let conf1 = function(person){
return person.first + ' ' + person.last;
}
//es6
const conf2 = ({first,last}) =>first + last;
/**
* 回调函数
*/
//es5
let callF1 = [1,2,3].map(function(x){
return x * x;
});
//es6
let callF2 = [1,2,3].map(x => x * x);
/**
* 使用length相同的数组拼接成json
*/
let amountList = [3,3,3,3,3];//金额数组
let scoreList = [1,1,1,1,1];//积分数组
let idList = [2,2,2,2,2];//id数组
let jsonList = [];//ajax入参json对象
//拼接生成json
//jq写法
// $.each(amountList,function(i){
// item = {id:idList[i],lowerLimit:amountList[i],score:scoreList[i]};
// jsonList.push(item);
// });
//原生JS写法
for (let i = 0; i < amountList.length; i++) {
var item = {id:idList[i],lowerLimit:amountList[i],score:scoreList[i]};
jsonList.push(item);
}
JSON.stringify(jsonList);
/*
* 这个函数用来解析来自URL的查询串中的name=value参数对
* 它将name=value对存储在一个对象的属性中,并返回该对象
* 这样来使用它
*
* var args = urlArgs(); // 从URL中解析参数
* var q = args.q || ""; // 如果参数定义了的话就使用参数;否则使用一个默认值
* var n = args.n ? parseInt(args.n) : 10;
*/
function urlArgs() {
var args = {}; // 定义一个空对象
var query = location.search.substring(1); // 查找到查询串,并去掉'? '
var pairs = query.split("&"); // 根据"&"符号将查询字符串分隔开
for (var i = 0; i < pairs.length; i++) { // 对于每个片段
var pos = pairs[i].indexOf('='); // 查找"name=value"
if (pos == -1) continue; // 如果没有找到的话,就跳过
var name = pairs[i].substring(0, pos); // 提取name
var value = pairs[i].substring(pos + 1); // 提取value
value = decodeURIComponent(value); // 对value进行解码
args[name] = value; // 存储为属性
}
return args; // 返回解析后的参数
}
console.warn("------------------------------------这个是demo输出的end------------------------------------");
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
# Promise 实现
/*
* @Author: etongfu
* @Email: 13583254085@163.com
* @Date: 2019-01-25 11:00:44
* @LastEditors: etongfu
* @LastEditTime: 2019-01-25 14:40:25
* @Description: Promise 实现 遵循promise/A+规范
* @youWant: add you want info here
*/
// promise 三个状态
let pendingSym = Symbol('pending') , fulfilledSym = Symbol('fulfilled') , rejectedSym = Symbol('rejected')
const STATUS = {
[pendingSym]: "pending",
[fulfilledSym]: "fulfilled",
[rejectedSym]: "rejected"
}
/**
* 构造函数
* @param {*} excutor
*/
function Promise(excutor) {
let _this = this // 缓存当前promise实例对象
_this.status = STATUS.pendingSym //初始状态
_this.value = undefined // fulfilled状态时 返回的信息
_this.reason = undefined // rejected状态时 拒绝的原因
_this.onFulfilledCallbacks = [] // fulfilled状态时的执行函数
_this.onRejectedCallbacks = [] // rejected状态时的执行函数
/**
* reslove 函数
* @param {*成功时接受的值} value
*/
function reslove(value) {
// 先判断value是不是个value 是个value的话需要接着then出去
if (value instanceof Promise) {
return value.then(reslove, reject)
}
/**
// 为什么resolve 加setTimeout?
// 2.2.4规范 onFulfilled 和 onRejected 只允许在 execution context 栈仅包含平台代码时运行.
// 注1 这里的平台代码指的是引擎、环境以及 promise 的实施代码。实践中要确保 onFulfilled 和 onRejected 方法异步执行,
且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。
*/
setTimeout(() => {
if (_this.status === STATUS.pendingSym) {
_this.status = STATUS.fulfilledSym //切换状态 避免多次调用
_this.value =value
// 执行then
_this.onFulfilledCallbacks.forEach(cb => cb(_this.value))
}
})
}
/**
* reject函数
* @param {*} reason
*/
function reject(reason) {
setTimeout(() => {
if (_this.status = STATUS.pendingSym) {
_this.status = STATUS.rejectedSym
_this.reason = reason
// 执行catch
_this.onRejectedCallbacks.forEach(cb => cb(_this.reason))
}
})
}
// 捕获生成实例过程中的异常
try {
excutor(resolve, reject);
} catch (error) {
reject(e)
}
}
/**
* 对resolve 进行改造增强 针对resolve中不同值情况 进行处理
* 针对不同的值的promise处理
* @param {*promise} promise2 promise2 promise1.then方法返回的新的promise对象
* @param {*} x promise1中onFulfilled的返回值
* @param {*} resolve promise2的resolve方法
* @param {*} reject promise2的reject方法
*/
function reslovePromise (promise2, x, resolve, reject) {
if(promise2 === x) {
return reject(new TypeError('循环引用'));
}
let called = false
}
/**
* then 方法
* @param {*function} onFulfilledCb 成功回掉
* @param {*function} onRejectedCb 异常回掉
* @return {function} newPromsie 返回一个新的promise对象
*/
Promise.prototype.then = function (onFulfilledCb, onRejectedCb) {
const _this = this
let newPromise
// 默认值处理
// 注意这个value和reason 都是当前这个Promise实例中的对象
onFulfilledCb = typeof onFulfilledCb === 'function' ? onFulfilledCb : value => value
onRejectedCb = typeof onRejectedCb === 'function' ? onRejectedCb : reason => {
throw reason
}
}
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
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
# Vue响应式原理
/*
* @Author: etongfu
* @Email: 13583254085@163.com
* @LastEditors: etongfu
* @Description: Vue响应式原理
* @youWant: add you want info here
* @Date: 2019-02-25 17:29:13
* @LastEditTime: 2019-02-26 16:32:12
*/
console.warn(`------------Vue响应式原理部分Start------------`)
// 第一步实现一个简单的对象拦截
const data = {
a: 1,
b:2,
name: 'Vue',
age: 2
}
let target = null
for (const key in data) {
// 拦截列表
const dep = []
// 缓存val
let val = data[key]
Object.defineProperty(data, key, {
// set赋值
set (newVal) {
if (newVal == val) return;
val = newVal
dep.forEach(fn => fn())
},
get () {
dep.push(target)
return val // 返回值
}
})
}
/**
* 简易版watch
* @param {*} exp
* @param {*} fn
*/
function easyWatch (exp, fn) {
target = fn
data[exp]
}
// 第二部完善watch
// 在上一步我们的watch只能监听没有嵌套的对象, 下面我们呢实现嵌套对象的监听 嵌套监听也很简单,递归一下就行了
/**
* 递归监听
* @param {*} data
*/
function walk (data) {
for (const key in object) {
// 拦截列表
const dep = []
// 缓存val
let val = data[key]
// 如果当前的val是个对象那么开始递归调用
const nativeStr = Object.prototype.toString.call(val)
if (nativeStr === '[object object]') {
walk(val)
}
Object.defineProperty(data, key, {
// set赋值
set (newVal) {
if (newVal == val) return;
val = newVal
dep.forEach(fn => fn())
},
get () {
dep.push(target)
return val // 返回值
}
})
}
}
walk(data)
// 改造了之后之前的watch也肯定是不好用的
//读取字段值的时候我们直接使用 data[exp],如果按照 $watch('a.b', fn) 这样调用 $watch 函数,
// 那么 data[exp] 等价于 data['a.b'],这显然是不正确的,正确的读取字段值的方式应该是 data['a']['b']。所以我们需要稍微做一点小小的改造:
function walkWatch(exp, fn){
target = fn
let pathArr, obj = data
// 如果是函数那么直接执行
if (typeof(exp) === 'function') {
exp()
return
}
// 检查exp中是否有.
if (/\./.test(exp)) {
pathArr = exp.split('.')
// 循环读取
pathArr.forEach(o => {
obj = obj[o]
})
return
}
//正常读取
data[exp]
}
function render () {
return document.write(`姓名:${data.name}; 年龄:${data.age}`)
}
walkWatch(render, render)
// 监听之后会自动更新dom
/* setTimeout(() => {
data.name = 'Vue Observe'
}, 2000); */
// 第三步 observe 工厂函数
// 上面的是实现的基本思路 下面我们来讨论具体的实现,首先实现Observer 类
class Observer {
constructor(value) {
// 赋值
this.value = value
this.dep = []
this.vmCount = 0
//
}
walk () {
}
observeArray () {
}
}
console.warn(`------------Vue响应式原理部分End--------------`)
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
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
# Array API模块回顾
/**
* Array API模块回顾 ⭐⭐⭐⭐⭐ array模块相当重要
* MDN文档为主
* Array具有两个属性
* Array.prototype
* Array.length
*/
//************数组中的thisArg是传递进去的执行上下文,如果不穿数组内的callback中的this是undefined 传了就是具体的this指向********************* */
/**
* ⭐⭐⭐
* from(arrayLike,mapFn,thisArg) 方法从一个类似数组或可迭代对象中创建一个新的数组实例。
* 注意:返回一个新的数组实例
* arrayLike:要转化的对象
* mapFn:map方法 可以对数组进行遍历
* thisArg:他执行回掉函数时的this对象
*/
let fromArr = Array.from('fooasd',x => x + 'a')
console.log('from 方法返回的值为一个新的数组实例');
console.log(fromArr);
/**
* ⭐⭐⭐
* isArray()
* 用于确定传递的值是否是一个 Array。
* 注意:返回true或者false
*
* // 鲜为人知的事实:其实 Array.prototype 也是一个数组。
Array.isArray(Array.prototype);
*/
console.log('鲜为人知的事实:其实 Array.prototype isArray:' + Array.isArray(Array.prototype))
/**
* ⭐⭐⭐⭐
* concat()方法用于合并两个或多个数组或数值。
* 注意:不会修改原数组 会返回一个新数组 而且生成的数组还是一个浅拷贝 ,生成的新数组会随着原数组的改变
*/
let num1 = [[2],5]
let num2 = [2,[2]]
let num3 = [[7]]
let nums = num1.concat(num1,num2,9)
console.log('concat 方法返回的是一个原数组浅拷贝生成的新数组:');
console.log(nums);
num1[0].push(7)
console.log('修改原数组之后新数组也会改变:');
console.log(nums);
/**
* ⭐⭐⭐
* copyWithin(target,start,end)方法浅复制数组的一部分到同一数组中的另一个位置,并返回它,而不修改其大小。
* target:目标位置开始
* start:复制的开始位置
* end:复制的结束位置
* 注意:修改原数组 start和end一般都是要头不要尾
*/
console.log([1, 2, 3, 4, 5].copyWithin(-2))// [1, 2, 3, 1, 2]
console.log([1, 2, 3, 4, 5].copyWithin(0,3));// [4, 5, 3, 4, 5]
console.log([1, 2, 3, 4, 5].copyWithin(0,2,3));// [3, 2, 3, 4, 5]
/**
* ⭐⭐⭐
* entries() 方法返回一个新的Array Iterator对象,该对象包含数组中每个索引的键/值对。
* 注意:返回一个新的 Array 迭代器对象。Array Iterator是对象,它的原型(__proto__:Array Iterator)上有一个next方法,可用用于遍历迭代器取得原数组的[key,value]。
* 返回的对象中的value才是真正的遍历的值
*/
let arr = ["a", "b", "c"]
let iteator = arr.entries()
console.log(iteator);
/*{value: Array(2), done: false}
done:false
value:(2) [0, "a"]
__proto__: Object
*/
// iterator.next()返回一个对象,对于有元素的数组,
// 是next{ value: Array(2), done: false };
// next.done 用于指示迭代器是否完成:在每次迭代时进行更新而且都是false,
// 直到迭代器结束done才是true。
// next.value是一个["key":"value"]的数组,是返回的迭代器中的元素值。
// 遍历iteator
for(let item of iteator){
console.log(item);
}
//Iterator的方法运行
let forArr = ["a", "b", "c"]
let forIter = forArr.entries()
var a = [];
for (let index = 0; index < forArr.length + 1; index++) { // 注意,是length+1,比数组的长度大
let element =forIter.next() // 每次迭代时更新next
console.log(element.done); // 这里可以看到更新后的done都是false
if(element.done !== true){
console.log(element.value);
a.push(element.value)
}
}
console.log(a);
// 二维数组中的数组排序可以使用
function sortDubble(arr){
let goNext = true
let entries = arr.entries()
while(goNext){
let result = entries.next()
if(result.done !== true){
result.value[1].sort((a,b) => a -b)
goNext = true
}else{
goNext = false
}
}
return arr
}
let dArr = [[1,34],[456,2,3,44,234],[4567,1,4,5,6],[34,78,23,1]];
console.log(sortDubble(dArr));
/**
* ⭐⭐⭐
* every(callback(元素值,元素的索引,原数组),thisArg) 用于测试数组得所有元素是否都通过了指定函数得测试
* callback:测试函数
* thisArg:执行callback中得this函数
* 注意:every不会改变数组 返回得是ttue或者false
*/
function bigEnough(item,index,arr){
return item > 10
}
let passed = [12, 5, 8, 130, 44].every(bigEnough,this)
console.log(passed);
/**
* ⭐⭐⭐
* some(callback(元素值,元素的索引,原数组),thisArg)方法测试数组中的某些元素是否通过由提供的函数实现的测试。
* 和every差不多
* 也是返回true或者false
*/
let pass = [1, 5, 8, 2, 3].some(bigEnough,this)
console.log(pass);
/**
* ⭐⭐⭐⭐
* fill(value,start,end)方法用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。
* value:值
* start:开始索引
* end:结束索引
* 注意:修改的是当前的数组
* 如果 start 是个负数, 则开始索引会被自动计算成为 length+start, 其中 length 是 this 对象的 length 属性值. 如果 end 是个负数, 则结束索引会被自动计算成为 length+end.
*/
console.log(['qw','qwe','wqe','wqe','wqe','wqe'].fill('fill',3,5));
/**
* ⭐⭐⭐⭐⭐
* filter(callback(element, index, array),thisArgs) 数组过滤器 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。
* 注意:返回的是新数组
*/
let filtered = [12, 5, 8, 130, 44].filter(bigEnough,this)
console.log(filtered);
/**
* ⭐⭐⭐⭐
* find(callback(元素值,元素的索引,原数组),thisArg)方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。
* 注意:在不满足条件的时候返回的是undefined
* 返回得是数组中得一个值
* 注意这个和string.indexOf一样是第一个出现得值 :注意 这个是返回的是值
*/
//用对象得属性查找数组中得对象
var inventory = [
{name: 'apples', quantity: 2},
{name: 'bananas', quantity: 0},
{name: 'cherries', quantity: 5}
];
function findObj(fruit){
return fruit.name === 'cherries';
}
console.log(inventory.find(findObj));
/**
* ⭐⭐⭐⭐
* findIndex(callback,thisArg)返回的是满足回掉函数的第一个值得索引
* 注意:返回得是一个索引 find方法返回得是值 没找到就返回-1 和string得indexOf()返回值一样
*/
console.log(inventory.findIndex(findObj));
/**
* ⭐⭐⭐⭐⭐
* forEach(callback(currentValue, index, array),thisArg)遍历数组得方法
* 注意:没有返回一个新数组! & 没有返回值! 修改得是原数组得值
* 没有办法中止或者跳出 forEach 循环,除了抛出一个异常。这个是需要注意得。如果你需要这样,使用forEach()方法是错误的,你可以用一个简单的循环作为替代。
* 如果您正在测试一个数组里的元素是否符合某条件,且需要返回一个布尔值,那么可使用 Array.every 或 Array.some。
* 如果可用,新方法 find() 或者findIndex() 也可被用于真值测试的提早终止。
* 应用场景:为一些相同的元素,绑定事件处理器! 主要用于对数组得遍历
*/
let i = 1
inventory.forEach((item,index,arr) =>{
item.id = i++
})
console.log('foreach');
console.log(inventory)
/**
* ⭐⭐⭐
* includes(searchElement,fromIndex)方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。
* 注意:返回的是true或者false
* fromIndex大于数组的长度时直接返回false
*/
let inArr = [1, 2, 3];
console.log(inArr.includes(2))
// true
console.log(a.includes(4));
// false
//includes()可以使用一个通用方法 不仅仅可以用于数组对象,也可以用于类数组对象
(function(){
// 通用的自执行方法
console.log([].includes.call(arguments,'a'));
console.log([].includes.call(arguments,'m'));
})('a','b','c')
/**
* ⭐⭐⭐
* indexOf(elem,fromIndex)方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。
* 他和findIndex()不同的是参数不同,这个是具体的element,而findIndex查找的是满足回掉函数条件的元素的位置索引
* 和string的字符串的使用方法类似
*/
let indexArr = [1, 2, 3];
console.log(indexArr.indexOf(2));
//查找一个元素出现的所有位置
let po = [];
let arrs = ['a', 'b', 'a', 'c', 'a', 'd']
let element = 'a'
let idx = arrs.indexOf(element)
while(idx != -1){
po.push(idx)
idx = arrs.indexOf(element,idx+1)
}
console.log('用indexOf查找出现的位置');
console.log(po);
/**
* ⭐⭐⭐⭐⭐
* join(separator) 将一个数组或者类数组对象转化为以separator分割的字符串
* 注意:不会改变原数组,会返回一个新的字符串 参数默认的是 , 也可以是空字符串
* 如果arr的length是0 则返回空字符串
*/
console.log(arrs.join('分割'));
//分割类数组对象
(function(){
let str = Array.prototype.join.call(arguments)
console.log(str);
})('a','b','c')
/**
* ⭐⭐⭐
* keys()方法返回一个新的Array迭代器,它包含数组中每个索引的键。
* 注意:返回的是一个新的array迭代器对象
* 和Array.entries()差不多 但是这个只有key 也就是迭代器value只有下标索引
*/
let keyArr = [1, 2, 3];
let keyIter = keyArr.keys()
console.log('key 方法的返回值是一个包括key的数组迭代器');
console.log(keyIter.next());
/**
* ⭐⭐⭐
* values() 方法返回一个新的 Array Iterator 对象,该对象包含数组每个索引的值。
* 注意:Chrome 未实现,Firefox未实现,Edge已实现。 测试该方法建议使用edge
* Chrome 及Firefox可以用"arr[Symbol.iterator]()"方法来代替values()方法
*/
let valueArr = ["angel", "clown", "mandarin", "surgeon"]
// let valueIter = valueArr.values()
console.log('values 方法的返回值是一个包括value的数组迭代器')
// console.log(valueIter.next());
/**
* ⭐⭐⭐
* lastIndexof(searchElement,开始位置,默认为数组的长度-1)方法返回指定元素(也即有效的 JavaScript 值或变量)在数组中的最后一个的索引,如果不存在则返回 -1。
* 从数组的后面向前查找,从 fromIndex 处开始。
* 注意:这个方法是逆向查找, 找到是一个元素在数组中最后一次出现为止的索引,和indexOf一样返回索引 如果没找到返回-1
*/
let lastArr = [2, 5, 9, 2]
let lastIndex =lastArr.lastIndexOf(9);
console.log(lastIndex);
//查找当前元素在数组中的出现的位置
/**
* 注意,我们要单独处理idx==0时的情况,因为如果是第一个元素,忽略了fromIndex参数则第一个元素总会被查找。这不同于indexOf方法
*/
let idy = arrs.lastIndexOf(element)
let lastIndexArr = []
while(idy != -1){
lastIndexArr.push(idy)
idy = idy > 0 ? arrs.lastIndexOf(element,idy-1) : -1;
}
console.log('用lastIndexOf查找出现的位置');
console.log(lastIndexArr);
/**
* ⭐⭐⭐⭐⭐
* map(callback(currentValue, index, array),thisArg)遍历数组的方法,返回通过每一项回掉函数修改之后的新数组
* 注意:返回的是新数组 不修改原数组 这个值返回得是callback主执行完得返回 没有返回就返回undefined
* map 方法会给原数组中的每个元素都按顺序调用一次 callback 函数。callback 每次执行后的返回值(包括 undefined)组合起来形成一个新数组。
* callback 函数只会在有值的索引上被调用;那些从来没被赋过值或者使用 delete 删除的索引则不会被调用。
*/
let mapInventory = [
{name: 'apples', quantity: 2},
{name: 'bananas', quantity: 0},
{name: 'cherries', quantity: 5}
];
let mapArr = mapInventory.map((item,index,mapInventory) => {
item.operate= 'map操作'
item.index = index
return item
})
console.log(mapArr);
/**
* ⭐⭐⭐⭐
* pop()删除数组得最后一项并返回最后一项得值
* 注意:返回值是数组中得最后一项得值,而且修改了原数组
*/
let pop = mapInventory.pop()
console.log(pop);
/**
* ⭐⭐⭐⭐⭐
* push(item)方法将一个或多个元素添加到数组的末尾,并返回新数组的长度。
* 注意:这个是添加到末尾 新数组length 属性值将被返回。 并且修改远数组
*/
let push = mapInventory.push({
name: 'cherries', quantity: 5
})
console.log(push);
//可以用来合并两个数组 通过call方法
let vegetables = ['parsnip', 'potato']
let moreVegs = ['celery', 'beetroot']
// 这就相当于 vegetables.push('celery', 'beetroot');
Array.prototype.push.call(vegetables,moreVegs)
console.log(vegetables)
// Array.prototype.push可以在一个对象上面使用
let obj = {
length:0,
addElem(elem){
[].push.call(this,elem) // 这个key是数组得索引
}
}
obj.addElem('add')
obj.addElem('123')
console.log(obj)
/**
* ⭐⭐⭐⭐⭐
* reduce(callback(accumulator,currentValue,currentIndex,array),initialValue)方法对累加器和数组中的每个元素(从左到右)应用一个函数,将其减少为单个值。
* reduceRight(callback(accumulator,currentValue,currentIndex,array),initialValue) 方法接受一个函数作为累加器(accumulator)和数组的每个值(从右到左)将其减少为单个值。 PS:这个方法和reduce方法执行顺序相反
* callback处理方法
* accumulator:累加器累加回掉得返回值
* currentValue:当前得value
* currentIndex:当前下标
* array:当前数组
* initialValue:累加器默认值
* 注意: IE9以下不支持,
* 注意:回调函数第一次执行时,accumulator 和currentValue的取值有两种情况:
* 1:调用reduce时提供initialValue,accumulator取值为initialValue,currentValue取数组中的第一个值;
* 2:没有提供 initialValue,accumulator取数组中的第一个值,currentValue取数组中的第二个值。
* 如果没有提供initialValue reduce 会从索引1的地方开始执行 callback 方法,跳过第一个索引。如果提供initialValue,从索引0开始。
* 如果数组为空且没有提供initialValue,会抛出TypeError 。如果数组仅有一个元素(无论位置如何)并且没有提供initialValue, 或者有提供initialValue但是数组为空,那么此唯一值将被返回并且callback不会被执行。
*/
// 提供初始值通常更安全,正如下面的例子,如果没有提供initialValue,则可能有三种输出:
let maxCallback = (pre,cur) => Math.max(pre.x,cur.x)
let maxCallback2 = (pre,cur) => Math.max(pre,cur)
// reduce() without initialValue
// [{ x: 22 }, { x: 42 }].reduce( maxCallback ); // 42
// [ { x: 22 } ].reduce( maxCallback ); // { x: 22 }
// [ ].reduce( maxCallback ); // TypeError
// // map/reduce; better solution, also works for empty arrays
[ { x: 22 }, { x: 42 } ].map( el => el.x ).reduce( maxCallback2, -Infinity );
//reduce是如何运行的
let accumulator = [0, 1, 2, 3, 4].reduce(function(pre,cur,index,arr){
return pre + cur
})
console.log(accumulator);
// 这个reduce执行4次 没有初始值从index为1的部分开始执行每次累加 最终返回callback处理完的accumulator
//用途
//1:将二维数组转换为一维数组
let reduceTran = [[0, 1], [2, 3], [4, 5]].reduce((pre,cur) => pre.concat(cur),[])
console.log(reduceTran);
//2:计算数组中每个元素出现的次数
let names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice']
let countNames = names.reduce(function(allName, name){
if(name in allName){
allName[name]++
}else{
allName[name] = 1;
}
return allName
},{})
console.log(countNames);
// 3:利用扩展运算符和initialValue绑定包含在对象数组中的数组
let friends = [{
name: 'Anna',
books: ['Bible', 'Harry Potter'],
age: 21
}, {
name: 'Bob',
books: ['War and peace', 'Romeo and Juliet'],
age: 26
}, {
name: 'Alice',
books: ['The Lord of the Rings', 'The Shining'],
age: 18
}];
let friendList = friends.reduce((pre,cur) =>{
return [...pre,...cur.books]
},['begin'])
console.log(friendList);
//数组去重 借助一个外部的对象的方式
let reduceArr = [1,2,1,2,3,5,4,5,3,4,4,4,4]
let reduceObj = {}
let norepeats = reduceArr.reduce(function(pre,cur){
if(!reduceObj[cur]){
reduceObj[cur] = cur
pre.push(cur)
}
return pre
},[])
console.log('reduce和对象组合数组去重');
console.log(norepeats);
/**
* ⭐⭐⭐⭐
* reverse 方法颠倒数组中元素的位置,并返回该数组的引用。
* 注意:返回的是当前数组的引用 不是一个完全的新数组 会修改原数组
*/
let myArray = ['one', 'two', 'three' ,'four']
console.log(myArray.reverse());
console.log(myArray);
/**
* ⭐⭐⭐⭐
* shift()删除数组中的第一个元素并返回这个元素的值
* 注意:返回的是数组中第一项的值
*/
let shiftArr = [1, 2, 3]
let shiftArrb = shiftArr.shift()
console.log('shift 返回值为第一项元素的值');
console.log(shiftArrb);
/**
* ⭐⭐⭐⭐
* unshift() 将一个或者多个元素添加到数组的开头,并返回新数组的长度
* 注意:返回的是新数组的长度,和上面那个返回值是不一样的
* 当这个方法是用在对象身上的时候返回的是对象的length属性
*/
let unshiftArr = [1,2,3]
let unshiftBack = unshiftArr.unshift(5,6)
console.log(unshiftArr);
console.log('unshift 返回值为调用之后该数组的长度');
console.log(unshiftBack);
/**
* slice(startIndex,endIndex)方法返回一个从开始到结束(不包括结束)选择的数组的一部分浅拷贝到一个新数组对象。且原始数组不会被修改。
* 注意:返回的是一个浅拷贝的新数组 但是不会修改原数组 包括看来是不包括结束 总结来说:返回的是一个含有提取元素的数组 也就是现有元素的一部分
*/
let sliceArr = [1, 2, 3].slice(2,3)
console.log('slice 返回值')
console.log(sliceArr);
// 类数组对象同样可以使用
/**
* 除了使用 Array.prototype.slice.call(arguments),你也可以简单的使用 [].slice.call(arguments) 来代替。
*/
function list(){
return Array.prototype.slice.call(arguments)
}
console.log(list(1,23,23,23,23))
//另外,你可以使用 bind 来简化该过程。
var unboundSlice = Array.prototype.slice
var slice = Function.prototype.call.bind(unboundSlice)
function bindList(){
return slice(arguments)
}
console.log(bindList(23,213,33,3,4,4));
/**
* ⭐⭐⭐⭐
* sort(compareFunction)数组排序 但是不一定稳定 是根据Unicode码点
* compareFunction:可选。用来指定按某种顺序进行排列的函数。如果省略,元素按照转换为的字符串的各个字符的Unicode位点进行排序。
* 注意:返回排序后的数组。原数组已经被排序后的数组代替。修改原数组
*/
var scores = [1, 10, 21, 2];
console.log(scores.sort());
function compare(a,b){
if(a > b){
return 1
}
if(a<b){
return -1
}
return 0
}
let compareNumbers = (a,b) => a-b
scores = [1, 10, 21, 2];
// let comparF = scores.sort(compare)
let comparF = scores.sort(compareNumbers)
console.log(comparF);
/**
* ⭐⭐⭐⭐⭐
* splice(startIndex,delCout,item1,item2,item3...) 方法通过删除现有元素和/或添加新元素来更改一个数组的内容。
* start:操作得开始位置
* delCout:删除得个数
* item1,item2,item3...:要添加进数组的元素,从start 位置开始。如果不指定,则 splice() 将只删除数组元素。
* splice方法使用deleteCount参数来控制是删除还是添加:
* start参数是必须得
* splice(start) : 从start一直删除到数组末尾
* splice(start, deleteCount) : 从start删除deleteCount个元素
* splice(start, 0, item1, item2, ...) : 从start增加0个item元素
* 注意:这个方法可删可增加,而且修改得是原数组 ,但是它得返回值是由被删除元素组成得数组,删除0个返回一个空数组
* 请注意,splice() 方法与 slice() 方法的作用是不同的,splice() 方法会直接对数组进行修改。
*/
let myFish = ["angel", "clown", "mandarin", "surgeon"]
let delArr = myFish.splice(2,1,'drum')
console.log(myFish);
console.log('splice返回值:')
console.log(delArr);
/**
* ⭐⭐⭐⭐⭐
* toString() 数组转字符串 返回一个字符串,表示指定的数组及其元素。
* 注意:返回字符串,相当于join() 不会修改原数组
*/
let stringArr = ["angel", "clown", "mandarin", "surgeon"]
console.log(stringArr.toString())
console.log('toString的返回值');
console.log(stringArr);
/**
* 测试demo
*/
const jsonArr = [
{id:1,name:'demo',num:1},
{id:2,name:'demo',num:1},
{id:1,name:'demo',num:1},
{id:4,name:'demo',num:1},
]
let newJsonArr = [
{id:1,name:'demo',num:1}
]
jsonArr.forEach((item,index) =>{
let curIndex,isInArr = false
isInArr = newJsonArr.some((sub,index) => {
return sub.id === item.id
})
if(isInArr){
newJsonArr.forEach(child =>{
child.id == item.id ? child.num += 1 :''
})
}else{
newJsonArr.push(item)
}
})
console.log('====================================');
console.log('数组中的总结')
/**
* 根据分类对数组的方法进行总结
*/
//1:不修改原数组,返回一个新数组的方法 ⭐⭐⭐⭐⭐
/**
* 1:from(arrLike,mapFn.this) 类数组对象转为数组
* 2:concat() 连接两个或者多个数组
* 3:
*/
//2:返回一个浅拷贝的数组
/**
*
*/
//3:修改原数组 ⭐⭐⭐⭐⭐
/**
* 1:copyWithin(targetIndex,startIndex,endIndex) 把从startIndex到endIndex的元素拷贝到targetIndex位置
*/
//4:返回一个Iterator对象的方法
/**
* 1:entries()
*/
//5:返回boolean值得方法
/**
* every(mapFn,thisArg) 对数组遍历进行 && 操作 之后结果
* some(mapFn,thisArg) 对数组操作返回 || 操作之后结果
*/
console.log('====================================');
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
# Function模块
// 防抖
/**
*
* @param {*Function} fn 目标函数
* @param {*Number} wait 等待时长
* @param {*Boolean} immediate 是否立即执行
*/
function debunce (fn, wait, immediate = true) {
let timer
// 稍后执行
const later = (context, args) => setTimeout(() => {
timer = null
if (!immediate) {
fn.apply(context, args)
context = args = null
}
}, wait)
// 延迟处理
let debunced = function (...params) {
let context = this
let args = params
if(!timer) {
// 如果没有计时器 就创建timer
timer = later(context, args)
// 在需要立即执行的时候先执行一下
if (immediate) {
fn.call(context, args)
}
} else {
// 如果有计时器,那么重置计时器
clearTimeout(timer)
timer = later(context, args)
}
}
debunced.cancel = function() {
clearTimeout(timer)
timer = null
}
return debunced
}
// 使用
window.onresize = debunce(() => {
const time = new Date().getSeconds()
console.log(time)
}, 2000, false)
// 节流
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
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
# ES6中新API专题
/*
* @Author: etongfu
* @Date: 2018-11-06 14:37:11
* @Last Modified by: etongfu
* @Last Modified time: 2018-11-30 16:36:40
* ES6 中新特性
*/
console.warn('ES6中新API专题开始')
console.log('====================================');
console.log('==================> Symbol相关')
console.log('====================================');
// 创建Symbol
let s = Symbol()
console.log(typeof s) // symbol 所以标识Symbol是一个原始类型
let s1 = Symbol('s1')
let s2 = Symbol('s2')
console.log(s1, s1.toString())// Symbol(s1) "Symbol(s1)"
console.log(s2, s2.toString())// Symbol(s2) "Symbol(s2)"
const obj = {
a: 1
}
let s3 = Symbol(obj)
console.log(s3) // Symbol([object Object])
// 计算symbol
// console.log('Symbol is' + s3) // Uncaught TypeError: Cannot convert a Symbol value to a string
// 转化为Boolean值
let sym = Symbol()
console.log(Boolean(sym)) // true
console.log(!sym) // false
//symbol.for
let sfor1 = Symbol.for('string')
let sfor2 = Symbol.for('string')
console.log(sfor1 === sfor2) // true
console.log(Symbol.keyFor(sfor1)) // string
// 当作键名来使用
let objSymbol = Symbol()
let obj1 = {}
obj1[objSymbol] = '第一种写法'
let obj2 = {
[objSymbol]: '第二种写法'
}
let obj3 = {}
Object.defineProperty(obj3, objSymbol, {
value: '第三种写法'
})
const obj4 = {}
obj4.objSymbol = '字符串属性!' // 这个是加上了个普通的字符串属性,并不是个Symbol属性
obj4[objSymbol] // undefined
obj4['objSymbol'] // "字符串属性!"
// 作为常量使用
const RED = Symbol('red')
const GREEN = Symbol('green')
function getComputed (color) {
switch (color) {
case RED: // 保证条件是唯一的
return RED
break;
case GREEN:
return GREEN
break;
default:
throw new Error('Color is undefined')
break;
}
}
console.log(getComputed(RED)) // Symbol(red)
// 魔术字符串
/* function getArea(shape, options) {
let area = 0;
switch (shape) {
case 'Triangle': // 魔术字符串
area = .5 * options.width * options.height;
break;
}
return area;
}
getArea('Triangle', { width: 100, height: 100 }); // 魔术字符串 */
// 使用Symbol代替
const symbolType = {
triangle: Symbol('triangle')
}
function getArea (shape, options) {
let a = 0
switch (shape) {
case symbolType.triangle:
a = options.width * options.height
break
}
return a
}
getArea(symbolType.triangle, {width: 100, height: 100})
// 循环遍历
const symObj = {
[s1]: 's1',
[s2]: 's2'
}
for (const key in symObj) {
console.log('symbol key') // 不会输出 甚至检测不到key
console.log(key) // 没有作用
}
const objectSymbols = Object.getOwnPropertySymbols(symObj)
console.log(objectSymbols) // [Symbol(s1), Symbol(s2)]
// 和`getOwnPropertyNames`对比
const obj5 = {}
let s4 = Symbol('s4')
Object.defineProperty(obj5, s4, {
value: 'value'
})
for (const key in obj5) {
console.log(key) // 无输出
}
console.log(Object.getOwnPropertyNames(obj5)) // []
console.log(Object.getOwnPropertySymbols(obj5)) // [Symbol(s4)]
// 获取全部的keys
const allKeys = {
[Symbol('key')] : 'symbol',
num: 1
}
console.log(Reflect.ownKeys(allKeys)) // ["num", Symbol(key)]
// Set And Map
// set
const set1 = new Set([1,2,2,3,3,3,3])
console.log(set1) // Set(3)
const arrayLikes = document.getElementsByTagName('div')
console.log(arrayLikes) // HTMLCollection
const set2 = new Set(arrayLikes)
console.log(set2.size) // 27
// 先创建set
const setFirst = new Set()
const setArr = [1,2,3,1,12,3,4,3,4,3,4]
setArr.forEach(element => {
setFirst.add(element)
})
console.log(setFirst) // Set(5) {1, 2, 3, 12, 4}
// Api操作示例
let setMethod = new Set()
setMethod.add(1).add(2).add(2) // Set(2) {1, 2}
console.log(setMethod) //
setMethod.has(1) // true
setMethod.delete(1) //
console.log(setMethod) // Set(1) {2}
setMethod.clear()
console.log(setMethod) // Set(0) {}
// 遍历方法
let setFor = new Set(['red', 'green', 'blue'])
console.log(setFor.keys()) // SetIterator {"red", "green", "blue"}
for (const iterator of setFor.keys()) {
console.log(iterator) // red green blue
}
console.log(setFor.values()) // SetIterator {"red", "green", "blue"}
for (const iterator of setFor.values()) {
console.log(iterator) // red green blue
}
console.log(setFor.entries())
for (const iterator of setFor.entries()) {
// entries方法返回的遍历器,同时包括键名和键值,所以每次输出一个数组,它的两个成员完全相等。
console.log(iterator) // ["red", "red"] ["green", "green"] ["blue", "blue"]
}
// forEach 方法
setFor.forEach((value, key) => console.log(key + ' : ' + value))
// set的应用
// arrary.from转化
const setArrays = new Set([1, 2, 3, 4, 5])
let arraySets = Array.from(setArrays)
console.log(arraySets) // [1, 2, 3, 4, 5]
// 配合`...`展开运算符快速去重。
let numbers = [1,2,1,1,23,4,12,1,1]
console.log([...new Set(numbers)])// [1, 2, 23, 4, 12]
//求两个数组的集合
const arrayA = [1,2,3], arrayB= [2,3,4]
let setA = new Set(arrayA), setB = new Set(arrayB)
// 并集
const union = new Set(arrayA.concat(arrayB))
console.log(union) // Set(4) {1, 2, 3, 4}
// 交集
const intersect = new Set(arrayA.filter(x => setB.has(x)))
console.log(intersect) // Set(2) {2, 3}
// 差集
const differenceA = arrayA.filter(x => !setB.has(x))
const diefferenceB = arrayB.filter(x => !setA.has(x))
const difference = new Set([...differenceA, ...diefferenceB])
console.log(difference) // set(2) {1, 4}
// Map
// Map的基本使用
const map = new Map()
const mapObj = {m: 'map'}
map.set(mapObj, 'this is value')
console.log(map.get(mapObj)) // this is value
console.log(map.has(mapObj)) // true
console.log(map.delete(mapObj)) // true
console.log(map.has(mapObj)) // fasle
// 数组入参
const arrMap = new Map([
['name', 'arr'],
['title', 'map']
])
console.log(arrMap.has('name'))
console.log(arrMap.get('name')) // arr
console.log(arrMap.get('title'))// map
// 语法趟
const arr = [
['name', '张三'],
['title', 'Author']
]
let map2 = new Map()
arr.forEach(
([key, value]) => map2.set(key, value)
)
console.log(map2) // Map(2) {"name" => "张三", "title" => "Author"}
// set和map作为构造函数
const mapSet = new Set([
['foo', 1],
['bar', 2]
])
const setMap = new Map(mapSet)
console.log(setMap) // Map(2) {"foo" => 1, "bar" => 2}
const mapMap = new Map([['name', 'mapMap']])
const mapMap2 = new Map(mapMap)
console.log(mapMap2) // Map(1) {"name" => "mapMap"}
// 键的引用问题
const b = ['b']
const refMap = new Map()
refMap.set(['a'], 555)
refMap.set(b, '指向同一地址')
console.log(refMap);
console.log(refMap.get(['a'])) // undefined
console.log(refMap.get(b)) // 指向同一地址
// Map属性
console.log(refMap.size) // 2
// 遍历
const forMap = new Map([
['for', 'no'],
['map', 'yes'],
])
// key
for (const key of forMap.keys()) {
console.log(key) // for map
}
for (const key of forMap.values()) {
console.log(key) // no yes
}
for (const key of forMap.entries()) {
console.log(key) // ["for", "no"] ["map", "yes"]
}
// 结构显示
for (let [key, value] of forMap.entries()) {
console.log(key, value) // for no, map yes
}
// forEach
forMap.forEach(function(value, key, map) {
console.log("Key: %s, Value: %s", key, value);
},this) // Key: for, Value: no , Key: map, Value: yes
// 数据结构间的转换
const mapToArr = [...forMap]
console.log(mapToArr) // ) [Array(2), Array(2)]
// 对象和Map
function mapToObject(map) {
let obj = {}
for (const [k, v] of map.entries()) {
obj[k] = v
}
return obj
}
console.log(mapToObject(forMap)) // {for: "no", map: "yes"}
function objToMap(object) {
const map = new Map()
for (const key in object) {
if (object.hasOwnProperty(key)) {
map.set(key, object[key])
}
}
return map
}
console.log(objToMap({ka:'va', kb: 'vb'})) // Map(2) {"ka" => "va", "kb" => "vb"}
// JSON和MAP
// 2 数组json
let jsonMap = new Map().set(true, 7).set({foo: 3}, ['abc'])
function mapToJson (map) {
return JSON.stringify([...map])
}
console.log(mapToJson(jsonMap)) // [[true,7],[{"foo":3},["abc"]]]
// weakMap
let weakObj = {
a: 1
}
const weakMap = new WeakMap()
weakMap.set(weakObj, 1)
console.log(weakMap.get(weakObj)) // 1
weakObj = null
console.log(weakMap.get(weakObj)) // undefined 这个key会跟着weakObj的销毁而销毁
//但是值却不被影响
let valueObj = {
a: '我是 value object key'
}
let value = {
a: 'value '
}
weakMap.set(valueObj, value)
console.log(weakMap.get(valueObj)) // {a: "value "}
value = null
console.log(weakMap.get(valueObj)) // {a: "value "}
// Proxy
// 下面代码对一个空对象架设了一层拦截,重定义了属性的读取(get)和设置(set)行为
const proxyObj = new Proxy({}, {
/**
* 读取行为
* @param {*目标对象} target
* @param {*目标key} key
* @param {*接收对象(Symbol值)} receiver
*/
get (target, key, receiver) {
console.log(target) // {count: 1}
console.log(`getting ${key}!`); // getting count!
return Reflect.get(target, key, receiver);
},
/**
* 设置行为
* @param {*目标对象} target
* @param {*属性名} key
* @param {*设置value} value
* @param {*proxy实例本身(Symbol)} receiver
*/
set (target, key, value, receiver) {
console.log(target) // {}
console.log(receiver) // Proxy {}
console.log(`getting ${key} ${value}!`);
return Reflect.set(target, key, value, receiver);
}
})
proxyObj.count = 1 // getting count 1!
console.log(proxyObj.count) // 1
// 更实际的拦截
const objForPro1 = {
a: 1
}
const proxyObj1 = new Proxy(objForPro1, {
set (target, key) {
return 1
}
})
// 拦截针对的是Proxy对象!
proxyObj1.a = 4
proxyObj1.a = 5
console.log(proxyObj1.a) // 1
// 如果handler没有设置任何拦截,那就等同于直接通向原对象。
const proxyObj2 = new Proxy(objForPro1, {})
proxyObj2.a = 2
console.log(proxyObj2.a) // 2
// Proxy 实例也可以作为其他对象的原型对象。
const objByProxy = Object.create(proxyObj2)
console.log(objByProxy.a) // 2
// 同一个拦截器函数,设置拦截多个操作。
const mutiHandle = {
get (target, name) {
if (name === 'prototype') {
return Object.prototype
}
console.log(`Hello ${name}`);
return `Hello ${name}`
},
/**
* 自定义apply 函数
* @param {*目标对象} target
* @param {*目标this} thisBinding
* @param {*参数数组} args
*/
apply (target,thisBinding, args) {
console.log(`rewrite call && args = ${[...args]}`);
return args[0]
},
/**
* 自定义构造器函数
* @param {*} target
* @param {*} args
*/
construct (target, args) {
console.log(`rewrite construct && args = ${[...args]}`);
return {value: args[1]}
}
}
let proxyObj3 = new Proxy(function(x, y) {
return x + y
}, mutiHandle)
console.log(proxyObj3(1,2))// call: 调用函数被拦截 rewrite call && args = 1,2
console.log(new proxyObj3(1, 2)) // {value: 2} rewrite construct && args = 1,2
console.log(proxyObj3.prototype === Object.prototype) // true 因为我们拦截了get
console.log(proxyObj3.a === "Hello a") // true 因为我们同时也拦截了value
// proxy实例的的方法
// get方法的继承
let proto = new Proxy({}, {
get (target, key) {
console.log('GET ' + key)
return target[key]
}
})
let proxyObj4 = Object.create(proto) // proxyObj4直接继承了proto中的get 方法
proxyObj4.foo // foo
// get 根据复索引取值
function createArr (...args) {
let handler = {
get (target, key, receiver) {
const index = Number(key)
// 构建真实的key
if (key < 0) {
key = String(target.length + index);
}
// Reflect以后再说
return Reflect.get(target, key, receiver)
}
}
let target = []
target.push(...args)
// 返回一个被proxy拦截的数组对象
return new Proxy(target, handler);
}
const proxyArr = createArr('a','b', 'c', 'd')
console.log(proxyArr[-1]) // d
// 如果目标对象自身的某个属性,不可写且不可配置,那么set方法将不起作用。
const readObj = {}
// 又是一种设置只读属性的方法
Object.defineProperty(readObj, 'name', {
value: 'readOnly',
writable: false,
})
const uselessHandle = {
set (target, key, value, receiver) {
target[key] = 'set by proxy'
}
}
const uselessProxy = new Proxy(readObj, uselessHandle)
// uselessProxy.name = 'proxy' // Cannot assign to read only property 'name' of object '#<Object>
console.log(uselessProxy.name)// readOnly
// 简单的apply()
let applyFunc = function() {console.log(`I am target function`)}
const applyProxy = new Proxy(applyFunc, {
/**
* @param {*} target
* @param {*} targetThis
* @param {*} args
*/
apply (target, targetThis, args) {
console.log(`i am proxy`)
}
})
applyProxy() // i am proxy
// 使用has() 隐藏部分属性
const hasHandle = {
/**
* 隐藏“_”开头的属性
* @param {*} target
* @param {*} key
*/
has (target, key) {
if (key[0] === '_') {
return false
}
return key in target
}
}
let hasObj = {name: 'has', _age: 'private'}
let hasProxy = new Proxy(hasObj, hasHandle)
console.log('_age' in hasProxy) // false
// 不可扩展的时候使用has会报错
let noHasObj = {a: 1}
Object.preventExtensions(noHasObj) // 禁止扩展
const noHasProxy = new Proxy(noHasObj, {
has: function(target, prop) {
return false
}
})
// console.log('a' in noHasProxy) // Uncaught TypeError: 'has' on proxy: trap returned falsish for property 'a' but the proxy target is not extensible
// has对for in 循环是不起作用的
for (const key in hasProxy) {
console.log(key)// name _age
}
// construct 拦截
let newProxy = new Proxy(function () {
console.log(`old construct`)
}, {
/**
* @param {*目标对象} target
* @param {*参数列表} args
* @param {*new命令作用的构造函数(例子中的newProxy)} newTarget
*/
construct: function(target, args, newTarget) {
console.log(target)
console.log('proxy construct called: ' + args.join(', '));
console.log(newTarget)
return { value: args[0] * 10 };
}
})
let proxyValue = new newProxy(1)
console.log(proxyValue.value) // 10
// delete操作符拦截
let deleteObj = {
ok: 'ok',
_no: 'no'
}
const deleteProxy = new Proxy(deleteObj, {
/**
* @param {*} target
* @param {*} key
*/
deleteProperty (target, key) {
if (key[0] === '_') {
throw new Error(`Invalid attempt to private "${key}" property`);
}
}
})
// delete deleteProxy._no //报错
// 拦截Object.defineProperty
const defineHandle = {
/**
* @param {*} target
* @param {*} key
* @param {*修饰符} descriptor
*/
defineProperty (target, key, descriptor) {
console.log(descriptor) // {value: "demo", writable: true, enumerable: true, configurable: true}
return false
}
}
const defineProxy = new Proxy({}, defineHandle)
// defineProxy.demo = 'demo' // 不会生效
// 拦截getOwnPropertyDescriptor
const getOwn = {
getOwnPropertyDescriptor (target, key) {
if (key[0] === '_') {
return;
}
return Object.getOwnPropertyDescriptor(target, key)
}
}
let ownTaget = {
_foo: 'foo',
bar: 'bar'
}
const ownProxy = new Proxy(ownTaget, getOwn)
console.log(Object.getOwnPropertyDescriptor(ownProxy, '_foo'))// undefined
console.log(Object.getOwnPropertyDescriptor(ownProxy, 'bar'))// {value: "bar", writable: true, enumerable: true, configurable: true}
// getPrototypeOf()拦截
let protoObj = {
name: 'protoObj'
}
const protoProxy = new Proxy({}, {
getPrototypeOf (target) {
console.log('protoObj be proxyed')
return protoObj
}
})
console.log(Object.getPrototypeOf(protoProxy) === protoObj)// true
// 拦截Object.isExtensible(检测对象是否可扩展)
let extendObj = {}
const extendProxy = new Proxy(extendObj, {
isExtensible (target) {
console.log("call isExtensible proxy")
return true
}
})
Object.isExtensible(extendProxy)
// 拦截preventExtensions操作
const preventExtHandle = {
preventExtensions(target) {
console.log('you will preventExtensions target')
Object.preventExtensions(target)
return true
}
}
let preventExtPro = new Proxy({}, preventExtHandle)
Object.preventExtensions(preventExtPro)
// 拦截ownKeys
let keysObj = {
a: 'a',
b: 'b',
c: 'c',
_d: 'd'
}
let keysProxy = new Proxy(keysObj, {
/**
* 拦截只返回a
* @param {*} target
*/
ownKeys(target) {
return ['a']
}
})
for (const key in keysProxy) {
console.log(key) // a
}
// Object.keys的特殊情况
let keySymbol = Symbol('symbol')
let keysFilterObj = {
name: 'name',
[keySymbol]: 'symbol'
}
// 不可枚举属性
Object.defineProperty(keysFilterObj, 'enumerabled', {
enumerable: false,
configurable: true,
writable: true,
value: 'enumerabled'
})
let keysFilteProxy = new Proxy(keysFilterObj, {
ownKeys (target) {
return ['name', keySymbol, 'enumerabled']
}
})
console.log(Object.keys(keysFilteProxy)) // ["name"]
// ownKeys拦截getOwnPropertyNames 方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。
console.log(Object.getOwnPropertyNames(keysFilteProxy)) // ["name", "enumerabled"]
// 拦截setPrototypeOf
let setProtObj = {
foo: 'foo'
}
const setProtProxy = new Proxy(setProtObj, {
setPrototypeOf (target) {
console.log(`called setPrototypeOf`)
return true // 只能返回boolean值
}
})
// Object.setPrototypeOf(obj, prototype)
Object.setPrototypeOf(setProtProxy, Array)
// 返回一个可取消的 Proxy 实例
let {proxy, revoke} = Proxy.revocable({bar: 'bar'}, {})
console.log(proxy) // Proxy {} proxy实例
console.log(revoke) // 取消proxy的函数
proxy.foo = 'foo'
console.log(proxy.bar)
console.log(proxy.foo)
revoke()
// console.log(proxy.bar) // Cannot perform 'get' on a proxy that has been revoked 此时代理被取消,所以proxy实例对象中已经没有不存在了
// Relfact
//Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。
// 没有 Relfact
/* try {
Object.defineProperty(target, property, attributes);
} catch (error) {
console.log(error)
}
// 使用Relfact
if ( Reflect.defineProperty(target, property, attributes)) {
console.log(true)
} else {
console.log(false)
} */
// 让Object操作都变成函数行为。
// 某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
let relfactObj = {
name: 'Reflect'
}
// old
console.log('name' in relfactObj)
// Reflect
console.log(Reflect.has(relfactObj, 'name'));
// 与proxy配合
let logObj = new Proxy(relfactObj, {
get (target, name) {
console.log('get ', target, name)
return Reflect.get(target, name)
},
deleteProperty (target, name) {
console.log('delete ', target, name)
return Reflect.deleteProperty(target, name)
},
has(target, name) {
console.log('has ' + name)
return Reflect.has(target, name)
}
})
console.log('name' in logObj)// true has name
console.log(logObj.name)// get {name: "Reflect"} name Reflect
// 替代new操作符
let ReflectCons = function (name) {
this.name = name
}
const consReflect = Reflect.construct(ReflectCons, ['ReflectCons'])
console.log(consReflect.name)
// Reflect.set
// 如果 Proxy对象和 Reflect对象联合使用,前者拦截赋值操作,后者完成赋值的默认行为,而且传入了receiver,那么Reflect.set会触发Proxy.defineProperty拦截。
let setReObj = {
name: 'setReObj'
}
const setReProxy = new Proxy (setReObj, {
set (target, name, value, receiver) {
console.log('set')
Reflect.set(target, name, value, receiver)
return true
},
defineProperty (target, key , attribute) {
console.log('defineProperty');
Reflect.defineProperty(target, key, attribute);
}
})
setReProxy.name = 'aaa'
// Reflect.ownKeys(target)
const keysReflect = {
[Symbol('key')] : 'key',
key: 'keykey'
}
console.log(Reflect.ownKeys(keysReflect)) // ["key", Symbol(key)]
// Decorator
// @testable
// class MyTestableClass {
// // ...
// }
// function testable(target) {
// target.isTestable = true;
// console.log(`call Decorator`)
// }
console.log(MyTestableClass.isTestable)// true
console.warn('ES6中新API专题结束')
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
# Object API模块
/**
* Object API模块回顾
* MDN文档为主
* Object具有两个属性
* Object.prototype:表示对象得object原型对象
* Object.prototype.__proto__是一个访问器属性
* Object.prototype.constructor:所有得对象都会从它得原型上继承一个constructor属性
*/
/**
* ⭐⭐⭐⭐⭐
* Object.assign(target,...sources) 将source对象中得可枚举得值复制到目标对象 source可以是一个或者多个
* 注意:返回得是目标对象 如果目标对象中的属性具有相同的键,则属性将被源中的属性覆盖。后来的源的属性将类似地覆盖早先的属性。
* 注意,Object.assign 不会跳过那些值为 null 或 undefined 的源对象。
* 这个方法只是个浅拷贝,深拷贝需要使用其他方法
* 这个方法可以用来合并对象以及合并具有相同属性得对象
*/
//可以用来复制一个对象
let obj = {
a:1
}
let copyObj = Object.assign({},obj)
//正常用法
let source = {
b:2
}
let target = Object.assign(obj,source)
console.log(obj);
console.log('assign()返回得是目标对象 主要是修改了目标对象')
console.log(target);
// 深拷贝对象 通过JSON相关得API进行操作
let obj1 = { a: 0 , b: { c: 0}}
let objClone = JSON.parse(JSON.stringify(obj1))
objClone.a = 'clone'
console.log('原对象:');
console.log(obj1)
console.log('克隆出来得对象');
console.log(objClone)
/**
* ⭐⭐⭐
* Object.create(proto,propertiesObject)方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
* proto:新创建对象得原型对象
* propertiesObject:可选 如果没有则为undefined 对象得属性
* 注意:返回一个新对象
*/
//使用Object.create实现类式继承
//父类
function Shape(){
this.x = 0
this.y = 0
}
Shape.prototype.move = function(x,y){
this.x += x
this.y += y
}
//子类
function Rectangle(){
return Shape.call(this) // call super constructor
}
// 子类继承父类
Rectangle.prototype = Object.create(Shape.prototype)
Rectangle.prototype.constructor = Rectangle// 构造函数
let rect = new Rectangle()
console.log('Is rect an instance of Rectangle?', rect instanceof Rectangle); // true
/**
* 创建对象得方式
*/
// let o = {} 就像相当于下面的
let o = Object.create(Object.prototype)
//创建对象的时候使用第二个参数
let oPro = Object.create(Object.prototype, {
// foo会成为所创建对象的数据属性
foo: {
writable:true,
configurable:true,
value: "hello"
},
// bar会成为所创建对象的访问器属性
bar: {
configurable: false,
get: function() { return 10 },
set: function(value) {
console.log("Setting `o.bar` to", value);
}
}
})
function Constructor(){}
// oConstructor = new Constructor()就相当于
let oConstructor = Object.create(Constructor.prototype) // 如果在Constructor函数中有一些初始化代码,Object.create不能执行那些代码
/**
* ⭐⭐⭐⭐⭐
* defineProperties(obj,props)方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。
* 返回当前属性
*/
let defineObj = {}
Object.defineProperties(defineObj,{
'demo':{
value:1,
writable:true
}
})
/**
* ⭐⭐⭐⭐⭐
* defineProperty(obj,prop,descriptor)方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
* obj:目标对象
* prop: 目标对象要新增或者修改的名称
* descriptor:定义被修改的属性描述符
* 注意:这个是实现简单的数据双向绑定的原理和核心
*/
//简单的双向绑定的实现
let model = document.querySelector('#model')
let view = document.querySelector('#view')
var vm = {
val:""
}
//核心部分
Object.defineProperty(vm,'val',{
get(){
return val
},
set(newVal){
let val = newVal
model.value = val;
view.innerHTML = val;
}
})
//事件的监听
document.addEventListener('keyup',function(e){
vm.val = e.target.value || ""
})
/**
* ⭐⭐⭐⭐
* entries()方法返回一个给定对象自身可枚举属性的键值对数组,
* 其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环也枚举原型链中的属性)。
* 注意:返回的是一个数组
*/
let enObj = {
a:1213,
b:212,
c:3423
}
let enArr = Object.entries(enObj)
console.log('数组的entires返回的是一个二维的数组')
console.log(enArr)
// 将Objec转化为map
let mapobj = { foo: "bar", baz: 42 }
let map = new Map(Object.entries(mapobj))
console.log(map)
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
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
# Promise模块
/**
* Promise
一个promise代表了异步操作最终完成或者失败的对象。大多数人都在使用由其他函数创建并返回的promise
本质上:Promise是一个函数的返回的对象,你可以把回掉函数绑定在这个对象上,而不是把回掉函数当作参数传递进去
promise代替了旧式的处理回掉
*/
//旧式的函数
function successCb(){
//todo
console.log('success');
}
function failureCb(){
//todo
console.log('failure');
}
function doSomething(successCb,failureCb){
// todo
}
// 新式函数返回一个你可以直接回掉函数的对象
const promise = doSomething()
promise.then(successCb,failureCb)
//或者更简单的形式
doSomething().then(successCb,failureCb)
// 这种形式就称为异步函数调用 有以下的优点
/**
* 1:保证
* 不像旧式回掉那样传递回掉函数,promise会带来一些保证
* 在Javascript事件队列的当前运行完成之前,回掉函数永远不会被调用
* 通过 .then 形式添加的回调函数,甚至都在异步操作完成之后才被添加的函数,都会被调用,如上所示。
* 通过多次调用 .then,可以添加多个回调函数,它们会按照插入顺序并且独立运行
*
* 但是promise最直接的好处就是链式调用
*/
/**
* 2:链式调用
* 一个常见的需求就是连续执行两个或者多个异步操作,这种情况下,每一个后来的操作都在前面的操作执行成功之后,
* 带着上一步操作所返回的结果开始执行。我们可以通过创造一个promise chain来完成这种需求。
*/
const promise1 = doSomething();
const promise2 = promise1.then(successCb,failureCb)
// promise2不仅代表着doSomething函数的完成,还代表着传入的success和fail的完成,这也可能是其他异步函数返回的promise
//这样的话 任何被添加给promise2的回掉函数都会排在success或fail的返回后面 基本上每一个promise代表了链式中的另外一个异步加载的过程
/**
* 过去的多重赋值会导致回掉地狱
* doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);
*/
//通过promise我们可以换成更简洁的promise链式写法
doSomething().then(function(result){
return doSomethingElse(result);
}).then(function(newResult){
console.log('newresult')
return doThirdThing(newResult)
}).then(function(lastResult){
return doLastThing(lastResult)
}).catch(failureCb)
// then里面的参数是可选的 catch(failureCallback) 是 then(null, failureCallback) 的缩略形式
//也可以是用箭头函数来简化
doSomething().then(result =>doSomethingElse(result) )
.then(newResult => doThirdThing(newResult))
.then(lastResult => doLastThing(lastResult)).catch(failureCb)
//在catch之后可以继续链式操作 即使链式中的一个动作失败之后还能有助于新的动作继续完成
new Promise((resolve, reject) => {
console.log('ini')
resolve()
})
.then(()=>{
throw new Error('it is error')
console.log('do this ');
}).catch(() => {
console.log('do that')
}).then(() => {
console.log('Do this whatever happened before');
})
/**
* 错误传播
* promise链只有底部的一次调用 基本上一个Promise链式遇到异常就会停止,查看链式底的catch回掉来执行
* 在同步的代码执行之后这就非常模型化了
*/
try {
let result = syncDoSomething();
let newResult = syncDoSomethingElse(result);
let finalResult = syncDoThirdThing(newResult);
console.log(`Got the final result: ${finalResult}`);
} catch(error) {
failureCallback(error);
}
/**
* 在旧式的回掉API中创建Promise
* Promise通过它的构造器从头开始创建,只需压迫包裹旧的API就行
* 理想状态下,所有的异步函数都已经返回promise了,但有一些API仍然使用旧式的被传入的成功或者失败的回调。典型的例子就是setTimeout()函数
*/
function saySomething(str){
console.log(str)
}
setTimeout(() => saySomething('10s 之后执行'),10000)
// 混合旧式回调和promise是会有问题的。如果 saySomething 函数失败了或者包含了编程错误,那就没有办法捕获它了。
// 但是我们可以用promise来包裹他 最佳实践是在尽可能底层的地方来包裹有问题的函数,并且永远不要再直接调用它们:
const wait = ms => new Promise(resolve => setTimeout(resolve,ms))
wait(10000).then(() => saySomething('10s promise')).catch(failureCb)
//通常,promise的构造器会有一个可以让我们手动操作resolve和reject的执行函数。
//既然 setTimeout 没有真的执行失败,那么我们可以在这种情况下忽略reject。
/**
* 组成
* Promise.resolve()和Promise.reject()是手动创建一个已经resolve或者reject的promise快捷方法。它们有时很有用。
* Promise.all()和Promise.race()是并行运行异步操作的两个组合式工具。
*/
// 时序组合可以用一些优雅的JavaScript组合
[fun1,fun2].reduce((p,f) => p.then(f), Promise.resolve())
// 调用数组时就相当于
Promise.resolve.then(fun1).then(fun2)
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
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
# javascript 原型
/*
* @Author: etongfu
* @Email: 13583254085@163.com
* @Version:
* @Date: 2019-05-23 14:44:27
* @LastEditors: etongfu
* @LastEditTime: 2019-05-23 17:36:10
* @Description: 原型相关示例代码
* @youWant: add you want info here
*/
console.warn("------------------------------------这个是原型相关Demo begin------------------------------------");
// 构造函数Person
function Person (name, age) {
this.name = name
this.age = age
this.sayType = function () {
console.log(this.type)
}
}
// 原型中属性
Person.prototype.type = "human"
// 构造函数People
function People () {
}
People.prototype = new Person()
// 实例对象
const person = new Person()
person.sayType()
const people = new People()
// _proto_指向
console.log(person.__proto__) // ==> Person.prototype
// 实例对象的prototype
console.log(person.prototype) // undefined
// 构造函数的prorotype
console.log(Person.prototype) // ==> Person.prototype
// 指向对比
console.log(person.__proto__ === Person.prototype) // ==> true
// 原型属性的构造函数
console.log(Person.prototype.constructor === Person) // ==> true
// 同样的我们可以再进一步的访问Person.prototype.__proto__
console.log(Person.prototype.__proto__) // ==> 因为Person.prototype没有显式的定义, 那么__proto__就是`Object.prorotype`
// 验证
console.log(Person.prototype.__proto__ === Object.prototype) // ==> 因为Person.prototype没有显式的定义, 那么__proto__就是`Object.prorotype`
// 如果过显式的指定了Person.prototype
console.log(People.prototype.__proto__) // ==> 如果有显式的定义, 那么__proto__就是显式定义的值`Person.prorotype`
console.log(People.prototype.__proto__ === Person.prototype) // ==> true
// 同样的我们也会发现People和Person存在着继承关系
console.log([people instanceof People, people instanceof Person, people instanceof Object]); // ==> [true, true, true]
// 如果我们要查找people中的type, 那么他就会逐层的向上查找 People.prototype ==> Person.prototype ==> Object.prototype一旦匹配,就停止查找了。也就是在原型链上查找
console.log(people.type) // ==> human
// 通过原型链实现的继承
let Father = {
name: 'father',
sayName: function(){
console.log(this.name)
}
}
let child = Object.create(Father)
child.name = 'child'
child.sayName() // child
// 实现一个new
function myNew (Constructor, ...args) {
/* // 生成新对象
let _obj = Object.create({})
// 链接到prototype
_obj.__proto__ = arguments[0].prototype
// 绑定 this 为当前创造的_obj
const realArgs = [...arguments].splice(1) // 除了构造函数以外的其他参数
let result = arguments[0].apply(_obj, realArgs)
// 返回新对象
return typeof result === 'object' ? result : _obj */
let _obj = Object.create({})
Object.setPrototypeOf(_obj, Constructor.prototype)
let result = Constructor.apply(_obj, args)
return result instanceof Object ? result : _obj
}
let personNew = myNew(Person,'personNew',2)
console.log(personNew.name) // ==> personNew
console.log(personNew.__proto__) // ==> {type: "human", constructor: Person}
// 性能相关
let person3 = new Person("name", 1111)
for (const key in person3) {
// hasOwnProperty 只查找直接属性
if (person3.hasOwnProperty(key)) {
console.log(`对象中的key: ${key}`) // name age sayType
} else {
console.log(`prototype中的key: ${key}`) // type
}
}
console.log(Object.keys(person3)) // ["name", "age", "sayType"] 排除prototype中的key
console.warn("------------------------------------这个是原型相关Demo end------------------------------------");
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
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
# 正则表达式
# Javascript API
> 这一部分主要是回顾目前基础的JavaScript各个模块的API 个人认为比较重要的是数组等部分
## Array.js ⭐⭐⭐⭐⭐
> 主要是Javascript 数组的大部分重要可维护的API 参考着MDN的文档然后夹杂着自己的理解 数组的话在我眼里它最重要的一点你是要区分各个方法的返回值,返回值弄明白了 才能区分一类功能相似的API 才能清晰的掌握大部分API
## 正则表达式知识整理 ⭐⭐⭐⭐⭐
<small>
`\` : 转义字符
`^ `: 匹配字符串开始位置
`$` : 匹配字符串结束位置
`* `: 匹配前面的表达式任意次
`+` : 匹配前面的表达式一次或多次
`? `: 匹配前面的表达式零次或一次
`{n}` : 匹配确定的n次,n为非负整数
`{n,}` : 匹配至少n次,n为非负整数
`{n,m}` : 匹配至少n次,至多m次,n和m均为非负整数且n<=m
`(*,+,{n,m})?` : 非贪婪匹配模式,尽可能少的匹配,例如“z+”可以匹配“zzzzz”,“z+?”只匹配到“z”
`.` : 匹配除\r\n外任何单个字符
`(pattern)` : 匹配pattern,并获取匹配,可以获取匹配到的结果,用\1-\9表示,例如“(o)”匹配到“o”,“(o)\1”匹配到“oo”,“(\d{3})\1”匹配到“123123”,不能匹配到“123456”
`(?:pattern)` : 非获取匹配,匹配pattern,但不获取匹配结果
`(?=pattern)` : 非获取匹配,正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串,该匹配不需要获取供以后使用,例如“test(?=123)”能匹配“test123”中的“test”,但不能匹配“test456”中的“test”
`(?!pattern)` : 非获取匹配,正向否定预查,在任何匹配pattern的字符串开始处匹配查找字符串,该匹配不需要获取供以后使用,例如“test(?=123)”能匹配“test456”中的“test”,但不能匹配“test123”中的“test”
`(?<=pattern)` : 非获取匹配,反向肯定预查,和正向肯定预查类似,方向相反,例如“(?<=123)test”能匹配“123test”中的“test”,但不能匹配“456test”中的“test”
`(?<!pattern)` : 非获取匹配,反向否定预查,和正向否定预查类似,方向相反,例如“(?<!123)test”能匹配“456test”中的“test”,但不能匹配“123test”中的“test”
`x|y` : 匹配x或y
`[xyz]` : 匹配包含的任意一个字符
`[^xyz]` : 匹配未包含的任意字符
`\b `: 匹配一个单词边界,例如“on\b”能匹配“location”中的“on”,不能匹配到“component”中的“on”
`\B` : 匹配非单词边界,例如“on\B”能匹配到“component”中的“on”,不能匹配到“location”中的“on”
`\d` : 匹配一个数字字符
`\D `: 匹配非数字字符
`\s` : 匹配任何不可见字符,相当于[\f\n\r\t\v]
`\S `: 匹配任何可见符,相当于[^\f\n\r\t\v]
</small>
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
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
# 正则表达式
/*
* @Author: mark
* @Date: 2018-05-01 15:04:03
* @Last Modified by: mark
* @Last Modified time: 2018-06-28 15:30:55
* 正则表达式专题
*/
/**
正则表达式表示的是一种规则。
*/
console.log('********************正则表达式begin***************************')
/**
* ⭐⭐⭐⭐⭐
* 转移字符专题
* \d 数字 [0-9]
* \w 数字,英文,下划线 [a-z0-9_]
* \s 空白字符
*
* \D 除了0-9 [^0-9]
* \W [^a-z0-9_]
* \S 非空白字符
*/
/**
* ⭐⭐⭐⭐⭐
* 常用量词
* {n} 正好出现n次 /\d{8}/
* {n,m} 最少n次最多m次
* {n,} 最少n次 /(0\d{2,3}-)?[1-9]\d{7}/
*
* + +号就相当于:{1,}
* ? ?相当于{0,1}0到1次(可有可无)
* * *相当于任意次 {0,}
*/
/**
* ⭐⭐⭐⭐⭐
* 正则写法
*/
//正则有两中写法
//1:第一种写法 JS风格
/**
* Reg(flag(正则表达式的文本),pattern(我自己称他为匹配模式:可传可不传)
* pattern:如果指定,可以是以下任意值的组合
* g:全局匹配,查找到所有的匹配,而不是在第一个匹配之后停止
* i:忽略大小写
* m:多行见开始(^)和结束($)视为在多行上工作(也就是分别匹配没一行的开始和结束,而不是值匹配整个输入字符串的最开始和最末尾处)
*
*/
let re_js = new RegExp('a','i')
//第二中写法 perl风格
/**
* 这个风格中的匹配模式直接在后面加上就行了
* let re_perl = /a/i
* 正则中可以使用简单的转义 这个是写在正则里面的
* 数字:\d
* .:代表得是任意字符 尽量不要用
* 量词:+ 值的是个数
* |:在正则中表示或
*/
let re_perl = /a/
let num_re = /\d+/g //转义为数字
//str.match() 把所有匹配的东西 全都提取出来
var str = 'asdho aoeu bsa7 bq321432gg54bb623'
console.log(str.match(num_re))
//str.replace(pattern,newStr) 用于替换字符串中的出现的符合正则的字符串为新字符串,注意:不适用正则的话匹配的是第一个 返回的是替换完的字符串
//过滤字符串
let rep_re = /背景|测试/g
let oldStr = '这就是一个背景的测试'
console.log(oldStr.replace(rep_re,'***'))
/**
* 正则中方括号:元字符
* [abc]指得是或得意思 意思就是abc中得三个字符随便取哪一个都行:其中任选一个即刻
* [a-z]/[0-9]:找出所有得字母/字符串 意思就是中括号还能表示一个范围
* [^a-z]:排除字母
* [^a-z0-9]:这个用法是匹配除了字母和数字意外得字符
*
*/
let huoStr = 'amn smn dmn ddm ddn ddi bmn cmn'
let huo_re = /[abc]mn/g
console.log(huoStr.match(huo_re))
//过滤html标签得正则
let html_re = /<[^<>]+>/g
let html_str = '<div>这是一个测试html标签过滤得正则<br></div>'
console.log(html_str.replace(html_re,''))
/**
正则表达式知识整理
\ : 转义字符
^ : 匹配字符串开始位置
$ : 匹配字符串结束位置
* : 匹配前面的表达式任意次
+ : 匹配前面的表达式一次或多次
? : 匹配前面的表达式零次或一次
{n} : 匹配确定的n次,n为非负整数
{n,} : 匹配至少n次,n为非负整数
{n,m} : 匹配至少n次,至多m次,n和m均为非负整数且n<=m
(*,+,{n,m})? : 非贪婪匹配模式,尽可能少的匹配,例如“z+”可以匹配“zzzzz”,“z+?”只匹配到“z”
. : 匹配除\r\n外任何单个字符
(pattern) : 匹配pattern,并获取匹配,可以获取匹配到的结果,用\1-\9表示,例如“(o)”匹配到“o”,“(o)\1”匹配到“oo”,“(\d{3})\1”匹配到“123123”,不能匹配到“123456”
(?:pattern) : 非获取匹配,匹配pattern,但不获取匹配结果
(?=pattern) : 非获取匹配,正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串,该匹配不需要获取供以后使用,例如“test(?=123)”能匹配“test123”中的“test”,但不能匹配“test456”中的“test”
(?!pattern) : 非获取匹配,正向否定预查,在任何匹配pattern的字符串开始处匹配查找字符串,该匹配不需要获取供以后使用,例如“test(?=123)”能匹配“test456”中的“test”,但不能匹配“test123”中的“test”
(?<=pattern) : 非获取匹配,反向肯定预查,和正向肯定预查类似,方向相反,例如“(?<=123)test”能匹配“123test”中的“test”,但不能匹配“456test”中的“test”
(?<!pattern) : 非获取匹配,反向否定预查,和正向否定预查类似,方向相反,例如“(?<!123)test”能匹配“456test”中的“test”,但不能匹配“123test”中的“test”
x|y : 匹配x或y
[xyz] : 匹配包含的任意一个字符
[^xyz] : 匹配未包含的任意字符
\b : 匹配一个单词边界,例如“on\b”能匹配“location”中的“on”,不能匹配到“component”中的“on”
\B : 匹配非单词边界,例如“on\B”能匹配到“component”中的“on”,不能匹配到“location”中的“on”
\d : 匹配一个数字字符
\D : 匹配非数字字符
\s : 匹配任何不可见字符,相当于[\f\n\r\t\v]
\S : 匹配任何可见符,相当于[^\f\n\r\t\v]
*/
console.log('********************正则表达式end***************************')
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
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
# STring API模块
/**
* STring API模块回顾
* MDN文档为主
* String具有两个属性
* String.prototype
* String.length
*/
let str = 'etfdemostring'//测试string
/**
* ⭐⭐⭐⭐⭐
* charAt(index) index是数字
* 从一个字符串中返回指定得字符
* @return 特定字符
*/
let c_str = str.charAt(1)
console.log(c_str)
/**
* ⭐⭐⭐
* concat(str1,str2) 用于连接字符串形成一个新的字符串返回不影响源字符串
* @return 返回新的字符串不影响之前字符串
*/
let old = 'woshi '
let newStr = old.concat(str)
console.log(newStr)
/**
* ⭐⭐⭐
* endsWith(searchString [, position])方法用来判断当前字符串是否是以另外一个给定的子字符串“结尾”的,
* startsWith(searchString [, position]) 方法用来判断当前字符串是否是以另外一个给定的子字符串“开头”的,position开始位置
* 根据判断结果返回 true 或 false。 searchString是要搜索得字符串 position是结束位置 默认是str.length
* @return true/fasle
*/
let s_str = "To be, or not to be, that is the question"
console.log(s_str.endsWith('question'))
/**
* ⭐⭐⭐ es6方法
* includes(searchString [, position]) 方法用于判断一个字符串是否包含在另一个字符串中,
* 根据情况返回true或false。 用法和endsWith类似 可选。position:从当前字符串的哪个索引位置开始搜寻子字符串;默认值为0。
* @return true/fasle
*/
console.log(s_str.includes('question',0))
/**
* ⭐⭐⭐⭐⭐
* indexOf(searchValue[, fromIndex])
* 获取string中第一次出现得指定值得索引 注意是指定值 找到返回值 未找到返回-1
* 注意:区分大小写 ,可以用来检测字符串是否存在
* @return number/-1
*/
console.log(s_str.indexOf('yes'))
/**
* ⭐⭐⭐
* lastIndexOf(searchValue[, fromIndex])
* 返回指定值在调用该方法得字符串中得最后出现的位置 没找到返回-1
* fromIndex默认是str.length
* @return number/-1
*/
console.log(s_str.lastIndexOf('1'))
/**
* ⭐
* link() 方法创建一个 <a> HTML 元素,用该字符串作为超链接的显示文本,参数作为指向另一个 URL 的超链接。
* 这个几乎是没有什么用处
* @return html
*/
var hotText = "MDN";
var URL = "https://developer.mozilla.org/";
document.write("Click to return to " + hotText.link(URL));
/**
*⭐⭐⭐
match(regexp) 当一个字符串与一个正则表达式匹配时, match()方法检索匹配项。
如果匹配成功返回数组,匹配失败返回null
*/
let m_str = 'For more information, see Chapter 3.4.5.1';
let re = /see (chapter \d+(\.\d)*)/i;
console.log(m_str.match(re))
/**
* ⭐⭐⭐
* padEnd(targetLength [, padString])
* padStart(targetLength [, padString])
* targetLength:当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身。
* 用于从结尾/开始填充字符串到目标长度padString要填充得字符串 如果填充过长则保留左边
* @returns 新的字符串
*/
console.log('abc'.padEnd(10, "foo"))
console.log('abc'.padStart(10, "foo"))
/**
* ⭐⭐⭐
*repeat(count)
*用于循环当前字符串 count为Number类型 当count小于0 则返回空 cout为循环得次数
*@return 返回新的字符串
*/
console.log('abc'.repeat(10))
/**
* ⭐⭐⭐⭐⭐
* replace(regexp|substr, newSubStr|function)
* 方法返回会一个由替换之后替换一些或者所有匹配模式后得字符串(返回新的字符串)
* 模式可以是一个字符串或者一个正正则表达式,替换之可以是一个字符串或者一个函数
* @return 返回一个新字符串而且原字符串不会改变!
* regexp (pattern)
一个RegExp 对象或者其字面量。该正则所匹配的内容会被第二个参数的返回值替换掉。
substr (pattern) 原字符串
一个要被 newSubStr 替换的字符串。其被视为一整个字符串,而不是一个正则表达式。仅仅是第一个匹配会被替换。
newSubStr (replacement) 需要替换的新字符串
用于替换掉第一个参数在原字符串中的匹配部分的字符串。该字符串中可以内插一些特殊的变量名。参考下面的使用字符串作为参数。
function (replacement)
一个用来创建新子字符串的函数,该函数的返回值将替换掉第一个参数匹配到的结果。参考下面的指定一个函数作为参数。
*/
function replacer(match, p1, p2, p3, offset, string) {
// p1 is nondigits, p2 digits, and p3 non-alphanumerics
return [p1, p2, p3].join(' - ');
}
let newString = 'abc12345#$*%'.replace(/([^\d]*)(\d*)([^\w]*)/, replacer);
console.log(newString); // abc - 12345 - #$*%
/**
* ⭐
* search(reg)
* 用于对字符串进行正则搜索
* @return number: 返回匹配字符串的索引/-1
*/
/**
* ⭐⭐⭐⭐⭐
* slice(beginSlice,endSlice) 参数负数是倒数位置
* 提取字符串的一部分并返回一个新的字符串
* beginSlice:开始位置
* endSlice:结束位置
* 注意: 在截取的时候包括begin但是不包括end 同样的要头不要尾
* @return 不会修改原来的字符串 返回一个新的字符串
*/
let sliceStr = str.slice(0,7);
console.log(sliceStr)
/**
* ⭐⭐⭐⭐⭐
* split(separator,limit)
* 将字符串以指定分隔符分割并且返回一个数组
* separator:分割符
* limit:限制分割的数量
* 注意: 不会修改原字符串 如果空字符串("")被用作分隔符,则字符串会在每个字符之间分割。
* 当字符串为空时,split()返回一个包含一个空字符串的数组,而不是一个空数组,如果字符串和分隔符都是空字符串,则返回一个空数组。
* @return 返回的是一个新数组
*/
let myString = "Hello World. How are you doing?";
let newSplit = myString.split(" ", 3);
let strSplit = myString.split('are');
console.log(newSplit)
console.log("这个是split传一个字符串的用法")
console.log(strSplit)
/**
* ⭐⭐⭐⭐⭐
* substr(start[, length])
* 方法返回一个字符串中从指定位置开始到指定字符数的字符。 和slice的区别就是第二个参数的不同
* substr 从 start 位置开始提取字符,提取 length 个字符(或直到字符串的末尾)。
* 如果 start 为正值,且大于或等于字符串的长度,则 substr 返回一个空字符串。
* 如果 length 为 0 或负值,则 substr 返回一个空字符串。如果忽略 length,则 substr 提取字符,直到字符串末尾。
* start:开始位置
* length:结束长度
* @return 修改后的源字符串
*/
myString.substr(2,3)
console.log(myString)
/**
* ⭐⭐⭐⭐⭐
* substring(indexStart,indexEnd)
* 返回一个字符串在开始索引和结束索引之间的一个子集,或从开始索引到字符串末尾的一个子集 通常配合length属性来使用
* 注意:和slice不同的是slice返回一个新字符串 和substr不同的是sustr第二个参数是长度 这个是索引
* 如果 indexStart 等于 indexEnd,substring 返回一个空字符串。
如果省略 indexEnd,substring 提取字符一直到字符串末尾。
如果任一参数小于 0 或为 NaN,则被当作 0。
如果任一参数大于 stringName.length,则被当作 stringName.length。
// 会自动的调整参数
如果 indexStart 大于 indexEnd,则 substring 的执行效果就像两个参数调换了一样。例如,str.substring(1, 0) == str.substring(0, 1)。
@return 修改后的源字符串
*/
let subStringStr = 'asdasd asdhakh'
console.log(subStringStr.substring(3,9));
//这个方法可以用来替换字符串
let replaceBySubstring = function(old,newS,full){
for(let i=0;i<full.length;i++){
if(full.substring(i,i+old.length) == old){
full = full.substring(0,i) + newS + full.substring(i + old.length, full.length)
}
}
}
replaceBySubstring("World", "Web", "Brave New World");
// 但是这个如果替换的字符串和新字符串一样会出现死循环 所以一般使用下面的方式 更多是时候使用replae()原生方法
let commonReplae = function(oldStr,newStr,fullStr){
return fullStr.split(oldStr).join(newStr)
}
/**
* ⭐⭐⭐⭐⭐
* toLowerCase():toLowerCase 会将调用该方法的字符串值转为小写形式,并返回。toLowerCase 不会影响字符串本身的值。 不会修改源字符串
* toUpperCase() 将调用该方法的字符串值转换为大写形式,并返回。
*
*/
/**
* ⭐⭐⭐
* str.toString()
* String对象的转string object会转成string
*/
let x = new String ('woshi zi fuchuan duixaing')
console.log(typeof(x))
console.log(typeof(x.toString()) + '是' + x.toString())
/**
* trim() 方法会从一个字符串的两端删除空白字符。
* 注意:返回的是一个新字符串,不影响源字符串
* @return 一个新的字符串
*/
/**
* ⭐⭐⭐⭐⭐
* raw(callSite, ...substitutions)
* callSite:一个模板字符串的“调用点对象”。类似{ raw: ['foo', 'bar', 'baz'] }。
* ...substitutions 任意个可选的参数 表示任意个内插表达式对应的值。
*
* @return 给定模板字符串的原始字面量值
*/
console.log(String.raw `Hi\n!`)
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# 算法专题
"use strict";
exports.__esModule = true;
/*
* @Author: mark
* @Date: 2018-05-03 21:57:01
* @Last Modified by: etf
* @Last Modified time: 2018-09-10 13:52:36
* 简单的算法专题
*/
console.warn(' ------------------------------------算法专题begin----------------------------------');
/**
* 交换参数
* @param {*} arr
* @param {*} a
* @param {*} b
*/
var swap = function (arr, a, b) {
var curr = arr[a];
arr[a] = arr[b];
arr[b] = curr;
};
/**
*
* @param {选择排序算法} arr
*/
var sort = function (arr) {
console.time();
for (var i = 0; i < arr.length; i++) {
//假设遍历的当前第一个是最小的
var minIndex = i;
//第二次遍历把arr[minIndex]和数组中的其他的值进行遍历
for (var j = 0; j < arr.length; j++) {
if (arr[minIndex] > arr[j]) {
minIndex = j;
}
}
//外层循环做交换
swap(arr, minIndex, i);
}
console.log(arr);
console.timeEnd();
};
sort([3, 6, 28, 123, 34]);
// 冒泡排序算法
/**
*
* @param {*冒泡排序算法} arr
*/
var bubbleSort = function (arr) {
console.log('冒泡算法开始时间:');
console.time();
for (var i = 0; i < arr.length; i++) {
// 这个循环时获取到之后的项进行比较
for (var j = i + 1; j > 0; j--) {
// 这个核心就是 如果当前项小于前一项那么当前项向上冒泡
if (arr[i] < arr[j - 1]) {
// 冒泡交换
swap(arr, j, j - 1);
}
}
}
console.timeEnd();
console.log(arr);
};
bubbleSort([3, 123, 6, 28, 34]);
//插入排序算法
/**
*
* @param {插入排序} arr
*/
var insertSort = function (arr) {
console.time();
for (var i = 0; i < arr.length; i++) {
// 在一次循环的时候首先缓存下来当前的值和上一个index 缓存上一个index用来比较
var compareIndex = i - 1;
var currentValue = arr[i];
// 在当前位置可以比较并且当前的值小于前一项的值的时候插入缓存的值然后修改index
while (compareIndex >= 0 && arr[compareIndex] > currentValue) {
arr[compareIndex + 1] = arr[compareIndex];
compareIndex--;
}
arr[compareIndex + 1] = currentValue;
}
console.timeEnd();
console.log(arr);
};
insertSort([3, 2, 1]);
/**
* 二分查找算法
* 什么叫二分查找? 二分查找也称为折半查找。是指在有序的数组里找出指定的值,返回该值在数组中的索引。
* (1)从有序数组的最中间元素开始查找,如果该元素正好是指定查找的值,则查找过程结束。否则进行下一步;
* (2)如果指定要查找的元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半区域查找,然后重复第一步的操作;
* (3)重复以上过程,直到找到目标元素的索引,查找成功;或者直到子数组为空,查找失败。
* 注意: 这个先要把数组排序一下 在有序数组中查找
* 优点是比较次数少,查找速度快,平均性能好;
* 其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列表。
*/
/**
* 非递归实现
* @param {*} arr
* @param {*} target
*/
function binarySearcNoRecursive(arr, target) {
var low = 0, high = arr.length - 1;
while (low <= high) {
// 首先找到中间位置
var middle = ((high + low) / 2);
if (target === arr[middle]) {
return middle;
}
else if (target > arr[middle]) {
low = middle + 1;
}
else if (target < arr[middle]) {
high = middle - 1;
}
else {
return -1;
}
}
}
var result = binarySearcNoRecursive([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 23, 44, 86], 23);
console.log("\u4E8C\u5206\u67E5\u627E\u4E0D\u7528\u5FAA\u73AF\u627E\u5230\u7684\u4F4D\u7F6E:" + result);
/**
* 递归实现 循环调用自身
* @param {*} arr
* @param {*} target
*/
function binarySearcRecursive(arr, low, high, target) {
if (low > high) {
return -1;
}
var middle = ((high + low) / 2);
if (arr[middle] === target) {
return middle;
}
else if (arr[middle] > target) {
high = middle - 1;
binarySearcRecursive(arr, low, high, target);
}
else if (arr[middle] < target) {
low = middle + 1;
binarySearcRecursive(arr, low, high, target);
}
}
var recursiveRes = binarySearcNoRecursive([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 23, 44, 86], 3);
console.log("\u4E8C\u5206\u67E5\u627E\u4E0D\u7528\u5FAA\u73AF\u627E\u5230\u7684\u4F4D\u7F6E:" + recursiveRes);
/**
* leet code入门级算法系列
*/
console.warn("leet code Array \u4E13\u9898\u5F00\u59CB");
/**
* leet code 算法专题
*/
/**
* 1 删除排序数组中的重复项
* 给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
* 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
* 示例
* 给定数组 nums = [1,1,2],
* 函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
* 你不需要考虑数组中超出新长度后面的元素。
*/
var removeDuplicates = function (nums) {
var i = 0;
for (var j = 0; j < nums.length; j++) {
if (nums[j] !== nums[i]) {
i++;
nums[i] = nums[j];
}
}
nums.splice(i + 1);
console.log(nums);
console.log(nums.length);
return i + 1;
};
/**
* 解析
* 方法 双指针法
* i是慢指针,j是快指针 当我们遇到 nums[j] \neq nums[i]nums[j]≠nums[i] 时,跳过重复项的运行已经结束,
* 因此我们必须把它(nums[j]nums[j])的值复制到 nums[i + 1]nums[i+1]。然后递增 ii,接着我们将再次重复相同的过程,直到 jj 到达数组的末尾为止。
* 复杂度分析:
* 时间复杂度: O(n) 假设数组长度是n 那么i和j最多就是遍历n步
* 空间复杂度: O(1)
*/
removeDuplicates([0, 0, 1, 1, 1, 2, 2, 3, 3, 4]);
/**
* 2: 买卖股票的最佳时机
* 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
* 设计一个算法来计算你所能获取的最大利润。你最多可以完成一次交易
* 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)
*
* 输入: [7,1,5,3,6,4]
* 输出: 7
* 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
* 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
*/
var maxProfit = function (prices) {
if (prices.length < 2)
return 0;
// 定义利润
var count = 0;
var PreMin = prices[0];
// 获取最大的单天利润
for (var i = 0; i < prices.length; i++) {
count = Math.max(count, prices[i] - PreMin);
PreMin = Math.min(PreMin, prices[i]);
}
console.log(count);
return count;
};
/**
* 解析: 贪心算法
*/
console.log('=================股票最佳购买时机贪心算法===================');
console.log(maxProfit([7, 1, 5, 3, 6, 4]));
console.log('====================================');
/**
* 3: 买卖股票的最佳时机
* 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
* 设计一个算法来计算你所能获取的最大利润。你可以尽可能多的完成交易
* 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)
*/
var maxProfitMore = function (prices) {
if (prices.length < 2)
return 0;
var ret = 0;
for (var i = 0; i < prices.length; i++) {
if (prices[i + 1] > prices[i]) {
ret += prices[i + 1] - prices[i];
}
}
return ret;
};
/**
* 解析: 非贪心算法
* 只要下一天的价钱 大于今天的价钱 那我们就卖出当前天的 最终的结果就是我们的利润总和
*/
console.log('==================股票最佳购买时机非贪心算法==================');
console.log(maxProfitMore([7, 1, 5, 8, 3, 6, 4]));
console.log('=============================================');
/**
* 4: 给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。
* 要求O(1)的空间复杂度,对原数组进行操作
*
*/
var rotate = function (nums, k) {
// 循环k,通过k我们可以确定需要移动的次数
for (var i = 0; i < k; i++) {
// 先在前面插入我们需要移动的地方
nums.unshift(nums[nums.length - 1 - i]);
}
// 最后再去处理我们的数组
nums.splice(nums.length - k, k);
};
rotate([1, 2, 3, 4, 5, 6, 7], 3);
/**
* 5: 存在重复
* 给定一个整数数组,判断是否存在重复元素。
* 如果任何值在数组中出现至少两次,函数返回 true。如果数组中每个元素都不相同,则返回 false。
*
* 这个一定不是最优解
*/
var containsDuplicate = function (nums) {
// 设置flag
var judge = false;
// 容错判断
if (nums.length <= 1) {
return judge;
}
// 通过最简单直白的去重的思想去处理
var current = [];
for (var i = 0; i < nums.length; i++) {
if (current.indexOf(nums[i]) === -1) {
current.push(nums[i]);
}
else {
return judge = true;
}
}
return judge;
};
console.log('================是否存在重复算法====================');
console.log(containsDuplicate([3, 1]));
console.log('====================================');
// 这个其实是非常常见而且简单得一个算法 但是要考虑到得情况多一点
/**
* 6: 只出现一次得数字
* 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
* 你的算法应该具有线性时间复杂度。 不使用额外空间来实现
*/
var singleNumber = function (nums) {
var index = -1;
// 双层进行比对 目的是当前key和数组中的每一个key进行比较
nums.forEach(function (key, j) {
//每次循环小游标
var count = 0;
for (var k = 0; k < nums.length; k++) {
if (key === nums[k]) {
count += 1;
}
// 循环完找出符合条件的下标
if (k === nums.length - 1 && count === 1) {
index = j;
}
}
});
return nums[index];
};
console.log('=================查找单独数算法===================');
console.log(singleNumber([2, 2, 1, 3, 3]));
console.log('====================================');
/**
* 7:求两个数组的交集
*/
var intersect = function (nums1, nums2) {
var arr = [];
for (var i = 0; i < nums1.length; i++) {
if (nums2.indexOf(nums1[i]) !== -1) {
nums2.splice(nums2.indexOf(nums1[i]), 1);
arr.push(nums1[i]);
}
}
return arr;
};
/**
* 解析: 在求交集的过程中。主要的思想是关于什么是交集。
* 两个数组的重合部分理论上来讲就是交集
* 循环其中一个数组nums1在后在另外一个数组nums2中比对是否出现,如果出现的话就删除nums2中出现过的数组(注意是修改nums2)
*/
intersect([1, 2, 2, 1], [2, 2]);
/**
* 8:加1
* 给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。
* 你可以假设除了整数 0 之外,这个整数不会以零开头。
*/
var plusOne = function (nums) {
var j = nums.length - 1;
// js无法正常表示大于16位的整数【非科学计数】
for (var i = nums.length - 1; i >= 0; i--) {
// 开始逐个进行加法运算
if (i == j) {
// 大于10的情况
if (nums[i] + 1 >= 10) {
nums[i] = nums[i] + 1 - 10;
j--;
// 最后一次循环
if (i === 0) {
nums.unshift(1);
}
}
else {
nums[j]++;
}
}
else {
break;
}
}
console.log(nums);
return nums;
};
/**
* 解析: 如果使用太大的数的话转成数字再加1是不行的,我们需要对数组中的的单个数据进行运算,同样的是以辅助游标进行运算
* 辅助游标j的主要作用是记录需要+1的位置,如果当前的下标不等于j那么就跳出了循环:同时也提高了性能
*/
console.log('================加1算法====================');
console.log(plusOne([8, 2, 1, , 1, 2, 2, 2, 3, 5, 5, 5, 5, 5, 2, 3, 4, 2, 3, 4, 5, 5, 5, 5, 2, 9]));
console.log('====================================');
/**
* 9: 移动零
* 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
*
* 输入: [0,1,0,3,12]
* 输出: [1,3,12,0,0]
*/
var moveZeroes = function (nums) {
// 0出现的个数
var j = 0;
nums.forEach(function (el, index, arr) {
if (nums[j] === 0) {
nums.splice(j, 1);
nums.push(0);
}
else {
j++;
}
});
console.log(nums);
};
/**
* 解析: 新建一个小游标j 这个是用来标识0出现的地方,每次移动完之后小游标是不变化的,因为原数组已经修改所以要固定一下游标
* 双游标法在算法真的很实用
*/
console.log('==================移动零算法==================');
moveZeroes([1, 2, 0, 0, 0, 1]);
console.log('====================================');
/**
* 10: 找父亲节点
* fid为0代表一级,fid如果和fid为0的cid相等的话代表二级 以此类推...
*/
var findArr = [
{ "fid": 0, "cid": 3, "flag": "最外层3" },
{ "fid": 0, "cid": 4, "flag": "最外层4" },
{ "fid": 4, "cid": 5, "flag": "最外层-4" },
{ "fid": 5, "cid": 6, "flag": "最外层-4-1" },
{ "fid": 0, "cid": 7, "flag": "最外层7" },
{ "fid": 7, "cid": 8, "flag": "最外层-7" },
{ "fid": 0, "cid": 9, "flag": "最外层9" },
{ "fid": 9, "cid": 10, "flag": "最外层9-1" },
{ "fid": 9, "cid": 11, "flag": "最外层9-2" },
{ "fid": 11, "cid": 12, "flag": "最外层9-2-1" }
];
/**
* 第一种方法:双递归方式
* @param arr
*/
var findfid = function (arr) {
var newArr = [];
for (var i = 0; i < arr.length; i++) {
var flagId = arr[i].cid; // 取出来一个flag 这个是用于和下一个级别匹配的
for (var j = 0; j < arr.length; j++) {
var elJ = arr[j];
if (elJ.fid === flagId) { // fid 和 上级id 匹配
(arr[i].children = []).push(elJ);
}
}
// 只存入第一等级
arr[i].fid === 0 && newArr.push(arr[i]);
}
return newArr;
};
/**
* 第二种方法: 使用对象存储id 然后和fid进行对比
* @param arr
*/
var findfidByObj = function (arr) {
var newArr = [];
var flagObj = {};
arr.forEach(function (v) {
flagObj[v.cid] = v;
});
arr.forEach(function (item) {
// 根据当前遍历对象的fid,去map对象中找到对应索引的id
var top = flagObj[item.fid];
if (top) {
// 如果找到索引,那么说明此项不在顶级当中,那么需要把此项添加到,他对应的父级中
(top.children || (top.children = [])).push(item);
}
else {
// 如果没有在map中找到对应的索引ID,那么直接把当前的item添加到newData结果集中作为顶级
newArr.push(item);
}
});
return newArr;
};
console.log('====================================');
console.log('找父亲节点方式');
console.log(findfid(findArr));
console.log(findfidByObj(findArr));
console.log('====================================');
/**
* 11:两数之和
* 给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。
* 你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。
* 例如:
* 给定 nums = [2, 7, 11, 15], target = 9
* 因为 nums[0] + nums[1] = 2 + 7 = 9
* 所以返回 [0, 1]
*/
/**
* 第一种解法
* @param nums
* @param target
*/
var twoSumA = function (nums, target) {
console.log('两数求和第一种解法');
var arr = [0, 0], flag = false;
for (var i = 0; i < nums.length; i++) {
compare(i);
if (flag) {
arr = [i, compare(i)];
break;
}
}
/**
* @param num
*/
function compare(index) {
for (var j = 0; j < nums.length; j++) {
if (j !== index && nums[index] + nums[j] === target) {
flag = true;
return j;
}
}
}
return arr;
};
/**
* 第二种解法
*/
var twoSumB = function (nums, target) {
var arr = [0, 0];
console.log('两数求和第二种解法');
for (var i = 0; i < nums.length; i++) {
for (var j = 0; j < nums.length; j++) {
if (j !== i && nums[i] + nums[j] === target) {
return arr = [i, j];
}
}
}
return arr;
};
// 在进行一个数组中两个数得比较中:一定要注意在相加得时候要排除自身去进行相加。
console.log('=================两数之和算法===================');
console.log(twoSumA([3, 2, 4], 6));
console.log(twoSumB([2, 7, 11, 15], 9));
console.log('====================================');
/**
* 12:有效得数独
* 判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
* 1:数字 1-9 在每一行只能出现一次。
* 2:数字 1-9 在每一列只能出现一次
* 3:数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
* 例如
* 输入
* [
["5","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]
输出: true
*/
var board = /* [
["5","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]*/ [["7", ".", ".", ".", "4", ".", ".", ".", "."],
[".", ".", ".", "8", "6", "5", ".", ".", "."],
[".", "1", ".", "2", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", "9", ".", ".", "."],
[".", ".", ".", ".", "5", ".", "5", ".", "."],
[".", ".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", "2", ".", "."],
[".", ".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", ".", "."]
];
var isValidSudoku = function (board) {
var isPass = true;
var sudokuDeep = 9; // 数独深度
// 判断行和列
for (var i = 0; i < sudokuDeep; i++) {
var row = {};
var col = {};
for (var j = 0; j < sudokuDeep; j++) {
// 判断行
/**
* 判断方式
* 首先判断不为. => 判断是否存在row对象中
*/
if (board[i][j] !== '.') {
if (row[board[i][j]]) {
console.log(board[i][j]);
return isPass = false;
}
else {
row[board[i][j]] = board[i][j];
}
}
// 判断列
if (board[j][i] !== '.') {
if (col[board[j][i]]) {
console.log(board[j][i]);
return isPass = false;
}
else {
col[board[j][i]] = board[j][i];
}
}
// 判断九宫格 通过余数的形式判断出来当前所处的9宫格
// 先计算出位置
var c = Math.floor(i / 3); // col位置
var r = Math.floor(j / 3); // row 位置
// 勾勒出九宫格
for (var m = c * 3; m < c * 3 + 3; m++) {
for (var n = r * 3; n < r * 3 + 3; n++) {
if (m === i && n === j) {
// m === i && n === j 这时指向同一个位置
continue;
}
else {
// 最重要的一次求值判断
if (board[m][n] != '.' && board[i][j] !== '.' && (board[i][j]) === board[m][n]) {
return isPass = false;
}
}
}
}
}
}
return isPass;
};
console.log('=================有效数独算法结果===================');
console.log(isValidSudoku(board));
console.log('====================================');
/**
* 13: 旋转图像
* 给定一个 n × n 的二维矩阵表示一个图像。
* 将图像顺时针旋转 90 度。
* 说明:
* ⭐:你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。
* 示例:
* 给定:
* matrix = [
[1,2,3],
[4,5,6],
[7,8,9]
* ]
* 旋转完
* matrix = [
[7,4,1],
[8,5,2],
[9,6,3]
* ]
*/
var matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
//
/* const matrix = [
[ 5, 1, 9,11],
[ 2, 4, 8,10],
[13, 3, 6, 7],
[15,14,12,16]
] */
var rotateMaps = function (matrix) {
var n = matrix.length;
// 倒叙循环进行90度的反转
for (var i = n - 1; i >= 0; i--) {
// 新数组补位到原数组中,为了是实现原地的旋转操作,如果不需要
for (var j = 0; j < n; j++) {
// console.log(`当前坐标[${i},${j}]`)
var current = matrix[i][j];
matrix[j].push(current);
// 没完成一组的赋值操作,就删除旋转前数组
if (j === n - 1) {
matrix[i].splice(0, n);
}
}
}
console.log(matrix);
// return matrix
};
console.log('================旋转图像算法====================');
console.log(rotateMaps(matrix));
console.log('====================================');
console.warn("leet code Array \u4E13\u9898\u7ED3\u675F");
require("./Algorithm_str.ts");
console.warn(' ------------------------------------算法专题end----------------------------------');
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
# 日常积累
/**
* 平时问题的积累
*/
//2017年8月14日17:46:07
/**
* target,currentTarget和this三者的区别
*
* target在事件流的目标阶段;currentTarget在事件流的捕获,目标及冒泡阶段。只有当事件流处在目标阶段的时候,两个的指向才是一样的, 而当处于捕获和冒泡阶段的时候,target指向被单击的对象而currentTarget指向当前事件活动的对象(注册该事件的对象)(一般为父级)。this指向永远和currentTarget指向一致(只考虑this的普通函数调用)。
*/
/* let get = function(id) {
return document.getElementById(id);
} */
let get = (id) => document.getElementById(id)
//兼容的事件处理
let addEvent = function(obj, ev, handler) {
if (window.attachEvent) {
obj.attachEvent("on" + ev, handler);
} else if (window.addEventListener) {
obj.addEventListener(ev, handler, false);
}
}
//测试
let test = function(e) {
alert("e.target.tagName : " + e.target.tagName + "\n e.currentTarget.tagName : " + e.currentTarget.tagName);
}
let outer = get("outer");
let inner = get('inner');
//addEvent(inner, "click", test);
addEvent(outer, "click", test);
/**
* 事件委托
* 概述:
那什么叫事件委托呢?它还有一个名字叫事件代理,JavaScript高级程序设计上讲:事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。那这是什么意思呢?网上的各位大牛们讲事件委托基本上都用了同一个例子,就是取快递来解释这个现象,我仔细揣摩了一下,这个例子还真是恰当,我就不去想别的例子来解释了,借花献佛,我摘过来,大家认真领会一下事件委托到底是一个什么原理:
有三个同事预计会在周一收到快递。为签收快递,有两种办法:一是三个人在公司门口等快递;二是委托给前台MM代为签收。现实当中,我们大都采用委托的方案(公司也不会容忍那么多员工站在门口就为了等快递)。前台MM收到快递后,她会判断收件人是谁,然后按照收件人的要求签收,甚至代为付款。这种方案还有一个优势,那就是即使公司里来了新员工(不管多少),前台MM也会在收到寄给新员工的快递后核实并代为签收。
这里其实还有2层意思的:
第一,现在委托前台的同事是可以代为签收的,即程序中的现有的dom节点是有事件的;
第二,新员工也是可以被前台MM代为签收的,即程序中新添加的dom节点也是有事件的。
为什么要用事件委托:
一般来说,dom需要有事件处理程序,我们都会直接给它设事件处理程序就好了,那如果是很多的dom需要添加事件处理呢?比如我们有100个li,每个li都有相同的click点击事件,可能我们会用for循环的方法,来遍历所有的li,然后给它们添加事件,那这么做会存在什么影响呢?
在JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能,因为需要不断的与dom节点进行交互,访问dom的次数越多,引起浏览器重绘与重排的次数也就越多,就会延长整个页面的交互就绪时间,这就是为什么性能优化的主要思想之一就是减少DOM操作的原因;如果要用事件委托,就会将所有的操作放到js程序里面,与dom的操作就只需要交互一次,这样就能大大的减少与dom的交互次数,提高性能;
每个函数都是一个对象,是对象就会占用内存,对象越多,内存占用率就越大,自然性能就越差了(内存不够用,是硬伤,哈哈),比如上面的100个li,就要占用100个内存空间,如果是1000个,10000个呢,那只能说呵呵了,如果用事件委托,那么我们就可以只对它的父级(如果只有一个父级)这一个对象进行操作,这样我们就需要一个内存空间就够了,是不是省了很多,自然性能就会更好。
事件委托的原理:
事件委托是利用事件的冒泡原理来实现的,何为事件冒泡呢?就是事件从最深的节点开始,然后逐步向上传播事件,举个例子:页面上有这么一个节点树,div>ul>li>a;比如给最里面的a加一个click点击事件,那么这个事件就会一层一层的往外执行,执行顺序a>li>ul>div,有这样一个机制,那么我们给最外面的div加点击事件,那么里面的ul,li,a做点击事件的时候,都会冒泡到最外层的div上,所以都会触发,这就是事件委托,委托它们父级代为执行事件。
*/
/**
* 用事件委托来写 优化减少资源消耗 用事件委托就可以只用一次dom操作就能完成所有的效果,比上面的性能肯定是要好一些的
*/
let oBox = get('box');
oBox.onclick = function(event) {
event = event || window.event;
let target = event.target || event.srcElement;
if (target.nodeName.toLocaleLowerCase() == 'input') {
switch (target.id) {
case 'add':
alert('添加');
break;
case 'remove':
alert('删除');
break;
case 'move':
alert('移动');
break;
case 'select':
alert('选择');
break;
}
}
}
/**
* 现在讲的都是document加载完成的现有dom节点下的操作,那么如果是新增的节点,新增的节点会有事件吗? 有可以有但是消耗就很大了
*/
let oBtn = document.getElementById("btn");
let oUl = document.getElementById("ul1");
let aLi = oUl.getElementsByTagName('li');
let num = 4;
//事件委托,添加的子元素也有事件
oUl.onmouseover = function(ev) {
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if (target.nodeName.toLowerCase() == 'li') {
target.style.background = "red";
}
};
oUl.onmouseout = function(ev) {
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if (target.nodeName.toLowerCase() == 'li') {
target.style.background = "#fff";
}
};
//添加新节点
oBtn.onclick = function() {
num++;
var oLi = document.createElement('li');
oLi.innerHTML = 111 * num;
oUl.appendChild(oLi);
};
/**
* 看,上面是用事件委托的方式,新添加的子元素是带有事件效果的,我们可以发现,当用事件委托的时候,根本就不需要去遍历元素的子节点,只需要给父级元素添加事件就好了,其他的都是在js里面的执行,这样可以大大的减少dom操作,这才是事件委托的精髓所在。
*
*
* 那什么样的事件可以用事件委托,什么样的事件不可以用呢?
适合用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress。
值得注意的是,mouseover和mouseout虽然也有事件冒泡,但是处理它们的时候需要特别的注意,因为需要经常计算它们的位置,处理起来不太容易。
不适合的就有很多了,举个例子,mousemove,每次都要计算它的位置,非常不好把控,在不如说focus,blur之类的,本身就没用冒泡的特性,自然就不能用事件委托了。
*/
/**
* 2017年8月16日17:00:52
*/
/**
* 闭包到底是什么
* 在理解闭包之前,务必要理解执行上下文
* 执行上下文:
* 在运行JavaScript代码的时候,它的运行环境是非常重要的,运行环境可能是如下几种中的一种:
* 全局代码:首次执行代码的默认环境
* 函数代码:每当执行流程进入函数体的时候
* 我们将执行上下文定义当前代码的执行环境或者是作用域
*
* 换句话说,当我们启动程序的时候,我们从全局执行上下文开始。我们在全局执行上下文中声明一些变量,这些都是全局变量。当程序调用函数的时候,会发生以下几个步骤:
* 1:Javascript创建按一个新的本地执行上下文。
* 2:本地执行上下文将拥有子集的变量集。
* 3:新的执行上下文被抛到执行栈上。我们可以将执行栈是为一种用于跟踪程序执行位置的机制。
*
* 函数会在遇到 return 语句或结束括号}时结束执行,并发生以下情况:
* 1:本地执行上下文从执行栈中跳出。
* 2:函数将返回值发送给调用上下文。调用上下文是调用此函数的执行上下文,它可以是全局执行上下文或是另一个本地执行上下文。掉偶用上下文将负责处理返回值。返回值可以是对象,数组,函数,布尔值或者是其他任何东西。如果函数没有return语句,
* 则返回undefined
* 3:本地执行上下文被销毁,这个很重要。在本地执行上下文中声明的所有的变量都将被删除,它们不可再用,这就是问什么它们被称为局部变量。
*
*
* 一个函数可以访问在其调用上下文中定义的变量,这种现象的正式名称是词法作用域。
* 下面是一个基础的闭包
*/
function createCounter () {
let count = 0
const myFunction = function() {
count = count + 1
return count
}
return myFunction
}
const increment = createCounter()
const c1 = increment()
const c2 = increment()
const c3 = increment()
console.log(increment,c1,c2,c3);
/**
* 完整的解释
* 1:第1-8行,我们在全局执行上下文中创建了一个新的变量createCounter,它包含一个函数定义。
* 2:第9行,我们在全局执行上下文中声明一个名为increment的新变量。
* 3:第9行,我们调用createCounter函数并返回赋值给increment变量。
* 4:第1-8行,调用函数,创建新的本地执行上下文。
* 5:第2行,在本地执行上下文中声明了一个变量count并赋值为0
* 6:第3-6行,在本地执行上下文中声明名为myFunction的新变量。变量的内容是另一个函数的定义。即第4,5行所定义的内容
* 7:第七行,返回myFunction变量的内容,删除本地执行上下本。myFunction和counter不再存在,控制权返回到调用上下文。所以我们返回函数定义极其闭包,比保重包含创建函数时声明的变量。
* 8:第9行,在调用上下文(全局执行上下文)中,createCounter
* 9:第 10 行,声明一个新变量(c1)。
* 10:第10行,查找变量increment,发现它是一个函数,调用它,它包含之前返回的函数定义,也就是4-5行所定义的内容(还有一个带变量的闭包)
* 11:创建新得执行上下文,没有参数,开始执行这个函数
* 12:第4行,count = count+1 此时我们需要寻找count变量。在查看本地或者全局执行上下文之前,先让我们来看看闭包。请注意:闭包包含一个名为count得变量,值是0.再第4行表达式之后
* ,它得值被设置为1,然后再次保存在闭包中。闭包现在包含了值为1得变量count。
* 13:第5行,我们返回count得值或者数值1,销毁本地执行上下文。
* 14:返回第10行,返回值(1)被分配给c1
* 15:第11行,我们重复步骤11到14.这次我们可以看到变量count得值为1,这个值是在第4行代码中设置得。它得值加一,并在increment函数得闭包中存为2.c2被赋值为2
* 16:第 12 行,我们重复步骤 10-14,c3 被设为 3。
* 17:第 13 行,我们记录变量 c1、c2 和 c3 的内容。
*/
//闭包的工作原理:当声明一个函数时,它包含一个函数定义和一个闭包。闭包是函数创建时声明的变量的集合。
/**
* 在全局范围中创建的函数也会创建一个闭包。但是由于这些函数是在全局范围内创建的,所以全局范围内的变量也就都可以访问了,无所谓闭包不必闭包了。
*
* ⭐⭐⭐⭐⭐: 当一个函数返回另一个函数的时候才会真正的涉及闭包。返回的函数可以访问仅存在于闭包中的变量。
* 按照背包的理论来理解的话:
* 当创建和传递一个函数或将其从另一个函数返回时,这个函数就带有一个背包,背包中就是所有在创建函数时声明的变量。
*/
/**
* 1,理解闭包
* 先看下官方的解释:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
* 看另外一种解释:
* JavaScript闭包就是在另一个作用域中保存了一份它从上一级函数或者作用域得到的变量,而这些变量是不会随上一级函数的执行完成而销毁。
* 闭包就是在函数内对外部作用域上的变量引用,使其常驻内存中,得不到释放。(当然这只是闭包的表现)
*
* 闭包:
* 1:现象
* 2:产生
**/
function funTest() {
let tempNum = 100; //私有变量
//在函数funcTest内
//定义另外的函数作为funcTest的方法函数
function innerFuncTest() {
//引用外层函数funcTest的临时变量tmpNum
console.log("我就是闭包部分输出的值" + tempNum);
}
//
return innerFuncTest; //返回内部函数
}
//调用函数
var myFuncTest = funTest()
myFuncTest() //弹出100
/**
* 在函数体内定义另外的函数作为目标对象的方法函数(示例中就是在函数funcTest内定义另外的函数innerFuncTest作为funcTest的方法函数),而这个对象的方法函数反过来引用外层函数体中的临时变量(闭包是一种间接保持变量值的机制。示例中就是内部函数innerFuncTest引用外层函数funcTest的临时变量tmpNum,这里必须注意,临时变量可以包括外部函数中声明的所有局部变量、参数和声明的其他内部函数)。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包(示例中,调用函数的时候,myFuncTest实际调用的是innerFuncTest函数,也就是说funcTest的一个内部函数innerFuncTest在funcTest之外被调用,这时就创建了一个闭包)。
*/
//2、闭包的例子
/**
* (1)因为闭包产生的问题
* 上面的HTML标记片断中有4个<a>元素,现在要给后三个指定事件处理程序,使它们在用户单击时报告自己在页面中的顺序,比如:当用户单击第2个链接时,报告“您单击的是第1个链接” 。为此,如果编写下列为后三个链接添加事件处理程序的函数:
复制代码
*/
/* let badClosureExample = function() {
var as = document.querySelectorAll('a');
for (var i = 0; i < 4; i++) {
//匿名函数无法传递参数
as[i].onclick = function() {
alert('单击第' + i + '个');
}
}
}
badClosureExample(); */
/**
* 分析:因为在badClosureExample()函数中指定给element.onclick的事件处理程序,也就是onclick那个匿名函数是在badClosureExample()函数运行完成后(用户单击链接时)才被调用的。而调用时,需要对变量i求值,解析程序首先会在事件处理程序内部查找,但i没有定义。然后,又到 badClosureExample()函数中查找,此时有定义,但i的值是4(只有i大于4才会停止执行for循环)。因此,就会取得该值——这正是闭包(匿名函数)要使用其外部函(badClosureExample)作用域中变量的结果。而且,这也是由于匿名函数本身无法传递参数(故而无法维护自己的作用域)造成的
*/
//解决方法
/* let popNum = function(oNum) {
return function() {
alert('单击第' + oNum + '个');
}
}
let badClosureExample = function() {
var as = document.querySelectorAll('a');
for (var i = 0; i < 4; i++) {
as[i].onclick = new popNum(i);
}
} */
/**
* (2)、巧妙利用闭包绑定参数
*/
let badClosureExample = function() {
var as = document.querySelectorAll('a');
for (var i = 0; i < 4; i++) {
//as[i].onclick = new popNum(i);
(function(i) {
as[i].onclick = function() {
alert('单击第' + i + '个');
}
})(i)
}
}
badClosureExample();
/**
* 3、javascript的垃圾回收原理
* (1)、在javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收;
* (2)、如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。在js中使用闭包,往往会给javascript的垃圾回收器制造难题。尤其是遇到对象间复杂的循环引用时,垃圾回收的判断逻辑非常复杂,搞不好就有内存泄漏的危险。
其实,闭包就是读取scope罢了。
*/
/**
* 2017年8月16日11:05:07
* JS闭包可被利用的常见场景
*/
/**
* 场景一:采用函数引用方式的setTimeout调用
*
* 闭包的一个通常的用法是为一个在某一函数执行前先执行的函数提供参数。例如,在web环境中,一个函数作为setTimeout函数调用的第一个参数,是一种很常见的应用。
setTimeout将要执行的函数(或者一段javascript代码,但这不是我们要讨论的情况)作为它的第一个参数,下一个参数是需要延迟执行的时间。如果一段代码想通过setTimeout来调用,那么它需要传递一个函数对象的引用来作为第一个参数。延迟的毫秒数作为第二个参数,但这个函数对象的引用无法为将要被延迟执行的对象提供参数。
但是,可以调用另一个函数来返回一个内部函数的调用,将那个内部函数对象的引用传递给setTimeout函数。内部函数执行时需要的参数,在调用外部函数时传递给它。setTimeout在执行内部函数时无需传递参数,因为内部函数仍然能够访问外部函数调用时提供的参数:
*/
function callLater(parmA, parmB, parmC) {
/*使用函数表达式创建并放回一个匿名内部函数的引用*/
return (function() {
/*
这个内部函数将被setTimeout函数执行;
并且当它被执行时,
它能够访问并操作外部函数传递过来的参数
*/
parmA[parmB] = parmC;
});
}
/*
调用这个函数将在它的执行上下文中创建,并最终返回内部函数对象的引用
传递过来的参数,内部函数在最终被执行时,将使用外部函数的参数
返回的引用被赋予了一个变量
*/
//let funRef = callLater(elStyle, "display", "none");
//hideMenu = setTimeout(funRef, 500);
/**
* 场景二:将函数关联到对象的实例方法
* 有很多这样的场景:需要分配一个函数对象的引用,以便在未来的某个时间执行该函数。那么闭包对于为这个将要执行的函数提供引用会非常有帮助。因为该函数可能直到执行时才能够被访问。
* 有一个例子就是,一个javascript对象被封装用来参与一个特定DOM元素的交互。它有doOnClick、doMouseOver以及doMouseOut方法。并且想在DOM元素上对应的事件被触发时执行这些方法。但是,可能会有关联着DOM元素的任意数量的javascript对象被创建,并且单个的实例并不知道那些实例化它们的代码将如何处理它们。对象实例不知道怎样去“全局”地引用它们自己,因为它们不知道哪个全局变量(如果存在)的引用将被分配给它们。
*
* 所以,问题是执行一个与特定javascript对象实例关联的事件处理函数,并且知道调用那个对象的哪个方法。
*
* 接下来的一个例子,在有元素事件处理的对象实例的关联函数上使用一个简单的闭包。通过传递event对象以及要关联元素的一个引用,为事件处理器分配不同的对象实例方法以供调用。
*/
/**
* 一个对象关联一个事件处理器的普通方法
* 返回的内部函数被作为事件的处理器
* 对象实例作为obj参数,对象上将要被调用的方法名称被作为第二个参数
*/
function associateObjWithEvent(obj, methodName) {
/*返回的内部函数被用来作为一个DOM元素的事件处理器*/
return (function(e) {
/*
事件对象在DOM标准的浏览器中将被转换为e参数,
如果没有传递参数给事件处理内部函数,将统一处理成IE的事件对象
*/
e = e || window.event;
/*
事件处理器调用obj对象上的以methodName字符串标识的方法
并传递两个对象:通用的事件对象,事件处理器被订阅的元素的引用
这里this参数能够使用,因为内部函数已经被执行作为事件处理器所在元素的一个方法
*/
return obj[methodName](e, this);
});
}
/*
这个构造器函数,通过将元素的ID作为字符串参数传递进来,
来创建将自身关联到DOM元素上的对象,
对象实例想在对应的元素触发onclick、onmouseover、onmouseout事件时
对应的方法被调用。
*/
function DhtmlObject(elementId) {
/**
* 调用一个方法来获取DOM元素的引用 如果没找到返回null
*/
let el = document.getElementById(elementId);
/**
* 因为if语句块,el变量的值在内部进行了类型转换,变成了boolean类型
所以当它指向一个对象,结果就为true,如果为null则为false
*/
if (el) {
el.onclick = associateObjWithEvent(this, "onDoClick");
el.onmouseenter = associateObjWithEvent(this, "doOnMouseEnter");
el.onmouseover = associateObjWithEvent(this, "doOnMouseOver");
}
}
DhtmlObject.prototype.onDoClick = function(event, element) {
/**
* 任何DhtmlObject的实例都能够将它们自身关联到它们感兴趣的DOM元素上去,不需要去担心这些元素将被其他的代码怎么处理,以及被全局命名空间“污染”或者与其他的DhtmlObject的实例产生冲突。
*/
}
/**
* 场景三:封装相关的功能集
*
* 闭包可以创建额外的scope,这可以被用来组合相关的或有依赖性的代码。用这种方式可以最大限度地减少代码干扰的危害。假设,一个函数被用来创建一个字符串并且避免重复串联的操作(比如创建一系列的中间字符串)。一个想法是,用一个数组来顺序存储字符串的一部分,然后使用Array.prototype.join方法输出结果(使用一个空字符串作为它的参数)。数组将扮演着输出缓冲区的角色,但局部定义它又将会导致它在函数的每次执行时再次创建。如果这个数组只是作为唯一的变量被分配给每一个函数调用,这将会有点小题大做。
一个解决方案是将数组提升为全局变量,让它不需要被再次创建也能够再次使用。但结果并不是想的那么简单,另外,一个全局变量关联这使用缓冲数组的函数,那将会有第二个全局属性(函数本身也是window对象的属性)关联这个数组,这将让代码失去一定的可控性。因为如果将它使用在其他地方。这段代码的创建者不得不记住包含函数的定义以及数组的定义逻辑。它也使得代码不那么容易与其他代码整合,因为将从原来只需要确定函数名是否在全局命名空间中唯一,变成有必要确定和该函数关联的数组的名称是否在全局命名空间中保持唯一。
一个闭包可以让缓冲数组关联(干净地包含)它依赖的函数,并且同时保持缓冲数组的属性名称,像被分配在全局空间中一样,同时能够避免名称冲突以及代码交互干扰的危险。
这里有一招就是通过执行一个内联的函数表达式创建一个额外的执行上下文,让那个函数表达式返回一个内联的函数,该函数被外部代码使用。缓冲数组被定义在内联执行的函数表达式中,作为一个局部变量。它仅被调用一次,所以该数组只被创建一次。但是对于依赖它的函数来说该数组是一直可访问的,并且可被重用的。
接一下的代码创建了一个函数,将返回一个HTML字符串,其中的一部分是不变的,但那些不变的字符串需要被穿插进作为参数传递进来的变量中。
一个内联执行的函数表达式返回了内部函数对象的一个引用。并且分配了一个全局变量,让它可以被作为一个全局函数来调用。而缓冲数组作为一个局部变量被定义在外部函数表达式中。它没有被扩展到全局命名空间中,并且无论函数什么时候使用它都不需要被再次创建。
*/
/*
定义一个全局变量:getImgInPositionedDivHtml
被赋予对外部函数表达式一次调用返回的一个内部函数表达式
内部函数返回了一个HTML字符串,代表一个绝对定位的DIV
包裹这一个IMG元素,而所有的变量值都被作为函数调用的参数
*/
let getImgInPositionedDivHtml = (function() {
/*
buffAr 数组被定义在外部函数表达式中,作为一个局部变量
它只被创建一次。数组的唯一实例对内部函数是可见的,
所以它可以被用于每一次的内部函数执行
空字符串仅仅被用来作为一个占位符,它将被内部函数的参数代替
*/
let buffAr = [
'<div id="',
'', //index 1, DIV ID attribute
'" style="position:absolute;top:',
'', //index 3, DIV top position
'px;left:',
'', //index 5, DIV left position
'px;width:',
'', //index 7, DIV width
'px;height:',
'', //index 9, DIV height
'px;overflow:hidden;\"><img src=\"',
'', //index 11, IMG URL
'\" width=\"',
'', //index 13, IMG width
'\" height=\"',
'', //index 15, IMG height
'\" alt=\"',
'', //index 17, IMG alt text
'\"><\/div>'
];
/*
返回一个内部函数对象,他是函数表达式执行返回的结果
*/
return (function(url, id, width, height, top, left, altText) {
/*
分配各种参数给对应的数组元素
*/
buffAr[1] = id;
buffAr[3] = top;
buffAr[5] = left;
buffAr[13] = (buffAr[7] = width);
buffAr[15] = (buffAr[9] = height);
buffAr[11] = url;
buffAr[17] = altText;
/*
返回连接每个元素后创建的字符串
*/
return buffAr.join('');
});
})();
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
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