前言

本文记录JavaScript的高级语法,和ES6新增语法。

一点介绍

面向过程:就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了;按照我们分析好了的步骤,按照步骤解决问题。

面向对象:是把事务分解成为一个个对象,然后由对象之间分工与合作,是以对象功能来划分问题。


类和对象

类抽象了对象的公共部分,它泛指某一大类(class)

对象特指某一个,通过类实例化一个具体的对象

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
// 创建类 class  创建一个明星类
class Star {
// 构造函数
constructor(uname, age) {
this.uname = uname;
this.age = age;
}
// 方法,不需要写function
// 多个函数方法之间不要用,隔开会报错
sing() {
console.log(999)
}
}

// 2. 利用类创建对象 new
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 20);
console.log(ldh);
console.log(zxy);
//(1) 通过class 关键字创建类, 类名我们还是习惯性定义首字母大写
//(2) 类里面有个constructor 函数,可以接受传递过来的参数,同时返回实例对象
//(3) constructor 函数 只要 new 生成实例时,就会自动调用这个函数,
// 如果我们不写这个函数,类也会自动生成这个函数
//(4) 生成实例 new 不能省略
//(5) 最后注意语法规范, 创建类 类名后面不要加小括号,生成实例类名后面加小括号,
// 构造函数不需要加function

// 类的继承
class Father {
constructor() {}
money() {console.log(100)}
}
class Son extends Father {}
var son = new Son();
son.money() // 100

// super关键字
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
return this.x + this.y;
}
}
class Son extends Father {
constructor(x, y) {
// 调用了父类中的构造函数
// 如果不写调用sum报错,父的this.x和this.y是没有值的
super(x, y)
}
sum() {
// 先调用父的方法super.xxx()
console.log(super.sum() + 'Son');
}
}
var son = new Son(1, 2);
son.sum();

// 父类有加法方法
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
}
// 子类继承父类加法方法 同时 扩展减法方法
class Son extends Father {
constructor(x, y) {
// 利用super 调用父类的构造函数
// super 必须在子类this之前调用
super(x, y);
this.x = x;
this.y = y;
}
subtract() {
console.log(this.x - this.y);
}
}
var son = new Son(5, 3);
son.subtract();
son.sum();

class的其他属性

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
// class的静态成员
class Animal {
//静态方法
static eat(name) {
return console.log(name)
}
}
// 静态方法直接通过类方法名来调用
Animal.eat('猫');

// get 和 set
class Phone {
get price() {
console.log("价格属性被读取了");
return '返回值';
}

// set 必须要有参数
set price(newVal) {
console.log(newVal) // free
console.log('价格属性被修改了');
}
}

// 实例化对象
let s = new Phone();
let result = s.price // 价格属性被读取了
console.log(result) // 返回值
s.price = 'free'; // 价格属性被修改了

注意:this指向

  1. 在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象.

  2. 类里面的共有属性和方法一定要加this使用.

  3. 类里面的this指向问题.

  4. constructor里面的this指向实例对象, 方法里面的this 指向这个方法的调用者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<button>点击</button>
var that;
class Star {
constructor(uname, age) {
// constructor 里面的this指向的是创建的实例对象
that = this;
this.uname = uname;
// this.sing();
this.btn = document.querySelector('button');
this.btn.onclick = this.sing.bind(this);
// 或者 this.btn.onclick = this.sing;// 下面写that,不推荐封装不了完整的函数
}
sing() {
// 这个sing方法里面的this指向的是btn这个按钮,因为这个按钮调用了这个函数
console.log(this.uname);
// console.log(that.uname); //that里面存储的是constructor里面的this
}
}

var ldh = new Star('刘德华');
console.log(that === ldh);

apply、call与bind

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
window.color = "red";
var o = {color: "blue"}

function sayColor() {
console.log(this.color);
}

sayColor.call(window); //red
sayColor.call(o); //blue
sayColor.apply(o); //blue

// apply、call区别
// thisObj:call 和 apply 第一个参数是一样的,该参数将替代 function 类里面的 this。
// arg1, arg2....:是一个个的参数
// [args]:一个数组
call(thisObj, arg1, arg2, arg3, arg4);
apply(thisObj, [args]);

// bind
window.color = "red";
var o = {color: "blue"}
// bind不会立即调用,而是返回一个新函数
// 注意不能直接在立即调用的函数后面加bind会报错
var sayColor = function () {
console.log(this.color);
}.bind(o)

sayColor(); //blue

// 总结
// 当我们使用一个函数需要改变this指向的时候才会用到call,apply,bind
// 如果你要传递的参数不多,则可以使用fn.call(thisObj, arg1, arg2 ...)
// 如果你要传递的参数很多,则可以用数组将参数整理好调用fn.apply(thisObj, [arg1, arg2 ...])
// 如果你想生成一个新的函数长期绑定某个函数给某个对象使用,则可以使用bind
const fn = function(){}.bind()
fn(xx,xx)
1
2
3
4
5
6
7
8
9
10
11
12
13
function a() {
console.log(1);
}

var btn = document.querySelector('button')
// 自动直接调用
btn.onclick = a()
// 点击才会调用
btn.onclick = a
// 放在匿名函数中,这个函数不会自动触发
btn.onclick = function () {
a()
}

Json格式的转化

1
2
3
4
5
6
7
8
9
var obj = {name: 'Q', age: 18}
var strJson = JSON.stringify(obj) //对象变成了json字符串
var newObj = JSON.parse(strJson) //json字符串变成了对象

