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
60 === -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
12if (!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
2const add = a => (b, c) => a + b + c
add(1)(2, 3)组合函数
1
2
3
4const 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
9var 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
6function 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
2let arr = [1, 2, 3, 4]
let newArr = [].concat(0, arr)扩展运算符
1
2let arr = [1, 2, 3, 4]
let newArr = [0, ...arr]reduce
1
2
3
4
5let arr = [1, 2, 3, 4]
, newArr = arr.reduce((acc, cur) => {
acc.push(cur)
return acc
}, [0])slice
1
2let 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