一、JS
1. 节流和防抖
(1) 是什么
本质上是优化高频率执行代码的一种手段
如:浏览器的 resize、scroll、keypress、mousemove 等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能
为了优化体验,需要对这类事件进行调用次数的限制,对此我们就可以采用throttle(节流)和debounce(防抖)的方式来减少调用频率。
- 节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次执行
- 防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
(2) 区别
相同点:
- 都可以通过使用
setTimeout实现 - 目的都是,降低回调执行频率。节省计算资源
不同点:
- 函数防抖,在一段连续操作结束后,处理回调,利用
clearTimeout和setTimeout实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能 - 函数防抖关注一定时间连续触发的事件,只在最后执行一次;而函数节流一段时间内只执行一次
(3) 应用场景
防抖在连续的事件,只需触发一次回调的场景有:
- 搜索框搜索输入。只需用户最后一次输入完,再发送请求
- 手机号、邮箱验证输入检测
- 窗口大小
resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。
节流在间隔一段时间执行一次回调的场景有:
- 滚动加载,加载更多或滚到底部监听
- 搜索框,搜索联想功能
2 变量类型
(1) 基本数据 和 引用数据
-
基本数据类型: 只包括自身一般存储在栈内存中,任何方法都不能修改基本数据类型本身的值,都是返回了一个新的。
-
引用数据类型: 包括指针和内容,指针存储在栈内存,内容存储在堆内存。
(2) JS的内存空间 JS的内存空间分为栈(stack)、堆(heap)、池(一般也会归类为栈中)。
其中栈存放变量,堆存放复杂对象,池存放常量,所以也叫常量池。
-
基本数据类型保存在 栈内存 中,因为基本数据类型占用空间小、大小固定,通过按值来访问,属于被频繁使用的数据。
-
引用数据类型存储在 堆内存 中,因为引用数据类型占据空间大、大小不固定。
3 类型判断
(1) typeof
typeof 对于基本类型而言,可以正常检测其类型;
对于引用类型,只能具体判断函数的类型为 function;
typeof null 的值为 object,这是因为一个久远的 Bug。如果想具体判断 null 类型的话直接 xxx === null 即可。
(1.2) null
- undefined 的含义是 未被赋值 ;如果一个变量已经被声明,但未被赋值,那么它的值就是 undefined。
- JS中的 null 仅仅是一个代表 无 、 空 或 值未知的 特殊值。
- null 表示空,它是空对象
- 当需要对数组销毁、对象销毁,或者删除时间监听,通常将他们设置为 null
box.onclick = null
typeof null // "object"
let b = null
b == null // true
b === null // true
(1.3) NaN
- 不是一个数,是一个数字类型的值
typeof NaN // "number"
- 0除以0的结果是NaN;在数学运算中,若结果不能得到数字,其结果往往都是NaN。
0 / 0 // NaN '哈' - 3 // NaN 4 / 0 // Infinity
- NaN不自等
NaN == NaN // false
- 类型判断 isxxx API
(2) Array.isArray()方法检测数组
var a = [1,2,3];
Array.isArray(a) // true
Array.isArray([1,2,378]) // true
Array.isArray({a:1,d:55}) // false
(3) instanceof
- 虽然 typeof 运算符在检测基本类型的时候非常好用,但检测引用类型的时候,就不行了。
通常并不想知道它是不是对象,而是想知道它到底是什么类型的对象,因为 数组是 object ,null 也是 object ,正则也是 object 。
这时应该用 instanceof 运算符来检测
instanceof 内部通过原型链的方式来判断是否为构建函数的实例,常用于判断具体的对象类型。
- 当使用 instanceof 检查基本类型的值时,它会放回 false
(4) Object.prototype.toString
4 深拷贝浅拷贝
(1) 拷贝类型为基本类型
如果属性是基本类型,拷贝的就是基本类型的值。
如果属性是引用类型,拷贝的就是内存地址
(2) 拷贝类型为引用类型
- 浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址
- 深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址
(3) 浅拷贝
浅拷贝只复制属性指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存,修改对象属性会影响原对象
在JavaScript中,存在浅拷贝的现象有:
Object.assignArray.prototype.slice(),Array.prototype.concat()- 使用拓展运算符实现的复制
(3.1)浅拷贝—— Object.assign
Object.assign(dest,[src1,src2,src3...])
dest:目标对象
src1,src2,src3...:源对象
将所有源对象的属性拷贝到目标对象dest中。即从第二个开始的所有参数的属性都被拷贝到第一个参数的对象中
调用结果返回dest
用Object.assign 替代 for .. in 循环来进行简单克隆,它将 user 中的所有属性拷贝到一个空对象中,并返回这个新的对象。
var obj = {
age: 18,
nature: ['smart', 'good'],
names: {
name1: 'fx',
name2: 'xka'
},
love: function () {
console.log('fx is a great girl')
}
}
var newObj = Object.assign({}, fxObj);
(3.2)浅拷贝—— slice()
slice()方法返回一个含有被提取元素的新数组,原数组不会被改变
arr.slice(begin,end)
begin :可选;提取起始处的索引(从0开始),从该索引开始提取原数组元素
end :可选;提取终止处的索引(从0开始)。slice会提取原数组中索引从 begin 到 end 的所有元素( 包括 begin,但不包括 end )
// 浅拷贝
const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.slice(0)
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
(3.3)浅拷贝—— concat()
concat()方法,合并2个或多个数组,此方法不会改变原数组,返回新的数组
var new_array = old_array.concat(value1 [ ,value2 [ ,...[ valueN ] ] ])
valueN :可选。将数组和/或值连接成新的数组
const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.concat()
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
(3.4)浅拷贝—— ### 拓展运算符
const fxArr = ["One", "Two", "Three"]
const fxArrs = [...fxArr]
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
(4) 深拷贝
深拷贝开辟一个新的栈,两个对象属性完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
常见的深拷贝方式有:
- JSON.stringify()
- 手写循环递归
(4.1) 深拷贝—— JSON.stringify
const obj2=JSON.parse(JSON.stringify(obj1));
但是这种方式存在弊端,会忽略undefined、symbol和函数
const obj = {
name: 'A',
name1: undefined,
name3: function() {},
name4: Symbol('A')
}
const obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2); // {name: "A"}
JSON.parse(JSON.stringify(null)) // null
(4.2) 深拷贝—— 手写循环递归
function deepFun(obj) {
if(typeof obj !== 'object' || obj == null) {
return obj
}
let result
if(obj instanceof Array) {
result = []
} else {
result = {}
}
for(let key in obj) {
if(obj.hasOwnProperty(key)) {
result[key] = deepFun(obj[key])
}
}
return result
}
5 类型转换
(1) 常见的类型转换有:
- 强制转换(显示转换)
- 自动转换(隐式转换)
(2) 显示转换
显示转换,即我们很清楚可以看到这里发生了类型的转变,常见的方法有:
- Number()
- parseInt()
- String()
- Boolean()
Number()
Number转换的时候是很严格的,只要有一个字符无法转成数值,整个字符串就会被转为NaN
Number(324) // 324
// 字符串:如果可以被解析为数值,则转换为相应的数值
Number('324') // 324
// 字符串:如果不可以被解析为数值,返回 NaN
Number('324abc') // NaN
// 空字符串转为0
Number('') // 0
// 布尔值:true 转成 1,false 转成 0
Number(true) // 1
Number(false) // 0
// undefined:转成 NaN
Number(undefined) // NaN
// null:转成0
Number(null) // 0
// 对象:通常转换成NaN(除了只包含单个数值的数组)
Number({a: 1}) // NaN
Number([1, 2, 3]) // NaN
Number([5]) // 5
Number(NaN) // NaN
parseInt()
parseInt相比Number,就没那么严格了,parseInt函数逐个解析字符,遇到不能转换的字符就停下来
parseInt('32a3') //32
String()
可以将任意类型的值转化成字符串
// 数值:转为相应的字符串
String(1) // "1"
//字符串:转换后还是原来的值
String("a") // "a"
//布尔值:true转为字符串"true",false转为字符串"false"
String(true) // "true"
//undefined:转为字符串"undefined"
String(undefined) // "undefined"
//null:转为字符串"null"
String(null) // "null"
//对象
String({a: 1}) // "[object Object]"
String([1, 2, 3]) // "1,2,3"
Boolean()
可以将任意类型的值转为布尔值
Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true
(3) 隐式转换
在隐式转换中,我们可能最大的疑惑是 :何时发生隐式转换?
我们这里可以归纳为两种情况发生隐式转换的场景:
- 比较运算(
==、!=、>、<)、if、while需要布尔值地方 - 算术运算(
+、-、*、/、%)
除了上面的场景,还要求运算符两边的操作数不是同一类型
自动转换为布尔值
在需要布尔值的地方,就会将非布尔值的参数自动转为布尔值,系统内部会调用Boolean函数
可以得出个小结:
- undefined
- null
- false
- +0
- -0
- NaN
- ""
除了上面几种会被转化成false,其他都换被转化成true
自动转换成字符串
遇到预期为字符串的地方,就会将非字符串的值自动转为字符串
具体规则是:先将复合类型的值转为原始类型的值,再将原始类型的值转为字符串
常发生在+运算中,一旦存在字符串,则会进行字符串拼接操作
'5' + 1 // '51'
'5' + true // "5true"
'5' + false // "5false"
'5' + {} // "5[object Object]"
'5' + [] // "5"
'5' + function (){} // "5function (){}"
'5' + undefined // "5undefined"
'5' + null // "5null"
自动转换成数值
除了+有可能把运算子转为字符串,其他运算符都会把运算子自动转成数值
'5' - '2' // 3
'5' * '2' // 10
true - 1 // 0
false - 1 // -1
'1' - 1 // 0
'5' * [] // 0
false / '5' // 0
'abc' - 1 // NaN
null + 1 // 1
undefined + 1 // NaN
null转为数值时,值为0 。undefined转为数值时,值为NaN
6 使用 class 实现继承
(1.1) 实现继承
class Student {
constructor(name,age) {
this.name = name
this.age = age
}
fun() {
console.log(`我是${this.name},年龄是${this.age}`)
}
}
const xiaoming = new Student('xiaomiing',20)
console.log('xiaoming',xiaoming)
xiaoming.fun()
class Teacher {
constructor(name,subject) {
this.name = name
this.subject = subject
}
fun() {
console.log(`我是${this.name},教的课程是${this.subject}`)
}
}
const xiaoha = new Teacher('xiaoha','前端')
console.log('xiaoha',xiaoha)
xiaoha.fun()
(1.2) 相同的代码进行整合
在类继承的时候,不仅可以继承属性,也可以继承方法,extend;
super一定要写在最前面
class Person{
constructor(name) {
this.name = name
}
drink() {
console.log('aaaa')
}
}
class Student extends Person { // extends 继承 属性name
constructor(name,age) {
// this.name = name
super(name) // super(name) super一定要写在最前面
this.age = age
}
fun() {
console.log(`我是${this.name},年龄是${this.age}`)
}
}
const xiaoming = new Student('xiaomiing',20)
console.log('xiaoming',xiaoming)
xiaoming.fun()
xiaoming.drink() // 继承Person 的方法drink()
class Teacher extends Person{ // extends 继承 属性name
constructor(name,subject) {
// this.name = name
super(name)
this.subject = subject
}
fun() {
console.log(`我是${this.name},教的课程是${this.subject}`)
}
}
const xiaoha = new Teacher('xiaoha','前端')
console.log('xiaoha',xiaoha)
xiaoha.fun()
xiaoha.drink()
(2) 原型链继承
function Parent() {
this.name = 'parent1';
this.play = [1, 2, 3]
}
function Child() {
this.type = 'child2';
}
Child.prototype = new Parent();
console.log(new Child())
上面代码看似没问题,实际存在潜在问题
var s1 = new Child();
var s2 = new Child();
s1.play.push(4);
console.log(s1.play, s2.play); // [1,2,3,4]
改变s1的play属性,会发现s2也跟着发生变化了,这是因为两个实例使用的是同一个原型对象,内存空间是共享的
7 原型
当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾
准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非实例对象本身
8 原型链
当访问对象属性或方法时,
=> 首先从自身开始找,如果找不到;
=> 就往原型上去找,如果原型上还是找不到;
=> 就往原型的原型上去找;
这样一来就形成一条链式结构,把它称做原型链。
__proto__作为不同对象之间的桥梁,用来指向创建它的构造函数的原型对象的
每个对象的__proto__都是指向它的构造函数的原型对象prototype的
person1.__proto__ === Person.prototype
构造函数是一个函数对象,是通过 Function构造器产生的
Person.__proto__ === Function.prototype
原型对象本身是一个普通对象,而普通对象的构造函数都是Object
Person.prototype.__proto__ === Object.prototype
刚刚上面说了,所有的构造器都是函数对象,函数对象都是 Function构造产生的
Object.__proto__ === Function.prototype
Object的原型对象也有__proto__属性指向null,null是原型链的顶端
Object.prototype.__proto__ === null
9 Javscript数组的常用方法
(1) 操作方法
数组基本操作可以归纳为 增、删、改、查,需要留意的是哪些方法会对原数组产生影响,哪些方法不会
(1.1 )增
下面前三种是对原数组产生影响的增添方法,第四种则不会对原数组产生影响
- push()
- unshift()
- splice()
- concat() 不会对原数组产生影响
push()方法接收任意数量的参数,并将它们添加到数组末尾,返回数组的最新长度
let colors = []; // 创建一个数组
let count = colors.push("red", "green"); // 推入两项
console.log(count) // 2
console.log(colors) // ["red", "green"]
unshift() 在数组开头添加任意多个值,然后返回新的数组长度
let colors = new Array(); // 创建一个数组
let count = colors.unshift("red", "green"); // 从数组开头推入两项
alert(count); // 2
console.log(colors) // ["red", "green"]
splice() 传入三个参数,分别是开始位置、0(要删除的元素数量)、插入的元素,返回空数组。
splice(下标值,数量,添加值)
数量=0,无删除
数量=1,删除1个
let colors = ["red", "green", "blue"];
let removed = colors.splice(1, 0, "yellow", "orange")
console.log(colors) // red,yellow,orange,green,blue
console.log(removed) // []
concat() 首先会创建一个当前数组的副本,然后再把它的参数添加到副本末尾,最后返回这个新构建的数组,不会影响原始数组
let colors = ["red", "green", "blue"];
let colors2 = colors.concat("yellow", ["black", "brown"]);
console.log(colors); // ["red", "green","blue"]
console.log(colors2); // ["red", "green", "blue", "yellow", "black", "brown"]
(1.2) 删
下面三种都会影响原数组,最后一项不影响原数组:
- pop()
- shift()
- splice()
- slice() 不影响原数组
pop() 方法用于删除数组的最后一项,同时减少数组的length 值,返回被删除的项
let colors = ["red", "green"]
let item = colors.pop(); // 取得最后一项
console.log(item) // green
console.log(colors.length) // 1
shift()方法用于删除数组的第一项,同时减少数组的length 值,返回被删除的项
let colors = ["red", "green"]
let item = colors.shift(); // 取得第一项
console.log(item) // red
console.log(colors.length) // 1
splice() 传入两个参数,分别是开始位置,删除元素的数量,返回包含删除元素的数组
let colors = ["red", "green", "blue"];
let removed = colors.splice(0,1); // 删除第一项
console.log(colors); // green,blue
console.log(removed); // red,只有一个元素的数组
slice()用于创建一个包含原有数组中一个或多个元素的新数组,不会影响原始数组
slice(a,b)截取的子数组从下标a开始,到下标b(但不包括b)结束;
slice()如果不提供第二个参数,则表示从指定项开始,提取所有后续所有项作为子数组;
let colors = ["red", "green", "blue", "yellow", "purple"];
let colors2 = colors.slice(1);
let colors3 = colors.slice(1, 4);
console.log(colors) // red,green,blue,yellow,purple
concole.log(colors2); // green,blue,yellow,purple
concole.log(colors3); // green,blue,yellow
(1.2) 改
即修改原来数组的内容,常用splice
传入三个参数,分别是开始位置,要删除元素的数量,要插入的任意多个元素,返回删除元素的数组,对原数组产生影响
let colors = ["red", "green", "blue"];
let removed = colors.splice(1, 1, "red", "purple"); // 插入两个值,删除一个元素
console.log(colors); // red,red,purple,blue
console.log(removed); // green,只有一个元素的数组
splice 的总结:
splice(下标值,数量,添加值),会改变原数组
数量=0,无删除
数量=1,删除1个
- splice()方法用来替换数组中的指定项。
var arr = ['a','b','c','d','e','f']
arr.splice(3,2,'F','G','Y') // 下标为3开始,连续替换2项
console.log(arr) // ["a", "b", "c", "F", "G", "Y", "f"]
- splice()方法用于在指定的位置插入新项。
var arr = ['a','b','c','d']
arr.splice(2,0,'F','G','Y') // 第二个参数为0,即不删除原数组的任何一项,在下标为2的位置添加添加新项
console.log(arr) // ["a", "b", "F", "G", "Y", "c", "d"]
- splice()方法用于删除指定项。
var arr = ['a','b','c','d','e','f']
arr.splice(3,2) // 没有第三个参数,即没有设置替换的新项,
console.log(arr) // ["a", "b", "c", "f"]
- splice()方法以数组形式返回被删除的项。
var arr = ['a','b','c','d','e','f']
var newArr = arr.splice(3,2)
console.log(newArr) // ["d", "e"]
(1.3) 查
即查找元素,返回元素坐标或者元素值
- indexOf()
- includes()
- find()
indexOf()返回要查找的元素在数组中的位置,如果没找到则返回-1
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];\
numbers.indexOf(4) // 3
includes()返回要查找的元素在数组中的位置,找到返回true,否则false
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];\
numbers.includes(4) // true
find()返回第一个匹配的元素
const people = [
{
name: "Matt",
age: 27
},
{
name: "Nicholas",
age: 29
}
];
people.find((element, index, array) => element.age < 28) // // {name: "Matt", age: 27}
(2) 排序方法
数组有两个方法可以用来对元素重新排序:
- reverse()
- sort()
reverse()顾名思义,将数组元素方向排列
let values = [1, 2, 3, 4, 5];
values.reverse();
alert(values); // 5,4,3,2,1
sort()对一个数组进行排序,[2,8,11,6],已升序排序输出,已降序排序输出
(3) 转换方法
join()方法接收一个参数,即字符串分隔符,返回包含所有项的字符串
let colors = ["red", "green", "blue"];
alert(colors.join(",")); // red,green,blue
alert(colors.join("||")); // red||green||blue
(4) 迭代方法 常用来迭代数组的方法(都不改变原数组)有如下:
- some()
- every()
- forEach()
- filter()
- map()
some()对数组每一项都运行传入的函数,如果有一项函数返回 true ,则这个方法返回 true
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let someResult = numbers.every((item, index, array) => item > 2);
console.log(someResult) // true
every()对数组每一项都运行传入的函数,如果对每一项函数都返回 true ,则这个方法返回 true
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let everyResult = numbers.every((item, index, array) => item > 2);
console.log(everyResult) // false
forEach()对数组每一项都运行传入的函数,没有返回值
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
numbers.forEach((item, index, array) => {
// 执行某些操作
});
filter()对数组每一项都运行传入的函数,函数返回 true 的项会组成数组之后返回
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let filterResult = numbers.filter((item, index, array) => item > 2);
console.log(filterResult); // 3,4,5,4,3
map()对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];\
let mapResult = numbers.map((item, index, array) => item * 2);\
console.log(mapResult) // 2,4,6,8,10,8,6,4,2
10Javscript字符串的常用方法
(1) 操作方法 字符串常用的操作方法归纳为增、删、改、查
(1.1) 增
这里增的意思并不是说直接增添内容,而是创建字符串的一个副本,再进行操作
除了常用+以及${}进行字符串拼接之外,还可通过concat
concat用于将一个或多个字符串拼接成一个新字符串
let stringValue = "hello ";
let result = stringValue.concat("world");
console.log(result); // "hello world"
console.log(stringValue); // "hello"
(1.2) 删 这里的删的意思并不是说删除原字符串的内容,而是创建字符串的一个副本,再进行操作
常见的有:
- slice()
- substr()
- substring()
slice()
slice(a,b)方法方法从a开始到b结束(不包括b)的子串。
"我喜欢JS,我也喜欢HTML".slice(3,5) // "JS"
- slice(a,b)中a可以是负数
"我喜欢JS,我也喜欢HTML".slice(-4,-1) // "HTM"
- slice(a,b)中a必须小于b
"我喜欢JS,我也喜欢HTML".slice(5,2) // ""
substr()
substr(a,b)中,得到从a开始的长度为b的子串
"我喜欢JS,我也喜欢HTML".substr(3,2) // "JS"
- substr(a,b)中,可以b省略,表示到字符串的结尾。
"我喜欢JS,我也喜欢HTML".substr(3) // "JS,我也喜欢HTML"
- substr(a,b)中,a可以是负数,表示倒数位置。
"我喜欢JS,我也喜欢HTML".substr(-4,2) // "HT"
substring()
substring(a,b)方法从a开始到b结束(不包括b)的子串。
"我喜欢JS,我也喜欢HTML".substring(3,5) // "JS"
"我喜欢JS,我也喜欢HTML".substring(10,14) // "HTML"
- substring(a,b)忽略第二个参数,返回的子串一直到字符串的结尾。
"我喜欢JS,我也喜欢HTML".substring(6) // "我也喜欢HTML"
- substring(a,b)中a可以大于b,数字顺序将自动调整小数在前。
"我喜欢JS,我也喜欢HTML".substring(5,3) // "JS"
对比总结:
substring(a,b)和slice(a,b)功能基本一致,都是从a开始到b结束(不包括b)的子串,
区别一:substring(a,b)可以自动交换2个参数的位置,而slice(a,b)不行;
区别二:slice(a,b)的参数a可以是负数,而substring(a,b)不行。
substr(a,b)中参数b是子串长度,而不是位置编号。
(1.3) 改 这里改的意思也不是改变原字符串,而是创建字符串的一个副本,再进行操作
常见的有:
- trim()、trimLeft()、trimRight()
- repeat()
- padStart()、padEnd()
- toLowerCase()、 toUpperCase()
trim()、trimLeft()、trimRight()
删除前、后或前后所有空格符,再返回新的字符串
let stringValue = " hello world ";
let trimmedStringValue = stringValue.trim();
console.log(stringValue); // " hello world "
console.log(trimmedStringValue); // "hello world"
repeat() 接收一个整数参数,表示要将字符串复制多少次,然后返回拼接所有副本后的结果
let stringValue = "na ";
let copyResult = stringValue.repeat(2) // na na
padStart()、padEnd() 复制字符串,如果小于指定长度,则在相应一边填充字符,直至满足长度条件
let stringValue = "foo";
console.log(stringValue.padStart(6)); // " foo"
console.log(stringValue.padStart(9, ".")); // "......foo"
toLowerCase()、 toUpperCase() 大小写转化
let stringValue = "hello world";
console.log(stringValue.toUpperCase()); // "HELLO WORLD"
console.log(stringValue.toLowerCase()); // "hello world"
(1.4) 查 除了通过索引的方式获取字符串的值,还可通过:
- chatAt()
- indexOf()
- startWith()
- includes()
chatAt() 返回给定索引位置的字符,由传给方法的整数参数指定
let message = "abcde";
console.log(message.charAt(2)); // "c"
indexOf() 从字符串开头去搜索传入的字符串,并返回位置(如果没找到,则返回 -1 )
let stringValue = "hello world";
console.log(stringValue.indexOf("o")); // 4
startWith() includes() 从字符串中搜索传入的字符串,并返回一个表示是否包含的布尔值
let message = "foobarbaz";
console.log(message.startsWith("foo")); // true
console.log(message.startsWith("bar")); // false
console.log(message.includes("bar")); // true
console.log(message.includes("qux")); // false
(2) 转换方法
split 把字符串按照指定的分割符,拆分成数组中的每一项
let str = "12+23+34"
let arr = str.split("+") // [12,23,34]
(3) 模板匹配方法
针对正则表达式,字符串设计了几个方法:
- match()
- search()
- replace()
match() 接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp对象,返回数组
let text = "cat, bat, sat, fat";
let pattern = /.at/;
let matches = text.match(pattern);
console.log(matches[0]); // "cat"
search() 接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp对象,找到则返回匹配索引,否则返回 -1
let text = "cat, bat, sat, fat";
let pos = text.search(/at/);
console.log(pos); // 1
replace() 接收两个参数,第一个参数为匹配的内容,第二个参数为替换的元素(可用函数)
let text = "cat, bat, sat, fat";
let result = text.replace("at", "ond");
console.log(result); // "cond, bat, sat, fat"
11 闭包
闭包:一个函数和它的周围状态的引用捆绑在一起的组合
函数可以访问到其他函数作用域当中的数据;
子函数可以访问其他函数作用域中的数据,称之为闭包
内存泄漏:子函数一直 引用 父函数 里的变量,可以一直访问,不会消耗。
(1)函数作为返回值
function test() {
const a = 1
return function () {
console.log('a',a)
}
}
const fn = test()
const a = 2
fn() // a 1
(2) 函数作为参数
function test(fn) {
const a = 1
fn()
}
const a = 2
function fn() {
console.log('a',a)
}
test(fn) // a 2
12 作用域、作用域链
(1) 作用域
作用域,即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合
我们一般将作用域分成:
- 全局作用域
- 函数作用域
- 块级作用域
全局作用域
任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问
// 全局变量
var greeting = 'Hello World!';
function greet() {
console.log(greeting);
}
// 打印 'Hello World!'
greet();
函数作用域
函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问
function greet() {
var greeting = 'Hello World!';
console.log(greeting);
}
// 打印 'Hello World!'
greet();
// 报错:Uncaught ReferenceError: greeting is not defined
console.log(greeting);
块级作用域
ES6引入了let和const关键字,和var关键字不同,在大括号中使用let和const声明的变量存在于块级作用域中。在大括号之外不能访问这些变量
{
// 块级作用域中的变量
let greeting = 'Hello World!';
var lang = 'English';
console.log(greeting); // Prints 'Hello World!'
}
// 变量 'English'
console.log(lang);
// 报错:Uncaught ReferenceError: greeting is not defined
console.log(greeting);
(2) 作用域链
当在Javascript中使用一个变量的时候,首先Javascript引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域
如果在全局作用域里仍然找不到该变量,它就会在全局范围内隐式声明该变量(非严格模式下)或是直接报错
13 Javascript中this
(1) this
this:当前环境下,当前对象的引用
如果这个函数是对象的属性,或者叫类方法,这时的this就是当前的对象;
如果这个是普通的函数,这时的 this 就是全局对象,即Window
(2) 通过常量改变 this 指针
(3) 箭头函数中的this
14 # bind、call、apply
call、apply、bind作用是改变函数执行时的上下文,简而言之就是改变函数运行时的this指向
(1) apply
apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入
改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次
function fn(...args){
console.log(this,args);
}
let obj = {
myname:"张三"
}
fn.apply(obj,[1,2]); // this会变成传入的obj,传入的参数必须是一个数组;
fn(1,2) // this指向window
当第一个参数为null、undefined的时候,默认指向window(在浏览器中)
fn.apply(null,[1,2]); // this指向window
fn.apply(undefined,[1,2]); // this指向window
(2) call
call方法的第一个参数也是this的指向,后面传入的是一个参数列表
跟apply一样,改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次
function fn(...args){
console.log(this,args);
}
let obj = {
myname:"张三"
}
fn.call(obj,1,2); // this会变成传入的obj,传入的参数必须是一个数组;
fn(1,2) // this指向window
同样的,当第一个参数为null、undefined的时候,默认指向window(在浏览器中)
fn.call(null,[1,2]); // this指向window
fn.call(undefined,[1,2]); // this指向window
(3) bind
bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入)
改变this指向后不会立即执行,而是返回一个永久改变this指向的函数
function fn(...args){
console.log(this,args);
}
let obj = {
myname:"张三"
}
const bindFn = fn.bind(obj); // this 也会变成传入的obj ,bind不是立即执行需要执行一次
bindFn(1,2) // this指向obj
fn(1,2) // this指向window
从上面可以看到,apply、call、bind三者的区别在于:
- 三者都可以改变函数的
this对象指向 - 三者第一个参数都是
this要指向的对象,如果如果没有这个参数或参数为undefined或null,则默认指向全局window - 三者都可以传参,但是
apply是数组,而call是参数列表,且apply和call是一次性传入参数,而bind可以分为多次传入 bind是返回绑定this之后的函数,apply、call则是立即执行
15 new操作符到底做了什么
new操作符用于创建一个给定构造函数的实例对象
- a.创建一个空对象
- b. 把空对象的原型指向构造函数的原型
- c.改变this指向
- d.返回这个对象
16 Ajax
17 JavaScript中本地存储的方式有哪些?
javaScript本地缓存的方法我们主要讲述以下四种:
- cookie
- sessionStorage
- localStorage
- indexedDB
(1) cookie
Cookie,类型为「小型文本文件」,指某些网站为了辨别用户身份而储存在用户本地终端上的数据。是为了解决 HTTP无状态导致的问题
作为一段一般不超过 4KB 的小型文本数据,它由一个名称(Name)、一个值(Value)和其它几个用于控制 cookie有效期、安全性、使用范围的可选属性组成
但是cookie在每次请求中都会被发送到服务器,如果不使用 HTTPS并对其加密,其保存的信息很容易被窃取,导致安全风险。举个例子,在一些使用 cookie保持登录态的网站上,如果 cookie被窃取,他人很容易利用你的 cookie来假扮成你登录网站
- Cookie 的基本用法 写入cookie
不能一起设置,只能一个一个设置: document.cookie="name=aaa;age=13" //这样是错误的
document.cookie="name=aaa"
document.cookie="age=13"
读取cookie
读取的是一个由名值对构成的字符串,每个名值对之间由“;”(一个分号和一个空格)隔开
console.log(document.cookie) // name=aaa; age=13
- Cookie 的属性1——失效时间
对于失效的Cookie,会被浏览器清除;
对于没有设置失效的Cookie,称为活Cookie,当会话结束,也就是浏览器关闭时,Cookie消失。
长时间存在,需要设置 Expires 或 Max-Age
Expires 值为 Date 类型
document.cookie="name=aaa;expires=${new Date('2100-1-01 00:00:00')}"
Max-Age 用于设置在 Cookie 失效之前需要经过的秒数(优先级比Expires高);
Max-Age 值为数字,表示当前时间 + 多少秒后过期,单位是秒;
如果 Max-Age 的值为0 或是负数,则 Cookie 会被删除。
document.cookie="name=aaa;max-age=5"
- Cookie 的属性2——Domain 域
Domain指定了 Cookie 可以送达的主机名
只能读写当前的父域的 Cookie,无法读写其他域的 Cookie
document.cookie="name=aaa;domain=www.imooc.com"
- Cookie 的属性3——Path 路径
Path 路径 限定了访问 Cookie 的 范围 (同一个域名下)
只能读写当前路径 和上级路径的 Cookie,无法读写下级路径的 Cookie
当 Name、Domain、Path 这3个字段都相同的时候,才是同一个 Cookie
document.cookie="name=aaa;path=/course/list"
- Cookie 的属性4——HttpOnly
设置了 HttpOnly 属性的 Cookie 不能通过JS去访问
- Cookie 的属性5——Secure Secure 限定了 只有 在使用 https 而不是 http 的情况下才可以发送到服务器。
(2) localStorage
(2.1)特点
- 生命周期:持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的
- 存储的信息在同一域中是共享的
- 当本页操作(新增、修改、删除)了
localStorage的时候,本页面不会触发storage事件,但是别的页面会触发storage事件。 - 大小:5M(跟浏览器厂商有关系)
localStorage本质上是对字符串的读取,如果存储内容多的话会消耗内存空间,会导致页面变卡- 受同源策略的限制
- 它只是储存在本地,不会发送到服务器端。
(2.2)localStorage的基本用法:
1)设置setItem() ;2)获取 getItem() ;3)删除removeItem() ;4)一键删除全部clear()
//1)设置setItem()
localStorage.setItem('username','alex')
//有长度 length
console.log(localStorage.length)
//获取 getItem()
console.log(localStorage.getItem('username'))
//获取不存在的返回 null
console.log(localStorage.getItem('ftyj')) // null
//删除removeItem()
localStorage.removeItem('username')
//删除不存在的key,不报错
localStorage.removeItem('8952vfjjvj')
//一键删除全部clear()
localStorage.clear()
(2.3)localStorage 也不是完美的,它有两个缺点:
- 无法像
Cookie一样设置过期时间 - localStorage存储的键和值只能是字符串类型;不是字符串类型,也会自动先转换为字符串再存进去。
localStorage.setItem({},18)
console.log(typeof localStorage.getItem('[object Object]'))
console.log(localStorage.getItem('[object Object]'))
console.log({}.toString())
(3) sessionStorage
sessionStorage和 localStorage使用方法基本一致,唯一不同的是生命周期,一旦页面(会话)关闭,sessionStorage 将会删除数据
(4) 区别
关于cookie、sessionStorage、localStorage三者的区别主要如下:
-
存储大小:
cookie数据大小不能超过4k,sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大 -
有效时间:
localStorage存储持久数据,浏览器关闭后数据不丢失除非主动删除数据;sessionStorage数据在当前浏览器窗口关闭后自动删除;cookie设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭 -
数据与服务器之间的交互方式,
cookie的数据会自动的传递到服务器,服务器端也可以写cookie到客户端;sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存
(5) 应用场景
- 标记用户与跟踪用户行为的情况,推荐使用
cookie - 适合长期保存在本地的数据(令牌),推荐使用
localStorage - 敏感账号一次性登录,推荐使用
sessionStorage - 存储大量数据的情况、在线文档(富文本编辑器)保存编辑历史的情况,推荐使用
indexedDB
18 DOM
文档对象模型 (DOM) 是 HTML 和 XML 文档的编程接口
下面就来分析DOM常见的操作,主要分为:
- 创建节点
- 查询节点
- 更新节点
- 添加节点
- 删除节点
(1) 创建节点 createElement
createElement: 创建新元素,接受一个参数,即要创建元素的标签名
注意事项:
-
新创建出的节点是“孤儿节点”,这意味着它并没有被挂载到DOM树上,我们无法看到它。
-
必须继续使用 appendChild() 或 insertBefore() 方法将孤儿节点插入DOM数上。
appendChild() 追加: 任何已经在DOM树上的节点,都可以调用appendChild()方法,它可以将孤儿节点挂载到它的内部,成为它的最后一个子节点。
<div id="box1">
<p>我是原本的段落0</p>
<p>我是原本的段落1</p>
<p>我是原本的段落2</p>
</div>
<script>
var box = document.getElementById('box1')
// 创建节点,孤儿节点
var op = document.createElement('p')
// 设置内部文字
op.innerText = "我是新来的段落"
// 上树
box.appendChild(op)
</script>
insertBefore(): 任何已经在DOM树上的节点,都可以调用insertBefore()方法,它可以将孤儿节点挂载到它的内部,成为它的“标杆子节点”之前的节点。
父节点.insertBefore(孤儿节点,标杆子节点 )
<div id="box1">
<p>我是原本的段落0</p>
<p>我是原本的段落1</p>
<p>我是原本的段落2</p>
</div>
<script>
var box = document.getElementById('box1')
var p = document.getElementsByTagName('p')
// 创建节点,孤儿节点
var op = document.createElement('p')
// 设置内部文字
op.innerText = "我是新来的段落"
// 上树
box.insertBefore(op,p[1])
</script>
(2) 查询节点
querySelector
- 传入任何有效的
css选择器,即可选中单个DOM元素(首个); - 如果页面上没有指定的元素时,返回
null
document.querySelector('.element')
document.querySelector('#element')
document.querySelector('div')
document.querySelector('[name="username"]')
document.querySelector('div + p > span')
querySelectorAll
-
返回一个包含节点子树内所有与之相匹配的
Element节点列表,如果没有相匹配的,则返回一个空节点列表 -
需要注意的是,该方法返回的是一个
NodeList的静态实例,它是一个静态的“快照”,而非“实时”的查询
const notLive = document.querySelectorAll("p");
(3) 更新节点
innerHTML不但可以修改一个DOM节点的文本内容,还可以直接通过HTML片段修改DOM节点内部的子树
// 获取<p id="p">...</p>
var p = document.getElementById('p');
// 设置文本为abc:
p.innerHTML = 'ABC'; // <p id="p">ABC</p>
// 设置HTML:
p.innerHTML = 'ABC <span style="color:red">RED</span> XYZ';
// <p>...</p>的内部结构已修改
innerText、textContent自动对字符串进行HTML编码,保证无法设置任何HTML标签;
两者的区别在于读取属性时,innerText不返回隐藏元素的文本,而textContent返回所有文本;
innerHTML属性能以HTML语法设置节点中的内容
innerText属性只能以纯文本的形式设置节点中的内容
// 获取<p id="p-id">...</p>
var p = document.getElementById('p-id');
// 设置文本:
p.innerText = '<script>alert("Hi")</script>';
// HTML被自动编码,无法设置一个<script>节点:
// <p id="p-id"><script>alert("Hi")</script></p>
style
DOM节点的style属性对应所有的CSS,可以直接获取或设置。遇到-需要转化为驼峰命名
// 获取<p id="p-id">...</p>
const p = document.getElementById('p-id');
// 设置CSS:
p.style.color = '#ff0000';
p.style.fontSize = '20px'; // 驼峰命名
p.style.paddingTop = '2em';
(4) 删除节点
删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的removeChild把自己删掉;
删除后的节点虽然不在文档树中了,但其实它还在内存中,可以随时再次被添加到别的位置
// 拿到待删除节点:
const self = document.getElementById('to-be-removed');
// 拿到父节点:
const parent = self.parentElement;
// 删除:
const removed = parent.removeChild(self);
removed === self; // true
19 事件的传播
实际上,事件的传播:先从外到内,然后再从内到外。先捕获阶段,再冒泡阶段。
(1) onxxx写法只能监听到冒泡阶段。
// 运行后的结果是:从内到外的顺序,冒泡阶段
// box3事件
// box2事件
// box1事件
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
<script>
var box1 = document.getElementById('box1')
var box2 = document.getElementById('box2')
var box3 = document.getElementById('box3')
box1.onclick = function(){
console.log("box1事件")
}
box2.onclick = function(){
console.log("box2事件")
}
box3.onclick = function(){
console.log("box3事件")
}
</script>
(2) DOM2级事件监听:addEventListener
DOM0级事件监听,只能监听冒泡阶段。
box.onclick = function(){}
DOM2级事件监听:addEventListener
box.addEventListener('click',function(){},true)
addEventListener的参数 true表示监听捕获阶段,false表示监听冒泡阶段。
// 运行后的结果是:从外到内,捕获阶段
// box1事件
// box2事件
// box3事件
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
<script>
var box1 = document.getElementById('box1')
var box2 = document.getElementById('box2')
var box3 = document.getElementById('box3')
box1.addEventListener('click',function(){console.log("box1事件")},true)
box2.addEventListener('click',function(){console.log("box2事件")},true)
box3.addEventListener('click',function(){console.log("box3事件")},true)
</script>
(3)注意事项:
最内部的元素不再区分捕获和冒泡阶段,会先执行写在前面的监听,然后执行后写的监听。
如果给元素设置相同或多个同名事件,则 DOM0 级写法后面写的会覆盖先写的;DOM2 级会按顺序执行。
// 允许结果:依次弹出bb、C、DD
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
<script>
var box1 = document.getElementById('box1')
var box2 = document.getElementById('box2')
var box3 = document.getElementById('box3')
box3.onclick = function() {
alert("a")
}
box3.onclick = function() {
alert("bb")
}
box3.addEventListener('click',function(){alert("C")},false)
box3.addEventListener('click',function(){alert("DD")},false)
</script>
20 web常见的攻击方式
Web攻击(WebAttack)是针对用户上网行为或网站服务器等设备进行攻击的行为
如植入恶意代码,修改网站权限,获取网站用户隐私信息等等
我们常见的Web攻击方式有
- XSS (Cross Site Scripting) 跨站脚本攻击
- CSRF(Cross-site request forgery)跨站请求伪造
- SQL注入攻击
(1.1) XSS
XSS,跨站脚本攻击,允许攻击者将恶意代码植入到提供给其它用户使用的页面中
XSS涉及到三方,即攻击者、客户端与Web应用
XSS的攻击目标是为了盗取存储在客户端的cookie或者其他网站用于识别客户端身份的敏感信息。一旦获取到合法用户的信息后,攻击者甚至可以假冒合法用户与网站进行交互
(1.2) XSS 预防
XSS攻击的两大要素:
- 攻击者提交而恶意代码
- 浏览器执行恶意代码
针对第一个要素,我们在用户输入的过程中,过滤掉用户输入的恶劣代码,然后提交给后端,但是如果攻击者绕开前端请求,直接构造请求就不能预防了
而如果在后端写入数据库前,对输入进行过滤,然后把内容给前端,但是这个内容在不同地方就会有不同显示
二、ES6
1 var、let、const之间的区别
(1) var
- 在ES5中,顶层对象的属性和全局变量是等价的,用
var声明的变量既是全局变量,也是顶层变量
注意:顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象
var a = 10;
console.log(window.a) // 10
- 使用
var声明的变量存在变量提升的情况
console.log(a) // undefined
var a = 20
在编译阶段,编译器会将其变成以下执行
var a
console.log(a)
a = 20
- 使用
var,我们能够对一个变量进行多次声明,后面声明的变量会覆盖前面的变量声明
var a = 20
var a = 30
console.log(a) // 30
- 在函数中使用使用
var声明变量时候,该变量是局部的
var a = 20
function change(){
var a = 30
}
change()
console.log(a) // 20
而如果在函数内不使用var,该变量是全局的
var a = 20
function change(){
a = 30
}
change()
console.log(a) // 30
(2) let
let是ES6新增的命令,用来声明变量
- 用法类似于
var,但是所声明的变量,只在let命令所在的代码块内有效
{
let a = 20
}
console.log(a) // ReferenceError: a is not defined.
- 不存在变量提升
这表示在声明它之前,变量a是不存在的,这时如果用到它,就会抛出一个错误
console.log(a) // 报错ReferenceError
let a = 2
- 使用
let声明变量前,该变量都不可用,也就是大家常说的“暂时性死区”
只要块级作用域内存在let命令,这个区域就不再受外部影响
var a = 123
if (true) {
a = 'abc' // ReferenceError
let a;
}
let不允许在相同作用域中重复声明
let a = 20
let a = 30
// Uncaught SyntaxError: Identifier 'a' has already been declared
注意的是相同作用域,下面这种情况是不会报错的
let a = 20
{
let a = 30
}
因此,我们不能在函数内部重新声明参数
function func(arg) {
let arg;
}
func()
// Uncaught SyntaxError: Identifier 'arg' has already been declared
(3) const
const声明一个只读的常量,一旦声明,常量的值就不能改变
const a = 1
a = 3
// TypeError: Assignment to constant variable.
- 这意味着,
const一旦声明变量,就必须立即初始化,不能留到以后赋值
const a;
// SyntaxError: Missing initializer in const declaration
- 如果之前用
var或let声明过变量,再用const声明同样会报错
var a = 20
let b = 20
const a = 30
const b = 30
// 都会报错
const实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动
对于简单类型的数据,值就保存在变量指向的那个内存地址,因此等同于常量
对于复杂类型的数据,变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的,并不能确保改变量的结构不变
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
其它情况,const与let一致
(4) 区别
var、let、const三者区别可以围绕下面五点展开:
- 变量提升
- 暂时性死区
- 块级作用域
- 重复声明
- 修改声明的变量
- 使用
(4.1) 变量提升
var声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined
let和const不存在变量提升,即它们所声明的变量一定要在声明后使用,否则报错
(4.2) 暂时性死区
var不存在暂时性死区
let和const存在暂时性死区,只有等到声明变量的那一行代码出现,才可以获取和使用该变量
(4.3) 块级作用域
var不存在块级作用域
let和const存在块级作用域
(4.4) 复声明变量
var允许重复声明变量
let和const在同一作用域不允许重复声明变量
(4.5) 修改声明的变量
var和let可以
const声明一个只读的常量。一旦声明,常量的值就不能改变
(4.6)使用
能用const的情况尽量使用const,其他情况下大多数使用let,避免使用var
2 Promise,async和await
(1) Promise状态
promise对象仅有三种状态
pending(进行中)fulfilled(已成功)rejected(已失败)
Promise特点
- 对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态
- 一旦状态改变(从
pending变为fulfilled和从pending变为rejected),就不会再变,任何时候都可以得到这个结果
Promise用法
Promise对象是一个构造函数,用来生成Promise实例
const promise = new Promise(function(resolve, reject) {});
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”
(2) 这个方式的缺点是: 代码的耦合程度太高,connected 和 disconnected 这两个函数的名称是固定的,而且直接写在 callSteven() 函数里面
(3) 虽然这种写法解耦了,但是可读性差
(4) 接下来改用 Promise 写法
.then 、 .catch() 写法
(5) async、await
try 里面是 resolve()的结果;
catch 里面 是 reject() 的结果
try {
} catch (e) {
}
await 这个关键词的代码,只能在一个定义为 async的函数内执行
async function action(params) {
try {
} catch (e) {
}
}
action()
最后代码:
function callSteven(success) {
return new Promise((resolve,reject) => {
console.log('Calling')
setTimeout(() => {
if(success) {
resolve()
}else {
reject()
}
}, 3000)
})
}
async function action(params) {
try {
await callSteven(true)
console.log('Steven pick up the phone')
} catch (e) {
console.log('Steven reject the phone')
}
}
action()
改为IIFE的写法,匿名函数然后立即执行
(async () => {
try {
await callSteven(true)
console.log('Steven pick up the phone')
} catch (e) {
console.log('Steven reject the phone')
}
})()
3 ES6中新增的Set
Set是es6新增的数据结构,类似于数组,但是成员的值都是唯一的,没有重复的值,我们一般称为集合;
集合:是由一堆无序的、相关联的,且不重复的内存结构【数学中称为元素】组成的组合;
集合是以 [值,值] 的形式存储元素
Set本身是一个构造函数,用来生成 Set 数据结构
const s = new Set();
(1) 增删改查
Set的实例关于增删改查的方法:
- add()
- delete()
- has()
- clear()
add() 添加某个值,返回 Set 结构本身; 当添加实例中已经存在的元素,set不会进行处理添加
s.add(1).add(2).add(2); // 2只被添加了一次
delete()删除某个值,返回一个布尔值,表示删除是否成功
s.delete(1)
has()返回一个布尔值,判断该值是否为Set的成员
s.has(2)
clear()清除所有成员,没有返回值
s.clear()
(2) 遍历
- keys():返回键名的遍历器
- values():返回键值的遍历器
- entries():返回键值对的遍历器
- forEach():使用回调函数遍历每个成员
(3)扩展运算符和Set 结构相结合实现数组或字符串去重
// 数组
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)]; // [3, 5, 2]
// 字符串
let str = "352255";
let unique = [...new Set(str)].join(""); // '352'
(4) 类型转换
(5)交集 & 并集 & 差集
4 WeakSet
(1) WeakSet 语法
创建WeakSet实例
const ws = new WeakSet();
WeackSet只能成员只能是引用类型,而不能是其他类型的值
let ws=new WeakSet();
// 成员不是引用类型
let weakSet=new WeakSet([2,3]);
console.log(weakSet) // 报错
// 成员为引用类型
let obj1={name:1}
let obj2={name:1}
let ws=new WeakSet([obj1,obj2]);
console.log(ws) //WeakSet {{…}, {…}}
(2) 在API中WeakSet与Set有两个区别
- 没有遍历操作的
API - 没有
size属性
(3) 引用类型垃圾回收原理
(4) WeakSet弱引用特性
weakSet是弱引用,如果这个地址被垃圾回收了,那么weakSet是没有值的
5 # ES6中新增的 Map
Map是一种叫做字典的数据结构
字典: 是一些元素的集合。每个元素有一个称作key 的域,不同元素的key 各不相同
字典是以 [键,值] 的形式存储
Map类型是键值对的有序列表,而键和值都可以是任意类型
Map本身是一个构造函数,用来生成 Map 数据结构
const m = new Map()
(1) 增删改查
- size 属性
- set()
- get()
- has()
- delete()
- clear()
(1.1) size 属性返回 Map 结构的成员总数
const map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size // 2
(1.2) set()设置键名key对应的键值为value,然后返回整个 Map 结构
如果key已经有值,则键值会被更新,否则就新生成该键
同时返回的是当前Map对象,可采用链式写法
const m = new Map();
m.set('edition', 6) // 键是字符串
m.set(262, 'standard') // 键是数值
m.set(undefined, 'nah') // 键是 undefined
m.set(1, 'a').set(2, 'b').set(3, 'c') // 链式操作
(1.3) get方法读取key对应的键值,如果找不到key,返回undefined
const m = new Map();
const hello = function() {console.log('hello');};
m.set(hello, 'Hello ES6!') // 键是函数
m.get(hello) // Hello ES6!
(1.4) has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中
const m = new Map();
m.set('edition', 6);
m.set(262, 'standard');
m.set(undefined, 'nah');
m.has('edition') // true
m.has('years') // false
m.has(262) // true
m.has(undefined) // true
(1.5) delete方法删除某个键,返回true。如果删除失败,返回false
const m = new Map();
m.set(undefined, 'nah');
m.has(undefined) // true
m.delete(undefined)
m.has(undefined) // false
(1.6) clear方法清除所有成员,没有返回值
let map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size // 2
map.clear()
map.size // 0
(2) 遍历
- keys():返回键名的遍历器
- values():返回键值的遍历器
- entries():返回所有成员的遍历器
- forEach():遍历 Map 的所有成员
(3) Map类型转换
6 WeakMap
(1) WeakMap结构与Map结构类似,也是用于生成键值对的集合
在API中WeakMap与Map有两个区别:
- 没有遍历操作的
API - 没有
clear清空方法
(2) WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名
(3) WeakMap是弱引用类型和前面的WeakSet一样