// JSON.parse()必须使用双引号包裹
let a = '["1","2"]';
let b = "['1','2']";
console.log(JSON.parse(a));// Array [1,2]
console.log(JSON.parse(b));// 报错

构造函数和原型导读

构造函数

构造函数的成员:实例成员-静态成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 构造函数中的属性和方法我们称为成员, 成员可以添加
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function() {
console.log('我会唱歌');

}
}
var ldh = new Star('刘德华', 18);
// 1.实例成员就是构造函数内部通过this添加的成员 uname age sing 就是实例成员
// 实例成员只能通过实例化的对象来访问
console.log(ldh.uname);
ldh.sing();
// console.log(Star.uname); // 不可以通过构造函数来访问实例成员
// 2. 静态成员 在构造函数本身上添加的成员 sex 就是静态成员
Star.sex = '男';
// 静态成员只能通过构造函数来访问
console.log(Star.sex);
console.log(ldh.sex); // 不能通过对象来访问,为undefined

注意:构造函数每次实例化生成的函数会开辟新的内存来储存,造成存在浪费内存的问题

解决:采用prototype。

原型:prototype

构造函数通过原型分配的函数是所有对象所共享的。

1
2
3
4
5
6
// 构造函数使用原型(存方法),不会存在内存的浪费;
Star.prototype.sing = function() {
console.log('我会唱歌');
}
ldh.sing();
// 不能console.log(ldh.prototype),直接报错;

对象原型 proto

1
2
3
4
5
6
console.log(ldh.__proto__)
// 注意有两个下划线,指向我们构造函数的原型对象 prototype
// 方法的查找规则: 首先先看ldh 对象身上是否有 sing 方法,如果有就执行这个对象上的sing
// 如果么有sing 这个方法,因为有__proto__ 的存在,
// 就去构造函数原型对象prototype身上去查找sing这个方法
console.log(ldh.__proto__ === Star.prototype);

原型的constructor构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 很多情况下,我们需要手动的利用constructor 这个属性指回原来的构造函数
// 原型里面的constructor是构造函数本身
// 一个为构造函数、一个为函数里面的构造函数
console.log(Star.prototype.constructor === Star.constructor) // false

// 方法放在一个对象里面
Star.prototype = {
// 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,
// 则必须手动的利用constructor指回原来的构造函数。
constructor: Star,
sing: function() {
console.log('我会唱歌');
},
movie: function() {
console.log('我会演电影');
}
}

原型链

成员查找规则:

  1. 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。

  2. 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)。

  3. 如果还没有就查找原型对象的原型(Object的原型对象)。

  4. 依此类推一直找到 Object 为止(null)。

  5. __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。


构造函数的继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 借用父构造函数继承属性
// 1. 父构造函数
function Father(uname) {
// this 指向父构造函数的对象实例
this.uname = uname;
}
Father.prototype.money = function() {
console.log(100000);

};
// 2 .子构造函数
function Son(uname, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname);
this.score = score;
}
// Son.prototype = Father.prototype;
// 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
Son.prototype = new Father();
// 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数
Son.prototype.constructor = Son;
// 这个是子构造函数专门的方法
Son.prototype.exam = function() {
console.log('孩子要考试');
}
var son = new Son('刘德华', 100);
console.log(son);
console.log(Father.prototype);
console.log(Son.prototype.constructor);

JavaScript中的新增方法

数组方法

迭代(遍历)方法:forEach()map()filter()some()every()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
array.forEach(function(currentValue,index, arr){
// forEach()允许callback更改原始数组的元素
// 不能使用break
arr1[index] = item/2;
})

var arr = array.map(function(currentValue,index, arr){
// map()会分配内存空间存储新数组并返回
return currentValue % 2;
})

var arr = array.filter(function(currentValue,index, arr){
// 也是查找满足条件的元素 返回的是一个数组 而且是把所有满足条件的元素返回回来
// filter会将结果为true的数组存到新的数组里面,而map返回的是true和false。
return currentValue % 2 === 0;
})

var flag = array.some(function(currentValue,index, arr){
// 查找满足条件的元素是否存在,返回的是一个布尔值,如果查找到第一个满足条件的元素就终止循环
return currentValue == 'xxx';
// 在some里面遇到return true就会退出循环
})

字符串方法

trim()方法会从一个字符串的两端删除空白字符。

1
str.trim()

对象方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Object.keys() 
// 效果类似 for…in,返回一个由属性名组成的数组
var o = {
a: 1,
b: 2
}
console.log(Object.keys(o))

// Object.defineProperty()
Object.defineProperty(obj,prop, descriptor)
// obj:必需。目标对象
// prop:必需。需定义或修改的属性的名字
// descriptor:必需。目标属性所拥有的特性

// descriptor是一个对象{},包含四个参数。
// value:设置属性的值  默认为undefined
// writable:值是否可以重写。true |false  默认为false
// enumerable:目标属性是否可以被枚举。true |false 默认为 false
// configurable:目标属性是否可以被删除或是否可以再次修改特性 true|false  默认为false
delete obj.a; // 删除对象的某个属性

函数进阶

函数的定义方式

