JS的相关知识点比较繁杂,特此开篇整理一波,方便回顾总结查阅。
- 运行在宿主环境环境中,比如浏览器或node环境
- 不用预编译,直接解释执行代码
- 是弱类型语言,较为灵活
- 与操作系统无关,跨平台的语言
- 脚本语言,解释性语言
概念
JavaScript 是一门跨平台、面向对象、基于原型的轻量级动态脚本语言。
与java的对比:
| JavaScript | Java | 
|---|---|
| 面向对象。不区分对象类型。通过原型机制继承,任何对象的属性和方法均可以被动态添加。 | 基于类系统。分为类和实例,通过类层级的定义实现继承。不能动态增加对象或类的属性或方法。 | 
| 变量类型不需要提前声明(动态类型)。 | 变量类型必须提前声明(静态类型)。 | 
| 不能直接自动写入硬盘。 | 可以直接自动写入硬盘。 | 
变量声明
var(存在变量提升)
声明一个变量,可赋一个初始化值。
let(let 同一变量在同一作用域不能同时声明)
声明一个块作用域的局部变量,可赋一个初始化值。
const(const 声明时必须赋初始值,也不可以在脚本运行时重新声明)
声明一个块作用域的只读的命名常量。 const声明创建一个值的只读引用。但这并不意味着它所持有的值是不可变的,只是变量标识符不能重新分配。 如const a=[1,2,3] a[1]=4; const b={} b.name=”1” 数组元素和对象属性不受保护。
变量的作用域
在所有函数之外声明的变量,叫做全局变量,因为它可被当前文档中的任何其他代码所访问。在函数内部声明的变量,叫做局部变量,因为它只能在该函数内部访问。全区变量是全局对象的属性,在浏览器中可以用window.xx或xx来访问。
| 1 | if(true){ | 
变量提升
JavaScript 变量的另一特别之处是,你可以引用稍后声明的变量而不会引发异常。这一概念称为变量声明提升(hoisting); var ok ; let 和 const 则不会存在变量提升
| 1 | 1. | 
函数提升
声明函数的3种方式:
| 1 | function foo(){} // 函数声明 存在函数提升且大于变量提升 | 
从作用域上来说,函数声明式和函数表达式使用的是局部变量,而 Function()构造函数却是全局变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22var name = '我是全局变量 name';
// 声明式
function a () {
  var name = '我是函数a中的name';
  return name;
}
console.log(a()); // 打印: "我是函数a中的name"
// 表达式
var b = function() {
  var name = '我是函数b中的name';
  return name; // 打印: "我是函数b中的name"
}
console.log(b())
// Function构造函数
function c() {
  var name = '我是函数c中的name';
  return new Function('return name')
}
console.log(c()()) // 打印:"我是全局变量 name",因为Function()返回的是全局变量 name,而不是函数体内的局部变量。
从执行效率上来说,Function()构造函数的效率要低于其它两种方式,尤其是在循环体中,因为构造函数每执行一次都要重新编译,并且生成新的函数对象。
此时的3种递归调用自身的方式
- foo()
- foo1()
- arguments.callee()
现在已经不推荐使用arguments.callee(); 原因:访问 arguments 是个很昂贵的操作,因为它是个很大的对象,每次递归调用时都需要重新创建。影响现代浏览器的性能,还会影响闭包。
数据类型 8种
原始类型
- Boolean
- null
- undefined
- String
- Number 标识范围 -2^53~2^53 数字均为双精度浮点类型- 实质是一个 64 位的浮点数。使用 53 位表示小数位,10 位表示指数位,1 位表示符号位。
 
- Symbol(它的实例是唯一且不可改变)
- bigint
Object 是 JavaScript 中所有对象的父对象(object、array、function)
两种类型的区别是:存储位置不同; 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储; 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定,如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先 检索其在栈中的地址,取得地址后从堆中获得实体
数据封装类对象:Object、Array、Boolean、Number 和 String 其他对象:Function、Arguments、Math、Date、RegExp、Error
为什么 x=0.1 能得到 0.1? 恭喜你到了看山不是山的境界。因为 mantissa 固定长度是 52 位,再加上省略的一位,最多可以表示的数是 2^53=9007199254740992,对应科学计数尾数是
9.007199254740992,这也是 JS 最多能表示的精度。它的长度是 16,所以可以使用 toPrecision(16) 来做精度运算,超过的精度会自动做凑整处理。
不仅 JavaScript,所有遵循 IEEE 754 规范的语言都是如此;
在JavaScript中,所有的Number都是以64-bit的双精度浮点数存储的,在用二进制存储时会存在精度误差;
原因:计算机不能精确表示0.1, 0.2这样的浮点数,计算时使用的是带有舍入误差的数,但不是所有的浮点数在计算机内部都存在舍入误差,比如0.5就没有舍入误差
双精度的浮点数在这64位上划分为3段,而这3段也就确定了一个浮点数的值,64bit的划分是“1-11-52”的模式,具体来说:
1.就是1位最高位(最左边那一位)表示符号位,0表示正,1表示负;
2.11位表示指数部分;52位表示尾数部分,也就是有效域部分
任何原始类型都有字面量形式,可以不被变量所存储。
JavaScript语言有”垃圾回收”功能,所以在使用引用类型的时候无需担心内存分配。但是为了防止”内存泄漏”还是应该在不实用对象的时候将该对
象的引用赋值为null。让”垃圾回收”器在特定的时间对那一块内存进行回收。
64位浮点数:1位符号位 + 52位整数位 + 11位小数位,如果符号位为1,其他各位均为0,那么这个数值会被表示成”-0”。同理还可以表示”+0”
| 1 | // 二进制构造-0 | 
- 判断+-0 - 1 
 2
 3
 4
 5
 6- 0 === -0 // true 
 // Object.is(-0, 0)返回false,Object.is(NaN, NaN)返回true
 // 早期es利用1/-0为-Infinity的特点来判断
 function isNegativeZero(num) {
 return num === 0 && (1 / num < 0);
 }
- Object.is()方法的polyfill - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12- if (!Object.is) { 
 Object.is = function(x, y) {
 // SameValue algorithm
 if (x === y) { // Steps 1-5, 7-10
 // Steps 6.b-6.e: +0 != -0
 return x !== 0 || 1 / x === 1 / y;
 } else {
 // Step 6.a: NaN == NaN
 return x !== x && y !== y;
 }
 };
 }
原始封装类型
| 1 | var a ='qwer'; | 
在js引擎中发生了
1
2
3
4
5var a ='qwer';
var temp = new String(a);
var firstChar = temp.chatAt(0);
temp =null;
console.log(firstChar);// q
| 1 | 0.10000000000000000555.toPrecision(16) | 
"getters"/"setters"可以想象一个给定的字符串就像一个附加了一堆方法和属性的对象。当访问数组的长度时,你只需调用相应的 getter。setter 函数用于设置操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21var array = {
  value: ["Hello", 89, false, true],
  push: function(element) {
    //
  },
  shift: function() {
    //
  },
  get length() {
    // gets the length
  },
  set length(newLen) {
    // sets the length
  }
};
// Getter call
var len = array.length
// Setter call
array.length = 50;
对象Object
Object 是 JS 中最重要的类型,因此几乎所有其他实体都可以从中派生。 例如,函数和数组是专用对象。 JS 中的对象是键/值对的容器。
对象被定义为“无序属性的集合,其属性可以包含基本值,对象或者函数”。
只有null和undefined无法拥有方法
Function Array Number Boolean String Date Math RegExp类
| 1 | typeof null === 'object' // true | 
构造函数 Array(..) 不要求必须带 new 关键字。不带时,它会被自动补上。 因此 Array(1,2,3) 和 new Array(1,2,3) 的效果是一样的
MDN基本数据类型的定义
除 Object 以外的所有类型都是不可变的(值本身无法被改变)。例如,与 C 语言不同,JavaScript 中字符串是不可变的 (译注:如,JavaScript 中对字符串的操作一定返回了一个新字符串,原始字符串并没有被改变)。我们称这些类型的值为“原始值”。
| 1 | var a = 'string' | 
我们通常情况下都是对一个变量重新赋值,而不是改变基本数据类型的值。在 js 中是没有方法是可以改变布尔值和数字的。 倒是有很多操作字符串的方法,但是这些方法都是返回一个新的字符串,并没有改变其原有的数据。
引用数据类型
引用类型(object)是存放在堆heap内存中的,变量实际上是一个存放在栈内存的指针,这个指针指向堆内存中的地址。每个空间大小不一样,要根据情况开进行特定的分配。
传值和传址
了解了基本数据类型与引用类型的区别之后,我们就应该能明白传值与传址的区别了。 在我们进行赋值操作的时候,基本数据类型的赋值(=)是在内存中新开辟一段栈内存,然后再将值赋值到新的栈中。例如:
| 1 | var a = 10; | 
所以说,基本类型的赋值的两个变量是两个独立相互不影响的变量。
但是引用类型的赋值是传址。只是改变指针的指向,例如,也就是说引用类型的赋值是对象保存在栈中的地址的赋值,这样的话两个变量就指向同一个对象,因此两者之间操作互相有影响。例如:
| 1 | var a = {}; // a保存了一个空对象的实例 | 
字面量
字面量是由语法表达式定义的常量
- 数组字面量(Array literals) []
- 布尔字面量(Boolean literals) true/false
- 浮点数字面量(Floating-point literals) 3.14
- 整数(Intergers) 5
- 对象字面量(Object literals) {}
- RegExp literals 一个正则表达式是字符被斜线(译注:正斜杠“/”)围成的表达式 /a+b/ RegExp.test() RegExp.exec() string.match()
- 字符串字面量(String literals) “1212” ‘1212’ JavaScript会自动将字符串字面值转换为一个临时字符串对象,调用该方法,然后废弃掉那个临时的字符串对象。你也能用对字符串字面值使用类似
| 1 | String.length的属性: | 
| 1 | var obj={ | 
十进制整数字面量由一串数字序列组成,且没有前缀0。 八进制的整数以 0(或0O、0o)开头,只能包括数字0-7。 十六进制整数以0x(或0X)开头,可以包含数字(0-9)和字母 a~f 或 A~F。 二进制整数以0b(或0B)开头,只能包含数字0和1。
模板字符串
| 1 | var name = "Bob", time = "today"; | 
布尔环境的假值
- false
- undefined
- null
- 0
- NaN
- 空字符串(””)
try-catch
如果finally块返回一个值,该值会是整个try-catch-finally流程的返回值,不管在try和catch块中语句返回了什么:
| 1 | function f() { | 
for of 和 for in循环
- for in 更适合对象,遍历数组和对象
- for of 适合遍历数组,遍历没有Symbol.iterator属性的对象是会报错的1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28let arr = [3, 5, 7]; 
 arr.foo = "hello";
 for (let i in arr) {
 console.log(i); // logs "0", "1", "2", "foo"
 }
 // 所有可枚举的属性名
 for (let i of arr) {
 console.log(i); // logs "3", "5", "7" // 注意这里没有 hello
 }
 // for in 的循环顺序 => 遍历首先数字的可以接着按照创建顺序遍历
 // 对象数字键名会转成字符串 对象的key值只有string和symbol类型
 // 排序规则同样适用于下列API:
 // Object.entries
 // Object.values
 // Object.keys
 // Object.getOwnPropertyNames
 // Reflect.ownKeys
 var a = {1:1,name:'cosyer',2:2}
 for (let i in a) {
 if(a.hasOwnProperty(i)){
 console.log(i)
 }
 }
 // 1 2 name
嵌套函数和闭包
一个闭包是一个可以自己拥有独立的环境与变量的的表达式。
- 内部函数包含外部函数的作用域。
- 内部函数只可以在外部函数中访问。
- 内部函数可以访问外部函数的参数和变量,但是外部函数却不能使用它的参数和变量。
多层嵌套函数
函数可以被多层嵌套。例如,函数A可以包含函数B,函数B可以再包含函数C。B和C都形成了闭包,所以B可以访问A,C可以访问B和A。因此,闭包可以包含多个作用域;他们递归式的包含了所有包含它的函数作用域。这个称之为作用域链。
| 1 | function A(x) { | 
在这个例子里面,C可以访问B的y和A的x。这是因为:
- B形成了一个包含A的闭包,B可以访问A的参数和变量
- C形成了一个包含B的闭包
- B包含A,所以C也包含A,C可以访问B和A的参数和变量。换言之,C用这个顺序链接了B和A的作用域
反过来却不是这样。A不能访问C,因为A看不到B中的参数和变量,C是B中的一个变量,所以C是B私有的。
作用域链解释说明
当同一个闭包作用域下两个参数或者变量同名时,就会产生命名冲突。更近的作用域有更高的优先权,所以最近的优先级最高,最远的优先级最低。这就是作用域链。链的第一个元素就是最里面的作用域,最后一个元素便是最外层的作用域。
全局函数无法查看局部函数的内部细节,但局部函数可以查看其上层的函数细节,直至全局细节。
当需要从局部函数查找某一属性或方法时,如果当前作用域没有找到,就会上溯到上层作用域查找,
直至全局函数,这种组织形式就是作用域链。
| 1 | function outside() { | 
命名冲突发生在return x上,inside的参数x和outside变量x发生了冲突。这里的作用域链是{inside, outside, 全局对象}。因此inside的x具有最高优先权,返回了20(inside的x)而不是10(outside的x)。
闭包
JavaScript 允许函数嵌套,并且内部函数可以访问定义在外部函数中的所有变量和函数,以及外部函数能访问的所有变量和函数。但是,外部函数却不能够访问定义在内部函数中的变量和函数。这给内部函数的变量提供了一定的安全性。此外,由于内部函数可以访问外部函数的作用域,因此当内部函数生存周期大于外部函数时,外部函数中定义的变量和函数将的生存周期比内部函数执行时间长。当内部函数以某一种方式被任何一个外部函数作用域访问时,一个闭包就产生了。
| 1 | var pet = function(name) { //外部函数定义了一个变量"name" | 
arguments 对象
函数的实际参数会被保存在一个类似数组的arguments对象中。
| 1 | arguments[i] // 访问 | 
arguments变量只是 ”类数组对象“,并不是一个数组。称其为类数组对象是说它有一个索引编号和length属性。尽管如此,它并不拥有全部的Array对象的操作方法。
函数参数(默认参数、剩余参数(rest))
剩余参数语法允许将不确定数量的参数表示为数组
| 1 | function multiply(a, b = 1,...[1,2,3]) { | 
箭头函数
箭头函数总是匿名的 引入箭头函数的原因
- 更简洁的语法
- 捕捉闭包上下文的this值1 
 2
 3
 4
 5
 6
 7
 8
 9function Person(){ 
 this.age = 0;
 setInterval(() => {
 this.age++; // |this| properly refers to the person object
 }, 1000);
 }
 var p = new Person();
扩展语句
适用于对象,数组
1
2
3function f(x, y, z) { }
var args = [0, 1, 2];
f(...args);
临时对象
你可以在String字面值上使用String对象的任何方法—JavaScript自动把String字面值转换为一个临时的String对象, 然后调用其相应方法,最后丢弃销毁此临时对象.在String字面值上也可以使用String.length属性.
| 1 | var s1 = "2 + 2"; // Creates a string literal value | 
数组方法
concat() 连接两个数组并返回一个新的数组。
| 1 | var myArray = new Array("1", "2", "3"); | 
join() 将数组的所有元素连接成一个字符串。
| 1 | var myArray = new Array("Wind", "Rain", "Fire"); | 
push() 在数组末尾添加一个或多个元素,并返回数组操作后的长度。
| 1 | var myArray = new Array("1", "2"); | 
pop() 从数组移出最后一个元素,并返回该元素。
| 1 | var myArray = new Array("1", "2", "3"); | 
shift() 从数组移出第一个元素,并返回该元素。
| 1 | var myArray = new Array ("1", "2", "3"); | 
unshift()在数组开头添加一个或多个元素,并返回数组的新长度。
| 1 | var myArray = new Array ("1", "2", "3"); | 
slice(开始索引,结束索引) 从数组提取一个片段,并作为一个新数组返回。
| 1 | var myArray = new Array ("a", "b", "c", "d", "e"); | 
slice 方法可以用来将一个类数组(Array-like)对象/集合转换成一个新数组。只需将该方法绑定到这个对象上。 一个函数中的 arguments 就是一个类数组对象的例子。
| 1 | Array.prototype.slice.call({0:1,1:3,length:2}); | 
splice(index, count_to_remove, addElement1, addElement2, …)从数组移出一些元素,(可选)并替换它们。
| 1 | var myArray = new Array ("1", "2", "3", "4", "5"); | 
reverse() 颠倒数组元素的顺序:第一个变成最后一个,最后一个变成第一个。
| 1 | var myArray = new Array ("1", "2", "3"); | 
sort() 给数组元素排序。
| 1 | var arr=[2,1,3] | 
sort() 也可以带一个回调函数来决定怎么比较数组元素。这个回调函数比较两个值,并返回3个值中的一个:
- 如果 a 小于 b ,返回 -1(或任何负数) 降序
- 如果 a 大于 b ,返回 1 (或任何正数) 升序
- 如果 a 和 b 相等,返回 0。
indexOf(searchElement[, fromIndex]) 在数组中搜索searchElement 并返回第一个匹配的索引。
| 1 | var a = ['a', 'b', 'a', 'b', 'a']; | 
lastIndexOf(searchElement[, fromIndex]) 和 indexOf 差不多,但这是从结尾开始,并且是反向搜索。
forEach() 循环数组 不定的顺序 不能用break,return false跳出循环遍历
遍历都不会修改原来的基本类型(只能返回新数组)引用类型可以。
map() 循环数组返回新数组
| 1 | var a1 = ['a', 'b', 'c']; | 
find() 找到满足条件的第一个元素
filter() 循环数组返回符合条件的元素
| 1 | var a1 = ['a', 10, 'b', 20, 'c', 30]; | 
every() 循环数组 如果全部元素满足条件则返回true 否则返回false
some() 循环数组 只要有一项满足条件则返回true 全部不满足返回false
reduce() 迭代 使用回调函数 callback(firstValue, secondValue) 把数组列表计算成一个单一值 reduceRight() 从右边开始
| 1 | var a = [10, 20, 30]; | 
填充fill/splice
| 1 | var fruits = ["Banana", "Orange", "Apple", "Mango"]; | 
copyWithin
| 1 | var fruits = ["Banana", "Orange", "Apple", "Mango"]; | 
entries 一个数组的迭代对象,该对象包含数组的键值对
| 1 | var a = ['a','b','c'] | 
Map简单的键值对集合(字典的数字结构类似对象,键可以是各种类型的值)
| 1 | var sayings = new Map(); | 
new Map() 参数可以是一个数组或者其他 iterable 对象,其元素或为键值对,或为两个元素的数组。 每个键值对都会添加到新的 Map。null 会被当做 undefined。
1
2
3
4
5
6
7
8
9
10const set = new Set([
  ['foo', 1],
  ['bar', 2]
]);
const m1 = new Map(set);
m1.get('foo') // 1
const m2 = new Map([['baz', 3]]);
const m3 = new Map(m2);
m3.get('baz') // 3
WeakMap(类似Map,只接受对象作为键名(null除外),WeakMap的键名所指向的对象,不计入垃圾回收机制)
Object和Map的比较
- 一般地,objects会被用于将字符串类型映射到数值。Object允许设置键值对、根据键获取值、删除键、检测某个键是否存在。而Map具有更多的优势。
- Object的键均为Strings类型,在Map里键可以是任意类型。
- 必须手动计算Object的尺寸,但是可以很容易地获取使用Map的尺寸。
- Map的遍历遵循元素的插入顺序。
- Object有原型,所以映射中有一些缺省的键。(可以理解为map = Object.create(null))。
如果键在运行时才能知道,或者所有的键类型相同,所有的值类型相同,那就使用Map。 如果需要将原始值存储为键,则使用Map,因为Object将每个键视为字符串,不管它是一个数字值、布尔值还是任何其他原始值。 如果需要对个别元素进行操作,使用Object。
Set集合(类似数组,成员的值都是唯一且无序的)
| 1 | var mySet = new Set(); | 
WeakSet(类似Set,成员都是对象,弱引用)
WeakSet 对象中储存的对象值都是被弱引用的,即垃圾回收机制不考虑 WeakSet 对该对象的应用,如果没有其他的变量或属性引用这个对象值,则这个对象将会被垃圾回收掉(不考虑该对象还存在于 WeakSet 中),所以,WeakSet 对象里有多少个成员元素,取决于垃圾回收机制有没有运行,运行前后成员个数可能不一致,遍历结束之后,有的成员可能取不到了(被垃圾回收了),WeakSet 对象是无法被遍历的(ES6 规定 WeakSet 不可遍历),也没有办法拿到它包含的所有元素。
Array和Set的比较
- 数组中用于判断元素是否存在的indexOf 函数效率低下。
- Set对象允许根据值删除元素,而数组中必须使用基于下标的 splice 方法。
- 数组的indexOf方法无法找到NaN值。
- Set对象存储不重复的值,所以不需要手动处理包含重复值的情况。
- 数组是特殊的对象,对象是关联数组 字符串是特殊的数组
- 方括弧取值为动态判定[],数字非有效的js标识符
setter和getter (get set修饰function)
| 1 | var o = { | 
访问所有可枚举对象属性
- for in 无序
- Object.keys() 不包括原型的属性名数组
- Object.getOwnPropertyNames()
Symbol(原始数据类型) 不可枚举的 符号类型
- 应用于对象的属性,Symbol类型的属性具有一定的隐藏性。1 
 2var myPrivateMethod = Symbol(); // 不能使用new Symbol()创建,它是一个不完整的类属于基本类型 
 this[myPrivateMethod] = function() {...};
for in 和 Object.getOwnPropertyNames()访问不到,只能通过myPrivateMethod或者Object.getOwnPropertySymbols()来访问
| 1 | Symbol("foo") !== Symbol("foo") // true | 
| 1 | let name = Symbol('name'); | 
- 使用Symbol.iterator迭代器来逐个返回数组的单项1 
 2
 3
 4
 5
 6
 7
 8et arr = ['a', 'b', 'c']; 
 var iterator = arr[Symbol.iterator]();
 // next 方法返回done表示是否完成
 console.log(iterator.next()); // {value: "a", done: false}
 console.log(iterator.next()); // {value: "b", done: false}
 console.log(iterator.next()); // {value: "c", done: false}
 console.log(iterator.next()); // {value: undefined, done: true}
 console.log(iterator.next()); // {value: undefined, done: true}
Proxy 代理
let p= new Proxy(target,handler)
- target 用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。 
- handler 一个对象,其属性是当执行一个操作时定义代理的行为的函数。 
| 1 | // 设置缺省值 | 
生成器 generator
function* 来修饰GeneratorFunction函数
| 1 | function* idMaker() { | 
对象实现迭代行为
| 1 | var myIterable = {}; | 
3行实现Promise
| 1 | function Promise (fn) { | 
- 简单版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
 97function myPromise(constructor) { 
 let self = this;
 self.status = "pending";
 // resolved时候的值
 self.value = undefined;
 // rejected时候的值
 self.reason = undefined;
 // 存放成功回调的数组
 self.onResolveCallbacks = [];
 // 存放失败回调的数组
 self.onRejectedCallbacks = [];
 function resolve(value) {
 if(self.status === 'pending') {
 self.value = value;
 self.status = "resolved";
 self.onResolveCallbacks.forEach(cb => cb(self.value))
 }
 }
 function reject(reason) {
 if(self.status === 'pending') {
 self.reason = reason;
 self.status = "rejected";
 self.onRejectedCallbacks.forEach(cb => cb(self.value))
 }
 }
 // 捕获构造异常
 try {
 constructor(resolve, reject);
 } catch(e) {
 reject(e);
 }
 }
 myPromise.prototype.then = function(onFulfilled, onRejected) {
 // let self = this;
 // switch(self.status) {
 // case "resolved":
 // resolved(self.value);
 // break;
 // case "rejected":
 // rejected(self.reason);
 // break;
 // default:
 // 如果成功和失败的回调没有传,表示这个then没有任何逻辑,只负责把值往后抛
 onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : value => value
 onRejected = typeof onRejected == 'function' ? onRejected : reason => { throw reason }
 let self = this;
 let promise2;
 // 实现链式调用,每一种状态都要返回的是一个promise实例
 if(self.status == "resolved"){ // 如果promise状态已经是成功态,onFulfilled直接取值
 return promise2 = new Promise(function(resolve, reject){
 setTimeout(function(){ // 保证返回的promise是异步
 try{
 onFulfilled(self.value)
 } catch (e){
 // 如果执行成功的回调过程中出错,用错误原因把promise2 reject
 reject(e)
 }
 })
 })
 }
 if(self.status == "rejected"){
 return promise2 = new Promise(function(resolve, reject){
 setTimeout(function(){
 try{
 onRejected(self.reason)
 } catch (e){
 reject(e)
 }
 })
 })
 }
 if(self.status === "pending"){
 return promise2 = new Promise(function(resolve, reject){
 // pending 状态时就会把所有的回调函数都添加到实例中的两个堆栈中暂存,等状态改变后依次执行,其实这个过程就是观察者模式
 self.onResolveCallbacks.push(function(){
 setTimeout(function(){
 try{
 onFulfilled(self.value)
 } catch(e){
 reject(e)
 }
 })
 })
 self.onRejectCallbacks.push(function(){
 setTimeout(function(){
 try{
 onRejected(self.reason)
 } catch(e){
 reject(e)
 }
 })
 })
 })
 }
 }
现一个函数,将一个字符串中的空格替换成“%20”
| 1 | function convertSpace2percent20(str){ | 
var obj = { 1: “Hello”, 2: “World” }打印Hello World
| 1 | var obj = { | 
循环
- while - 只要指定的条件成立,则循环执行代码块
- do…while - 首先执行一次代码块,然后在指定的条件成立时重复这个循环
- for - 循环执行代码块指定的次数
- foreach - 根据数组中每个元素来循环代码块forEach等函数的第二个参数的用法forEach函数用得平时用得比较多,但是从来没想到forEach函数还有第二个参数。
简单点来说,就是我们可以直接使用第二个参数来指定函数里的this的值,而不需要使用箭头函数或者在外面定义var that = this;等操作。
| 1 | var obj = { | 
数组扁平化(将多维数组展开为一维数组)
| 1 | // es6 递归+判断是不是数组 也可以使用reduce进行遍历 | 
自定义错误类型
| 1 | // ES6 | 
命令式编程和声明式编程
- 纯粹性:纯函数不改变除当前作用域以外的值;
- 数据不可变性: Immutable
| 1 | // 反面示例 | 
- 函数柯里化 将多个入参的函数转化成1个入参的函数问题:sum(2, 3)实现sum(2)(3)的效果1 
 2const add = a => b => c => a + b + c 
 add(1)(2)(3)1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29// 闭包 
 function sub_curry(fn) {
 let args = [].slice.call(arguments, 1);
 return function() {
 return fn.apply(this, args.concat([].slice.call(arguments)));
 };
 }
 function curry(fn, length) {
 // 初始化时赋值为fn的形参个数,用以标示剩余需要传入参数的个数
 length = length || fn.length;
 const slice = Array.prototype.slice;
 return function() {
 if (arguments.length < length) {
 const combined = [fn].concat(slice.call(arguments));
 // length - arguments.length用以计数当前已传入的参数
 return curry(sub_curry.apply(this, combined), length - arguments.length);
 } else {
 return fn.apply(this, arguments);
 }
 };
 }
 function add(a, b) {
 return a + b
 }
 var sum = curry(add)
- 偏函数 将多个入参的函数转化成两部分(返回一个包含预处理参数的新函数) - 1 
 2- const add = a => (b, c) => a + b + c 
 add(1)(2, 3)
- 组合函数 - 1 
 2
 3
 4- const add = (x) => x + x 
 const mult = (x) => x * x
 const addAndMult = (x) => add(mult(x))
实现bind函数
| 1 | // 第一种: 借助 call/apply | 
call函数的实现
| 1 | // 对象属性指向函数并调用 | 
稀疏数组
| 1 | var ary = [0,1,2]; | 
switch严格比较
| 1 | function showCase(value) { | 
为什么JS是单线程的
与用途有关,JavaScript的主要用途是与用户互动,以及操作DOM 假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
去重总集篇
- Set - 1 
 2- [...new Set([1,1,2,3])] 
 Array.from(new Set([1,1,2,3]))
- filter - 1 
 2
 3- [1,1,2,3].filter((element,index,array)=>{ 
 return array.indexOf(element) === index;
 })
- Map - 1 
 2
 3
 4
 5
 6
 7
 8
 9- var set =new Set() 
 [1,1,2,3].filter((item)=>{
 return !set.has(item)&&set.add(item)
 })
 // 一个道理
 var map =new Map();
 [1,1,2,3].filter((item,index)=>{
 return !map.has(item)&&map.set(item,index)
 })
- 基础 - 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- // 不使用es6,考虑到(ie6-8)indexOf兼容性问题 
 // 对象容器
 function unique(arr) {
 var ret = []
 var hash = {}
 for (var i = 0; i < arr.length; i++) {
 var item = arr[i]
 // typeof 区分 1 与 ‘1’
 var key = typeof(item) + item
 if (hash[key] !== 1) {
 ret.push(item)
 hash[key] = 1
 }
 }
 return ret
 }
 // 数组
 function unique(arr) {
 const temp = []
 arr.forEach(item => {
 if (temp.indexOf(item) === -1) {
 temp.push(item)
 }
 })
 return temp
 }
- reduce - 1 
 2
 3
 4
 5
 6- function unique(arr) { 
 return arr.reduce((pre, cur) => {
 !pre.includes(cur) && pre.push(cur)
 return pre
 }, [])
 }
2对象
| 1 | (2).toString() // '2' | 
递归运用斐波那契数列 js 实现
| 1 | function* fibo() | 
爬楼梯问题
初始在第一级,到第一级有1种方法(s(1) = 1),到第二级也只有一种方法(s(2) = 1), 第三级(s(3) = s(1) + s(2))
1
2
3
4
5
6
7function cStairs(n) {
    if(n === 1 || n === 2) {
        return 1;
    } else {
        return cStairs(n-1) + cStairs(n-2)
    }
}
arguments对象的length属性显示实参的个数,函数的length属性显示形参的个数。
在不改变原数组的前提下,添加或删除某个元素
- concat - 1 
 2- let arr = [1, 2, 3, 4] 
 let newArr = [].concat(0, arr)
- 扩展运算符 - 1 
 2- let arr = [1, 2, 3, 4] 
 let newArr = [0, ...arr]
- reduce - 1 
 2
 3
 4
 5- let arr = [1, 2, 3, 4] 
 , newArr = arr.reduce((acc, cur) => {
 acc.push(cur)
 return acc
 }, [0])
- slice - 1 
 2- let arr = [1, 2, 3, 4] 
 , newArr = arr.slice(0, -1)- 总结·归纳
- 不修改原数组 - slice
- concat
- forEach
- map
- every
- some
- filter
- reduce
- reduceRight
- keys
- values
- entries
- includes
- find
- findIndex
- flat
- flatMap
 
- 修改原数组 - splice
- pop
- push
- shift
- unshift
- sort
- reverse
- fill
- copyWithin
 
polyfill(腻子)
polyfill 是“在旧版浏览器上复制标准 API 的 JavaScript 补充”,可以动态地加载 JavaScript 代码或库,在不支持这些标准 API 的浏览器中模拟它们。
- 常用的方案html5shiv、Geolocation、Placeholder 
 
        