1
2
3
4
5
6
7
8
9
10
// 1. 函数声明方式 function 关键字 (命名函数)
function fn() {};
// 2. 函数表达式 (匿名函数)
var fun = function() {};
// 3. 利用 new Function('参数1','参数2', '函数体');
var f = new Function('a', 'b', 'console.log(a + b)');
f(1, 2);
// 4. 所有函数都是 Function 的实例(对象)
// 5. 函数也属于对象
console.log(x instanceof Object) // true // 数组、函数都属于对象

函数的调用方式

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
// 1. 普通函数
function fn() {
console.log('人生的巅峰');

}
// fn(); fn.call()

// 2. 对象的方法
var o = {
sayHi: function() {
console.log('人生的巅峰');

}
}
o.sayHi();

// 3. 构造函数
function Star() {};
new Star();

// 4. 绑定事件函数
// btn.onclick = function() {}; // 点击了按钮就可以调用这个函数

// 5. 定时器函数
// setInterval(function() {}, 1000); 这个函数是定时器自动1秒钟调用一次

// 6. 立即执行函数
(function() {
console.log('人生的巅峰');
})();
// 立即执行函数是自动调用

改变函数this指向

常用的有 bind()call()apply() 三种方法。

  1. call方法调用一个对象,可以改变函数的this指向。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // fun.call(thisArg,arg1, arg2, ...) 
    var o = {
    name: 'andy'
    }

    function fn(a, b) {
    console.log(this);
    console.log(a + b);
    };
    fn.call(o, 1, 2);
  2. apply方法,参数必须是数组(伪数组)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // fun.apply(thisArg,[argsArray])
    var o = {
    name: 'andy'
    };

    function fn(arr) {
    console.log(this);
    console.log(arr); // 'pink'

    };
    fn.apply(o, ['pink']);

    // Math.max();
    var arr = [1, 66, 3, 99, 4];
    var max = Math.max.apply(null, arr);
    var max = Math.max.apply(Math, arr);
    var max = Math.max(...arr)
  3. bind方法不会调用函数,改变函数内部this 指向

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // fun.bind(thisArg,arg1, arg2, ...) 

    btn.onclick = function () {
    console.log(this)
    // bind写在定时器函数的外面,this指向btn
    setTimeout(function () {
    console.log(this)
    }.bind(this), 1000)
    // 使用箭头函数
    setTimeout(() => {
    console.log(this)
    }, 1000)
    }
    // 在面向对象中,传递参数改变this
    function f(that) {
    console.log(this)
    console.log(that)
    }
    btn.onclick = f.bind(btn,window)

严格模式

JavaScript除正常模式外,还有严格模式(strict mode),在严格的条件下运行 JS 代码。

严格模式有两种情况,应用到整个脚本或个别函数中。

1
2
3
4
5
6
7
8
9
10
// 整个脚本的严格模式
<script>
'use strict';
</script>
// 单个函数里面使用严格模式
<script>
(function() {
'use strict';
})();
</script>
严格模式的规范
  • 变量规定

    1. 我们的变量名必须先声明再使用。

    2. 我们不能随意删除已经声明好的变量,delete num

  • this 指向问题

    1. 严格模式下全局作用域中函数中的 thisundefined

    2. 严格模式下,如果构造函数不加new调用, this指向的是undefined 如果给他赋值则会报错。

    3. 定时器this还是指向window

  • 函数变化

    1. 严格模式下函数里面的参数不允许有重名。
    2. if、for里面不写声明函数。

更多参考:严格模式 - JavaScript | MDN (mozilla.org)

高阶函数

高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。

1
2
3
4
5
// 作为参数
function f(callBack) {callBack()}
function f1() {console.log(1)}
// 返回函数
function f() {return function f1() {console.log(1)}}

闭包

闭包(closure)指有权访问另一个函数作用域中变量的函数。

主要作用:延伸了变量的作用范围。

1
2
3
4
5
6
7
8
9
10
11
12
function f() {
var num = 1
return function () {
return num
}
}

var num = f()()
console.log(num)
num = null
console.log(num)
// 闭包中数据常驻内存,不用时需要删掉,否则容易内存溢出。

for循环是同步,而点击则是异步,里面的i已经是之后的值了。

1
2
3
4
5
6
7
8
9
10
11
// 利用闭包的方式得到当前小li 的索引号
for (var i = 0; i < lis.length; i++) {
// 利用for循环创建了4个立即执行函数
// 立即执行函数也成为小闭包因为立即执行函数里面的任何一个函数都可以使用它的i这变量
(function(i) {
lis[i].onclick = function() {
console.log(i);
}
})(i);
}
// 上的方法有内存泄漏,创建了多个立即执行函数、古还是使用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
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
var num = 1;
function fn() {
console.log(1);
if (num === 2) {
return
}
num++;
fn();
}
fn();

// 利用递归求和,或者阶乘(+ 换成 *)
function fn(n) {
if (n == 1) {
return 1;
}
return n + fn(n - 1);
}

// 斐波那契数列,1、1、2、3、5、8、13、21...
// 前两项的和等于后面一项
// 用户输入第几个则得出斐波那契数列中该位置的值
function x(n) {
if (n === 1 || n === 2) {
return 1;
}
return x(n - 1) + x(n - 2);
}

// 递归树
var data = [
{id: 1, name: "我是爸爸1", parent_id: 0},
{id: 2, name: "我是爸爸2", parent_id: 0},
{id: 3, name: "我是儿子3", parent_id: 1},
{id: 4, name: "我是孙子4", parent_id: 3},
{id: 5, name: "我是重孙5", parent_id: 4},
{id: 6, name: "我是儿子6", parent_id: 2},
{id: 7, name: "我是孙子7", parent_id: 6},
{id: 8, name: "我是重孙8", parent_id: 7},
{id: 9, name: "我是爸爸9", parent_id: 0},
{id: 10, name: "我是儿子10", parent_id: 9},
{id: 11, name: "我是孙子11", parent_id: 10},
{id: 12, name: "我是儿子12", parent_id: 1}
];

function tree(x) {
// x参数 接收的是我们需要规定为1级分类的id
let treeArr = []
data.filter(item => {
return item.parent_id === x;
}).forEach(item => { //item是已经比对过的parent_id 0 的一级
treeArr.push(
{
id: item.id,
label: item.name,
children: tree(item.id) //这里再次调用自己
}
)
})
return treeArr
}

console.log(tree(0))

深拷贝与浅拷贝

  1. 浅拷贝只是拷贝一层, 更深层次对象级别的只拷贝引用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 使用Object.assign(target, ...sources),对象也可以使用for in,数组forEach、push
    var obj1 = {a: 1}
    var obj2 = Object.assign({}, obj1);
    obj2.a = 2;
    console.log(obj1,obj2);

    var arr1 = [{a: 1}, {b: 2}]
    var arr2 = Object.assign([], arr1);
    arr2[0] = {c: 3}
    console.log(arr1, arr2);
  2. 深拷贝拷贝多层, 每一级别的数据都会拷贝。

    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
    // 使用JSON转字符串,再解析成对象
    // 注意obj中有函数、undefined、null、RegExp,会出现问题。
    var obj = {
    a: 1,
    b: {
    c: 2
    }
    }
    var o = JSON.parse(JSON.stringify(obj));
    o.b.c = 3
    console.log(obj,o)

    // 实现深拷贝
    function deepClone(target) {
    // 定义一个变量
    let result;
    // 如果当前需要深拷贝的是一个对象的话
    if (typeof target === 'object') {
    // 如果是一个数组的话
    if (Array.isArray(target)) {
    result = []; // 将result赋值为一个数组,并且执行遍历
    for (let i in target) {
    // 递归克隆数组中的每一项
    result.push(deepClone(target[i]))
    }
    // 判断如果当前的值是null的话;直接赋值为null
    } else if (target === null) {
    result = null;
    // 判断如果当前的值是一个RegExp对象的话,直接赋值
    } else if (target.constructor === RegExp) {
    result = target;
    } else {
    // 否则是普通对象,直接for in循环,递归赋值对象的所有值
    result = {};
    for (let i in target) {
    result[i] = deepClone(target[i]);
    }
    }
    // 如果不是对象的话,就是基本数据类型,那么直接赋值
    } else {
    result = target;
    }
    // 返回最终结果
    return result;
    }

正则表达式

正则表达式( Regular Expression )是用于匹配字符串中字符组合的模式。在JavaScript中,正则表达式也是对象。

创建正则表达式

1
2
3
4
5
6
7
// 1. 利用 RegExp对象来创建 正则表达式
var regexp = new RegExp(/123/);

// 2. 利用字面量创建 正则表达式
var rg = /123/;

// 注意//里面不需要加''

测试正则表达式test

test() 正则对象方法,用于检测字符串是否符合该规则,该对象会返回 truefalse,其参数是测试字符串。

1
2
3
// test 方法用来检测字符串是否符合正则表达式要求的规范
console.log(rg.test(123));
console.log(rg.test('abc')); // false

正则表达式的组成

正则表达式可由简单和特殊字符组合,比如 /ab*c/,特殊字符称为元字符,如 ^$+

正则表达相关链接

正则表达式中的特殊字符

1.边界符

正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符。

1
2
3
4
5
6
//var rg = /abc/;  只要包含abc就可以 
// 精确匹配 要求必须是 abc字符串才符合规范
var reg = /^abc$/;
console.log(reg1.test('abc')); // true
console.log(reg1.test('abcd')); // false
console.log(reg1.test('abcabc')); // false

2.字符类

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
// 字符类: [] 表示有一系列字符可供选择,只要匹配其中一个就可以了
var rg = /[abc]/;
// 只要包含有a 或者、包含有b、或者包含有c,都返回为true
console.log(rg.test('andy')); // true
console.log(rg.test('red')); // false

var rg1 = /^[abc]$/;
// 三选一只有是a、或者是 b、或者是c,这三个字母才返回true
console.log(rg1.test('a')); // true
console.log(rg1.test('aa')); // false
console.log(rg1.test('abc')); // false

var reg = /^[a-z]$/;
// 26个英文字母任何一个字母返回true,-表示的是a到z的范围
console.log(reg.test('a')); // true
console.log(reg.test(1)); // false
console.log(reg.test('A')); // false

// 字符组合
// 26个英文字母(大写和小写都可以)任何一个字母返回 true
var reg1 = /^[a-zA-Z0-9_-]$/;
console.log(reg1.test(8)); // true
console.log(reg1.test('!')); // false

// []加了^,就是排除里面输入的,只要有其他字符为true
var rg = /^[^a-zA-Z0-9]$/
console.log(rg.test('!')) // true

3.量词符

量词符用来设定某个模式出现的次数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//  * 相当于 >= 0 可以出现0次或者很多次 
var reg = /^a*$/;
console.log(reg.test('')); // true
console.log(reg.test('a')); // true
console.log(reg.test('aaaa')); // false

// + 相当于 >= 1 可以出现1次或者很多次
var reg = /^a+$/;
console.log(reg.test('')); // false
console.log(reg.test('a')); // true
console.log(reg.test('aaaa')); // true

// ? 相当于 1 || 0,只能是0、1
var reg = /^a?$/;
console.log(reg.test('')); // true
console.log(reg.test('a')); // true
console.log(reg.test('aaaa')); // false

// {3 } 就是重复3次
var reg = /^a{3}$/;
console.log(reg.test('aaa')); // true
console.log(reg.test('aaaa')); // false

// {3, } 大于等于3
var reg = /^a{3,}$/;
console.log(reg.test('a')); // false
console.log(reg.test('aaa')); // true

// {3,16} 大于等于3 并且 小于等于16
var reg = /^a{3,6}$/;
console.log(reg.test('a')); // false
console.log(reg.test('aaa')); // true

4.括号总结

1
2
3
4
5
6
7
8
9
var reg = /^abc{3}$/; 
// 它只是让c重复三次,abccc
console.log(reg.test('abcabcabc')); // false
console.log(reg.test('abccc')); // true

// 小括号表示优先级
var reg = /^(abc){3}$/;
console.log(reg.test('abcabcabc')); // true
console.log(reg.test('abccc')); // false

5.预定义类

预定义类指的是某些常见模式的简写方式。

1
2
3
// 座机号码验证两种格式: 010-12345678 或者 0530-1234567
// 正则里面的或者符号|
var reg = /^\d{3}-\d{8}|\d{4}-\d{7}$/;

正则替换

正则表达式参数:/表达式/[switch],有三种值:

  • g:全局匹配
  • i:忽略大小写
  • gi:全局匹配 + 忽略大小写
1
2
3
4
// 替换 replace
btn.onclick = function() {
div.innerHTML = text.value.replace(/a|b/g, '**');
}

ES6新增

ES6实际上是一个泛指,泛指ES2015及后续的版本。

声明变量的关键字

let关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// let声明的变量只在所处于的块级{}里面有效
if (true) {
var a = 1;
let b = 2
}
console.log(a) // 1
console.log(b) // 报错

// 防止循环变量变成全局变量
for (let i = 0; i < 2; i++) {}
console.log(i); // 报错

// 不存在变量提升
console.log(a)
let a = 1

// 使用let关键字声明的变量具有暂时性死区特性
var num = 10
if (true) {
console.log(num); // 因为有let,不会受到外部影响故报错
let num = 20;
}

const关键字

声明常量,常量就是值(内存地址)不能变化的量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 使用const关键字声明的常量具有块级作用域
if (true) {
const a = 10;
}
console.log(a); // 报错

// 使用const关键字声明的常量必须赋初始值
const PI = 3.14;

// 常量声明后值不可更改,引用类地址不能修改但值可以。
const PI = 3.14;
PI = 100; // 不可修改
const o = {a: 1};
o = {a: 2} // 不可修改
o.a = 2 // 可修改

var、let、const区别

var let const
函数级作用域 块级作用域 块级作用域
变量提升 不存在变量提升 不存在变量提升
值可更改 值可更改 值不可更改

解构赋值

数组解构

1
2
3
4
5
6
7
8
// 按照一一对应的关系从数组中提取值然后将值赋值给变量。
let ary = [1,2,3];
let [a, b, c] = ary;
console.log(a,b,c) // 1,2,3

// 如果解构不成功,变量的值为undefined。
let [d] = []; // console.log(d)为undefined
let [d, f] = [1]; // console.log(f)为undefined

对象解构

1
2
3
4
5
6
7
8
9
// 使用变量的名字匹配对象的属性,匹配成功将对象属性的值赋值给变量
let o = {a: 1, b: 2}
let {a, b} = o
console.log(a, b)

// 定义不同的变量名称
let o = {a: 1, b: 2}
let {a: c, b: d} = o
console.log(c, d)

箭头函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 箭头函数是用来简化函数定义语法的
const fn = () => { console.log(123) }
fn();

// 一句代码结果是返回值,可以省略大括号
const sum = (n1, n2) => n1 + n2;
const result = sum(10, 20);
console.log(result)

// 形参只有一个,小括号可以省略
const fn = v => {
alert(v);
}
fn(20)

箭头函数不绑定this关键字

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
// 指向的是函数定义位置的上下文this
function fn () {
console.log(this);
return () => {
console.log(this) // 这个this指向fn的this,如果没有就一直往上找
}
}
const obj = {name: 'moxie'};
const resFn = fn.call(obj); // obj
resFn() // obj

// ----------

const obj = {
fn: () => {
console.log(this) // 这个this指向window,obj没有作用域
},
}
obj.fn() // window

function f() {
const obj = {
fn: () => {
console.log(this) // 这个this指向f
}
}
obj.fn() // {}
}
f.call({})

箭头函数剩余参数

剩余参数是实参大于形参,箭头函数没有arguments

1
2
3
4
5
6
// 剩余参数使用
const fn = (...args) => {
console.log(args)
};
fn(10, 20, 30)
// 注意rest参数必须放在最后

拓展运算符

扩展运算符...可以将数组或者对象转为用逗号分隔的参数序列。

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
// 数组中的使用
// 1.直接使用...
let ary = [1, 2, 3]
// ...ary代表1, 2, 3
console.log(...ary) // 1 2 3
console.log(1, 2, 3) // 1 2 3
// 使用 Math() 函数
const arr1 = [1, 2, 3]
console.log(Math.min(...arr1)) // 1
console.log(Math.max(...arr1)) // 3

// 2.复制数组,注意的是这是一个浅拷贝
const arr1 = [1,2,3];
const arr2 = [...arr1];
console.log(arr2); // [ 1, 2, 3 ]

// 3.合并数组
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
console.log([...arr1, ...arr2]) // [ 1, 2, 3, 4, 5, 6 ]
console.log(arr1.concat(arr2)) // [ 1, 2, 3, 4, 5, 6 ]
// 使用push可以将arr2追加到arr1
arr1.push(...arr2)
console.log(arr1) // [ 1, 2, 3, 4, 5, 6 ]

// 4.数组添加元素
let arr = ['a', 'b'];
arr = [...arr, 'c'];
console.log(arr); // ['a', 'b', 'c']

// 5.伪数组转换为真正的数组
var oDivs = document.getElementsByTagName('div');
var ary = [...oDivs];
ary.push('a'); // 转化成真正的数组可以使用数组方法
console.log(ary);

// ---------------------------------------

// 对象中的使用
// 向对象添加属性
let obj = {
a: 1,
b: 2
};
obj = {...obj, c: 3};
console.log(obj) // {a: 1, b: 2, c: 3}

// ---------------------------------------

// 字符串的使用
// 1.直接使用
let str = 'abc'
// ...str代表'a','b','c'
console.log(...str)
console.log('a','b','c')

// 2.展开字符串变成数组
const str = 'abc';
const arr = [...str];
console.log(arr); // ['a', 'b', 'c']

// ---------------------------------------

// 解构中的使用
// 1.在解构对象的使用
const obj = {a: 1, b: 2, c: 3}
const {a, ...rest} = obj;
console.log(a, rest); // 1 {b: 2, c: 3}

// 2.在解构数组的使用
const arr = [1, 2, 3]
const [a,...rest] = arr;
console.log(a, rest); // 1 [2, 3]

Array 的扩展方法

  • Array.from()将类数组或可遍历对象转换为真正的数组
1
2
3
4
5
6
7
8
9
// 必须要有索引和length
var arr = {
"0": "1",
"1": "2",
"length": 2
}
// Array.from()有两个参数,第一个为伪数组、后为一个处理函数
var arr1 = Array.from(arr, item => item * 2)
console.log(arr1) // [2, 4]
  • Array.find()用于找出第一个符合条件的数组成员,返回为当前成员,如果没有找到返回undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var ary = [
{
id: 1,
name: '张三'
},
{
id: 2,
name: '李四'
},
{
id: 1,
name: '王五'
}
];
let target = ary.find(item => item.id === 1);
console.log(target) // {id: 1, name: '张三'}

let arr1 = ary.filter(item => item.id === 1)
console.log(arr1) // [{id: 1, name: '张三'},{id: 1, name: '王五'}]
  • Array.findIndex()用于找出第一个符合条件的数组成员的位置,如果没有找到返回-1
1
2
3
let ary = [10, 20, 50];
let index = ary.findIndex(item => item > 15);
console.log(index) // 1
  • Array.includes()表示某个数组是否包含给定的值,返回布尔值。
1
2
3
let ary = ["a", "b", "c"];
let result = ary.includes('a')
console.log(result) // true

String 的扩展方法

  • 模板字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 模板字符串使用${}读取变量
let str = 'b';
let str1 = `a${str}`;
console.log(str1) // ab

// 模板字符串可以换行
let str = `第一行
第二行`
console.log(str)

// 模板字符串可以调用函数得到返回值
const fn = () => {
return '我是fn函数'
}

let html = `我是模板字符串 ${fn()}`;
console.log(html) // 我是模板字符串 我是fn函数
  • startsWith() 和 endsWith()
1
2
3
4
5
// startsWith():表示参数字符串是否在原字符串的头部,返回布尔值
// endsWith():表示参数字符串是否在原字符串的尾部,返回布尔值
let str = 'Hello world!';
console.log(str.startsWith('Hello')) // true
console.log(str.endsWith('!')) // true
  • repeat()方法表示将原字符串重复n次,返回一个新字符串。
1
console.log("y".repeat(5)) // 'yyyyy'

Set 数据结构

ES6提供了新的数据结构Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

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
// 使用Set数据,接受一个数组作为参数,用来初始化。
const s1 = new Set([1,2]);
// size代表Set数据中的个数
console.log(s1.size) // 2

// 使用Set来处理数组去重
const s3 = new Set(["a","a","b","b"]);
const ary = [...s3];
console.log(ary) // ['a', 'b']

//-------------------------

// 实例方法
// 1. add(value):添加某个值,返回 Set 结构本身
const s4 = new Set();
// 向set结构中添加值 使用add方法
s4.add('a').add('b');
console.log(s4.size) // 2

// 2. delete(value):删除某个值,返回一个布尔值,表示删除是否成功
// 从set结构中删除值 用到的方法是delete
const s4 = new Set(['a', 'b']);
const r1 = s4.delete('a');
console.log(s4.size) // 1
console.log(r1); // true

// has(value):返回一个布尔值,表示该值是否为 Set 的成员
// 判断某一个值是否是set数据结构中的成员 使用has
const s4 = new Set(['a', 'b']);
const r2 = s4.has('c');
console.log(r2) // false

// clear():清除所有成员,没有返回值
// 清空set数据结构中的值 使用clear方法
s4.clear();
console.log(s4.size); // 0

// -------------------------

// Set数据遍历,从中取值
const s5 = new Set(['a', 'b', 'c']);
s5.forEach(value => {
console.log(value)
})

Map数据结构

类似于对象,键名可以是各种类型的值,Map实现 iterator接口,可以使用...for of进行遍历。

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
// 声明 Map
let m = new Map();

// 添加元素
m.set('name', 'root');

let key = {
name: 'admin'
};

m.set(key, [1, 2]);

// size
console.log(m.size); // 2

// 遍历
for (let v of m) {
console.log(v); // []形式,如:['name', 'root']
}

// 删除
m.delete('name');

// 获取
console.log(m.get(key)); // [1, 2]

// 清空
m.clear();

简化对象的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。
//这样的书写更加简洁
let name = 'moxie';
let change = function(){
console.log('我们可以改变你!!');
}

const obj = {
name,
change,
improve(){
console.log("我们可以提高你的技能");
}
}
console.log(obj);

函数的默认参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ES6 允许给函数参数赋值初始值
// 1. 形参初始值具有默认值的参数, 一般位置要靠后(潜规则)
function add(a, b, c = 3) {
return a + b + c;
}

console.log(add(1, 2)); // 6

// 2. 与解构赋值结合
function fn({a, b, c: d = 3}) {
console.log(a, b, d) // admin, 18, 3
}

fn({a: 1, b: 2})

Symbol

ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,是一种类似于字符串的数据类型。

特点

  • Symbol 的值是唯一的,用来解决命名冲突的问题
  • Symbol 值不能与其他数据进行运算
  • Symbol 定义的对象属性不能使用 for…in 循环遍历,但是可以使用 Reflect.ownKeys 来获取对象的所有键名
1
2
3
4
5
6
7
8
9
10
11
12
// 创建Symbol
let s = Symbol('moxie'); // 里面的参数相当于一个标识
let s1 = Symbol('moxie');
console.log(typeof s) // symbol
console.log(s === s1) // false,唯一性
// Symbol.for 创建
// 上为函数,现在是函数对象之前有就返回一个,没有就创建一个
let s2 = Symbol.for('默谐');
let s3 = Symbol.for('默谐');
console.log(s2 === s3) // true,

// 不能与其他数据进行运算

Symbol的使用

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
// 向对象中添加方法
// 场景:不清楚该对象是否拥有一个同名的可以使用Symbol
let obj = {
uname: function () {
console.log('初始内容')
},
};
let uname = Symbol('uname') // 或者使用一个对象let method = {}
obj[uname] = function () {
console.log('追加的内容')
}
obj['uname']() // 初始内容
obj[uname]() // 追加的内容

// 第二中方法
let obj = {
uname: function () {
console.log('初始内容')
},
[uname = Symbol('uname')]: function () {
console.log("追加的内容")
},
};
obj['uname']() // 初始内容
obj[uname]() // 追加的内容

Symbol 内置值

Symbol.hasInstance 当其他对象使用 instanceof 运算符,判断是否为该对 象的实例时,会调用这个方法
Symbol.isConcatSpreadable 对象的 Symbol.isConcatSpreadable 属性等于的是一个 布尔值,表示该对象用Array.prototype.concat()时, 是否可以展开。
Symbol.species 创建衍生对象时,会使用该属性
Symbol.match 当执行 str.match(myObject) 时,如果该属性存在,会 调用它,返回该方法的返回值。
Symbol.replace 当该对象被 str.replace(myObject)方法调用时,会返回 该方法的返回值。
Symbol.search 当该对象被 str.search (myObject)方法调用时,会返回 该方法的返回值。
Symbol.split 当该对象被 str.split (myObject)方法调用时,会返回该 方法的返回值。
Symbol.iterator 对象进行 for...of 循环时,会调用 Symbol.iterator 方法, 返回该对象的默认遍历器
Symbol.toPrimitive 该对象被转为原始类型的值时,会调用这个方法,返 回该对象对应的原始类型值。
Symbol. toStringTag 在该对象上面调用 toString 方法时,返回该方法的返 回值
Symbol. unscopables 该对象指定了使用 with 关键字时,哪些属性会被 with 环境排除。

迭代器

遍历器Iterator一种接口,用来访问不同数据结构。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。

  • ES6 创造了一种新的遍历命令 for...of 循环,Iterator 接口主要供 for...of遍历。
  • 原生具备 iterator 接口的数据(可用 for of 遍历),Array、Arguments 、Set 、Map 、String 、TypedArray 、NodeList
  • 工作原理
    1. 创建一个指针对象,指向当前数据结构的起始位置
    2. 第一次调用对象的 next 方法,指针自动指向数据结构的第一个成员
    3. 接下来不断调用 next 方法,指针一直往后移动,直到指向最后一个成员
    4. 每调用 next 方法返回一个包含 value 和 done 属性的对象
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
// 使用 for...of 遍历数组
const arr = ['a', 'b'];
for (let v of arr) {
console.log(v); // a b
}
// 工作原理
let iterator = arr[Symbol.iterator]();
//调用对象的next方法
console.log(iterator.next()); // {value: 'a', done: false}
console.log(iterator.next()); // {value: 'b', done: false}
console.log(iterator.next()); // {value: undefined, done: true}

// -----------------------
// 自定义对象的 for of 循环
const user = {
name: "默谐",
stus: [
'a',
'b',
],
[Symbol.iterator]() {
let index = 0;
let _this = this;
return {
next: function () {
if (index < _this.stus.length) {
const result = { value: _this.stus[index], done: false };
index++; // 下标自增
return result; // 返回结果
}else{
return {value: undefined, done: true};
}
}
};
}
}
for (let v of banji) {
console.log(v);
}

生成器

生成器函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。

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
// 生成器函数的使用,声明函数加*
function* fn(a, ...b) {
console.log(a, b); // 1 [2, 3]
// yield相当于分割函数,next先执行前面代码,再次next执行后面代码
let c = yield '分割'
console.log(c); // 使用next方法传递参数
}

// 调用函数生成迭代器对象
let iterator = fn(1, 2, 3); // 调用传入实参
console.log(iterator.next()); // {value: '分割', done: false}
// next方法传入实参,作为上一个yield的返回值
console.log(iterator.next('使用next方法传递参数')); // {value: undefined, done: true}

// ---------------------------

// 案例
function one() {
setTimeout(() => {
console.log(111);
// next执行下一个yield
iterator.next();
}, 1000)
}

function two() {
setTimeout(() => {
console.log(222);
iterator.next();
}, 2000)
}

function* gen() {
yield one();
yield two();
}

//调用生成器函数
let iterator = gen();
iterator.next();

promise

Promise 是 ES6 引入的异步编程的新解决方案。语法上 Promise 是一个构造函数, 用来封装异步操作并可以获取其成功或失败的结果。

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
//实例化 Promise 对象
const p = new Promise(function (resolve, reject) {
setTimeout(function () {
// resolve('成功');
reject('失败');
}, 1000);
});

//调用 promise 对象的 then 方法
p.then(function (value) {
console.log(value); // 成功
}, function (reason) {
console.error(reason); // 失败
})
// 直接使用catch就是失败的回调
p.catch(function (reason) {
console.warn(reason); // 失败
})

// 链式调用then方法的状态判断
// 1. 返回结果不是promise对象,then状态为成功
// 2. 返回结果是promise对象,then状态为返回的promise的状态
const p1 = new Promise((resolve) => {
setTimeout(() => {
console.log(1)
resolve("2")
}, 1000)
})

var p2 = p1.then(res => {
console.log(res)
return new Promise((resolve) => {
setTimeout(() => {
resolve(3)
},1000)
})
})
p2.then((res)=>{
console.log(res)
})

promise的使用及其他

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
// 封装Promise函数
const fetchRequest = (url) => {
return new Promise(async (resolve, reject) => {
let response = await fetch(url);
if (response.status === 200) {
resolve(response);
} else {
reject('出错了');
}
})
}

// 尝试故意将接口地址改错
fetchRequest('xxx').then((response) => {
console.log(response)
}).catch((err) => {
console.error(err)
})

// Promise.all 将多个 Promise 实例,包装成一个新的 Promise 实例。
const onePromise = fetchRequest('xxx1')
const twoPromise = fetchRequest('xxx2')
// Promise.all接受一个数组[],里面是两个Promise
Promise.all([
onePromise,
twoPromise
]).then((res) => {
console.log(res)
}).catch(err => {
console.error(err)
})

// Promise.race()里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
const threePromise = fetchRequest('xxx')
const fourPromise = fetchRequest('xxx')
// Promise.race接受一个数组[],里面是两个Promise
Promise.race([
threePromise,
fourPromise
]).then((res) => {
console.log(res)
}).catch(err => {
console.error(err)
})

// ------------------------

// try与catch
function f() {
try {
// 尝试执行代码块 ,检查是否有错误的代码块。
} catch (error) {
// 捕获错误的代码块,如果 try 语句发生错误执行的代码块。
// 如果 try 语句没发生错误该代码不会执行。
console.error(error)
}
}

ES6模块化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 1. 通用的导入方式
// 引入整个模块内容
import * as m from "./xxx.js";

// 2. 解构赋值形式
// export let m1 = 'a'
// export let m2 = 'b'
import {m1, m2} from "./xxx.js";
// 默认暴露的引入
import {default as m3} from "./xxx.js";

// 3. 简便形式,针对默认暴露
// export default {m1: 'moxie'}
import m from "./xxx.js";

// 出现重复的引入名定义别名
import m as m3 from "./xxx.js";

模块化方式二

1
2
3
4
5
6
// app.js文件模块引入
import * as m1 from "./m1.js";
import * as m2 from "./m2.js";
import * as m3 from "./m3.js";
// index.js使用
<script src="./app.js" type="module"></script>

防抖与节流

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
// 防抖
// 在第一次触发事件时,不立即执行函数,而是给出一个期限值
var input = document.querySelector("#input")
var timer;
input.onkeyup = function () {
// 停止操作后才执行1次
// 如果连续操作,则之前的延迟执行都被清除了

clearTimeout(timer)
timer = setTimeout(function () {
console.log("开始执行搜索...")
}, 1000);
}

// 节流
// 不断触发事件,但是在指定的单位时间内只触发一次
var input = document.querySelector("#input")
var lastTime = 0;
input.onkeyup = function () {
var nowTime = new Date().getTime();
if (nowTime - lastTime > 3000) {
console.log("开始执行搜索...")
lastTime = nowTime;
}
}