JavaScript高级
JavaScript高级
📚 Web 学习目录
🚀 HTML学习 - 📝 CSS学习 - 🔦 JavaScript学习 - 🎉 JavaScript 高级
本教程里的资料来源于网友的资料,自己整理以供学习。视频学习:黑马程序员
两大编程思想
(面向过程,面向对象)
面向过程pop
process-oriented programming
: 分析出问题所需的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个调用(蛋炒饭)
优点: 性能高,适合跟硬件紧密联系
缺点: 没有面向对象易维护、易复用、易扩展
面向对象oop
object-oriented programming
: 把事务分解成一个一个对象,然后对象之间分工与合作(盖浇饭)适用于复杂的项目
优点: 易维护、易复用、易扩展,可以设计出低耦合的系统。使系统更加灵活、更加易于维护
缺点: 性能比面向过程低
面向对象的特性
封装性: 即封装后无需知道原理,运行即可
继承性: 某个接口的功能继承于其父亲
多态性: 多个工作状态
面向对象的思维特点
抽取(抽象)对象公用的属性和行为阻止(封装)成一个类(模板)
对类进行实例化,获取类的对象
类
类抽象了对象的公共部分,泛指一大类
类和对象的区别: 对象是具体的,而类是所有同类具体对象的共同属性方法的集合。可以通过类实例化一个具体的对象
类(ES6)
类的创建
class name { // class body}
1 | class Star { |
类constructor构造函数
: 用于传递参数,返回实例,通过 new 命令生成对象实例时,自动调用该方法。如果没有显示定义,类内部会自动创建一个 constructor()。即将参数传递给实例对应的属性或方法。
创建实例
必须用new创建实例
var xx = new name(参数);
1 | var ldh = new Star('刘德华',20); |
注意:
通过 class 创建类,类名首字母大写
类里面有 constructor 函数,可以传递实参,同时返回实例, constructor 记得加小括号
只要 new 生成实例时,就会自动调用这个函数
生成实例 new 不能省略
创建类时类名后没有小括号,生成实例时类名后一定有小括号用于传递实参,构造函数不需要加 function()
类中添加方法
实质为添加在原型对象中
class name { 函数名(){}}
1 | class Star { |
注意:
直接添加在 constructor 之后
可传递参数
可添加多个方法
可在方法中调用该对象属性
添加类方法
在函数前面加 static 保留字即可
1
2
3
4
5
6class Big {
static big() {
console.log("i'm private!");
}
}
Big.big();
类的继承
子类可以继承父类的一些属性和方法
继承语法
class Son extends Father {}
1 | class Father { |
super 关键字
用于访问和调用对象父类上的函数。可以调用父类的构造函数,也可以调用父类的普通函数
利用 super 调用构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class Father {
constructor(x,y) {
this.x = x;
this.y = y;
} sum() {
console.log(this.x + this.y);
}
}
class Son extends Father {
constructor(z,v) {
super(z,v);
}
}
var son = new Son(1,2);
son.sum();即继承的本质是通过子类到父类中去执行函数,如果没有用 super 调用父类的函数,那么子类的形参无法传给父类,也就得不出结果。只有在子类的构造函数 constructor 中使用 super 调用父类的构造函数,才能将形参传给父类。
利用 super 调用普通函数
super.父类中的函数名()
1
2
3
4
5
6
7
8
9
10
11
12
13class Father {
say() {
return 'woshibaba';
}
}
class Son extends Father {
say() {
console.log(super.say() + 'deerzi');
}
}
var son = new Son();
son.say(); // 就近原则调用
// 继承中属性或者方法查找原则: 就近原则调用原则: 就近原则。如果子类调用了一个函数,则先查看子类中有无此函数,如果没有,就去父类中寻找,super 可视为等于f ather
子类继承父类方法同时扩展自己的方法
1 | class Father { |
重点: 构造函数里有 this 和 super 的时候,super 一定要写在this之前,父亲为大!
使用类注意事项
ES6 中没有变量提升,所以必须先定义类,才能通过类实例化对象
类里面的共有的属性和方法必须一定要加 this 使用
可以通过在构造函数里this.函数名直接调用方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14var that;
class Star {
constructor(uname,age) {
that = this;
//constructor里面的this指向创建的实例对象
this.uname = uname;
this.age =age;
this.sing();
}
sing() {
// this 指向实例
console.log(this);
}
}constructor 里面的 this 指向创建的实例对象,方法中的 this 指向调用者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20var that;
class Star {
constructor(uname,age) {
that = this;
//constructor里面的this指向创建的实例对象
this.uname = uname;
this.age =age;
this.sing();
this.btn = document.querySelector('button');
this.btn.onclick = this.dance;
}
sing() {
// this 指向实例
console.log(this);
}
dance() {
//指向btn的uname
console.log(this.uname);
}
}
ES6中的类的本质
- class 本质还是 function.
- 类的所有方法都定义在类的 prototype 属性上
- 类创建的实例里面也有
__proto__
指向类的 prototype 原型对象 - 所以 ES6 的类它的绝大部分功能, ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
- 所以 ES6 的类其实就是语法糖
- 语法糖语法糖就是一种便捷写法简单理解,有两种方法可以实现同样的功能但是一种写法更加清晰、方便
那么这个方法就是语法糖
扩展方法:
xx.select()
: 实现 xx 元素的选中xx.blur()
: 实现 xx 元素的失焦
构造函数和原型(ES5)
概述
在典型的 OOP 的语言中(如 Java ) , 都存在类的概念,类就是对象的模板,对象就是类的实例,但在 ES6 之前,
JS中并没用引入类的概念。ES6 前 JS 没有类,通过构造函数来定义
ES6 ,全称 ECMAScript6.0 , 2015.06发版。但是目前浏览器的 JavaScript 是 ES5 版本,大多数高版本的浏
览器也支持 ES6,不过只实现了 ES6 的部分特性和功能。
在 ES6 之前,对象不是基于类创建的,而是用一种称为构造函数
的特殊函数来定义对象和它们的特征。
创建对象三种方式
通过new Object创建对象
1
var obj1 = new Object();
通过字面量创建对象
1
var obj2 = {};
通过构造函数创建对象
1
2
3
4
5
6
7
8function Star(uname,age) {
this.uname = uname;
this.age = age;
this.sing = function() {
console.log('我会唱歌');
}
}
var ldh = new Star('刘德华',18);对象的增强写法
1
2
3
4
5
6
7var name = "zykj";
var age = 18;
var obj3 = {
name,
age
}
静态成员和实例成员
实例成员: 包括了实例方法和实例属性。
通过构造函数内部 this 创建的成员
,比如上面的 uname ,age , sing()
实例成员只能通过实例访问
1 | var ldh = new Star('刘德华',20); |
不能通过构造函数访问
1 | console.log(Star.uname); // 错误 |
静态成员: 即类属性和类方法(如Number.MAX_VALUE)。构造函数本身添加的成员,如
1 | Star.sex = 'man'; |
静态成员只能通过构造函数访问
1 | console.log(Star.sex); |
不能通过实例访问
1 | console.log(ldh.sex); // 错误 |
原型
重要内容、忘记了记得看原型
JS规定每一个构造函数都有一个 prototype 属性,指向一个原型对象。当函数被定义的时候,prototype 属性会自动创建和初始化。这个对象拥有的属性和方法都会被构造函数所拥有。即通过 prototype 定义的属性和方法都会自动成为该其构造函数创建实例所共有的属性和方法(prototype只有构造函数有,对象实例没有)
作用: 共享作用,可以把那些不变的方法
,直接定义在 Prototype 对象上,这样所有对象的实例就可以共享这些方法
。
优点:
使用原型对象可以显著减少每个对象所需的内存数量,因为对象可以继承原型的很多属性。
即便是在对象创建以后才添加到原型中的属性,对象也可以继承它
例
:
1 | Star.prototype.sing = function() { |
注意: 继承的属性依旧可以用 for/in 来枚举,in 运算符依旧起效。只能用 Object.hasOwnProperty() 来区分继承的属性和常规的属性
对象原型 __proto__
每个实例对象(包括原型对象)都有一个属性__proto__
(是两个杠),指向构造这个对象的构造函数的原型对象,对象通过__proto__
就可以调用其中的属性和原型(即__proto__
和prototype
是等价的)
开发中不可以使用__proto__
这个属性,不可对其赋值
1 | ldh.sing(); // 等价于 ldh.__proto__.sing() |
即构造函数只能通过prototype属性获取原型对象,而实例对象必须通过proto获取原型对象
原型对象中的 constructor 属性
原型对象初始化后只有一个属性 constructor
构造函数.prototype.constructor
或 对象实例. proto.constructor ( proto 可以省略)
可以返回这个对象所引用的构造函数(只会返回其构造函数,没有对象实例)
1 | console.log(ldh.__proto__.constructor) |
如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
1 | function Star(uname,age) { |
构造函数、实例、原型对象的三者关系
构造函数通过
new
生成对象实例构造函数通过
prototype
属性获取原型对象对象实例通过
__proto__
获取原型对象原型对象通过
constructor
获取构造函数
类和原型
原型对象是类的唯一标识: 当且仅当两个对象继承自同一个原型对象时,他们才是属于同一个类的实例。即类的识别是通过原型来完成而不是构造函数的名字。构造函数是类的外在表现,通常构造函数的名字用作类名
定义类的步骤
定义构造函数,设置初始化新对象的实例属性
给构造函数的 prototype 对象定义实例的方法
给构造函数定义类字段和类属性
原型链
1 | // 1. 只要是对象就 __proto__ 原型,指向原型对象 |
Object
是最通用的类。构造函数和实例的原型对象的原型对象指向
Object的原型对象(Object.prototype)
,Object的原型对象的原型对象为空。(所有通过 new Object 创建的对象都具有同一个原型对象,即Object.prototype.Object.prototype
是没有原型的对象)
也就是对象实例可以调用 Object 的 prototype 中的属性方法
实例对象属性方法的查找规则
- 当访问一个对象的属性(包括方法)时,首先查找这个
对象自身
有没有该属性。 - 如果没有就查找它的原型(也就是
__proto__
指向的prototype原型对象
)。 - 如果还没有就查找原型对象的原型(
Object的原型对象
). - 依此类推一直找到Object为止 (
null
)。 __ proto__
对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
1 | function Star(uname,age) { |
先查找实例对象中有没有查找的值,没有则到其原型对象中查找,再没有就到 Object 的原型对象中查找,如果再没有则返回 undefined
当写入一个属性的值时,js不会使用原型对象,因为这样会影响其他同个构造函数创建的对象
如果 o 对象的原型中有了 p 属性,给 o 对象设置 p 属性时只是直接在 o 对象中创建p属性,而不再继承原型中的 p属性,也就是 o 的 p 遮盖了原型对象中的 p。也就是说,JS 中只有查询属性时才体会到继承的存在,而设置属性和继承无关,属性的设置永远都是设置在目标对象中。
原型对象中 this 的指向
原型对象中的 this指向调用者
1 | var that; |
扩展内置对象方法(不建议这么做!)
可以通过给原型对象追加
方法,实现内置对象方法的扩展。如 Array 数组对象追加求和的方法
1 | Array.prototype.sum = function() { |
不可以使用下面形式(因为会覆盖内置的方法
)
1 | Array.prototype = { |
ES5的继承
call函数
fun.call(thisArg, arg1, arg2, ...)
- thisArg : 当前调用函数this的指向对象
- arg1 , arg2 : 传递的其他参数
1 | function fn(){ |
属性的继承利用call函数
ES6 前的继承就是在子构造函数中利用 call 引用父构造函数,并改变 this 指向为子构造函数的实例
调用这个函数并且修改函数运行时的this指向
1 | // 1.父构造函数 |
注意: 可以不断嵌套
1 | function Grandson(uname,age,score) { |
方法的继承
子类.prototype = new 父类()
确保子类的原型对象是父类的一个实例即可
1 | Son.prototype = new Father(); |
切记: Son 的原型对象的 constructor 指向要改回为 Son
类的判别方法
instanceof
基于原型链检测,而不是构造函数的名字
缺点: 无法通过对象获得类名,只能检测对象是否属于指定的类名。在多窗口和多框架子页面的Web应用中兼容性不佳,如一个页面中的数组不是另一个页面中数组的实例
constructor属性
缺点与 instanceof 中一样,在多个执行上下文的场景中无法正常工作
利用构造函数的名称
利用
Object.toString
方法获取然后裁剪出来鸭式辨型
如果该对象出现与某个同名的方法,那么就认为该对象属于这个类
ES5新增对象方法
Object.keys() 用于获取对象自身所有的属性
1
Object.keys(obj)
- 效果类似
for...in
- 返回一个由属性名组成的数组
- 效果类似
Object.defineProperty() 定义对象中新属性或修改原有的属性。
1
object.defineProperty(obj, prop, descriptor)
obj
: 必需。目标对象prop
: 必需。需定义或修改的属性的名字descriptor
: 必需。目标属性所拥有的特性
Object.defineProperty()第三个参数 descriptor 说明:
以对象形式{}写
value
: 设置属性的值默认为 undefinedwritable
: 值是否可以重写。true | false 默认为 falseenumerable
: 目标属性是否可以被枚举(遍历)。true | false 默认为 falseconfigurable
: 目标属性是否可以被删除或是否可以再次修改特性 true | false 默认为 false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29var obj = {
id:1,
name:'zykj'
}
Object.defineProperty(obj,'num',{
value: 100
});
Object.defineProperty(obj,'name',{
value: 'skx'
});
Object.defineProperty(obj,'id',{
//设置为false代表不能修改
writable: false
});
Object.defineProperty(obj,'price',{
value: 15,
// 默认为 false 代表不能被遍历
enumerable: false,
// 默认为 false 代表不能删除这个属性和修改这个参数的特性
configurable: false
});
//delete obj.price // 错误
console.log(obj);
函数进阶
函数定义方式
function 函数名(){}
var 函数名 = function(){}
var 函数名 = new Function('参数1','参数2','函数体');
(了解即可,这种方法创建的函数并不适用词法作用域)1
var f = new Function('a','b','console.log(a + b)');
函数的增强写法
1
2
3
4
5
6
7
8
9
10
11
12
13//原本
const fun2 = {
getName: function(){
}
}
//增强写法
const fun2 = {
getName(){
}
}
注意:
Function里面必须是
字符串格式
效率较低,较少使用
所有函数都是Function的实例对象
函数调用方式
1 | // 1.普通函数 |
函数内 this 指向
调用方式 | this 指向 |
---|---|
普通函数调用 | window |
构造函数调用 | 实例对象 函数原型对象里面的方法也指向实例对象 |
对象调用方法 | 该方法所属对象 |
事件绑定方法 | 绑定事件对象 |
定时器函数 | window |
立即执行函数 | window |
函数内改变 this 指向方法
call 方法
函数.call(this指向的对象,参数1,参数2,。。。)
作用: 调用该函数并且将其this指向修改
1
2
3
4
5
6
7function fn(x,y) {
console.log('123');
console.log(this);
console.log(x + y);
}
var o = { name: 'andy' } ;
fn.call(o,1,3); // 将fn函数的this指向改为oapply 方法
函数名.apply(this指向的对象,[参数1,参数2,。。。])
同样会调用函数
1
2
3
4
5
6
7
8
9function fn(a,b) {
console.log(this);
console.log(a + b);
}
fn.apply(o,[1,2]);
var arry = [1,66,3,99,4];
var max = Math.max.apply(Math,arry);
console.log(max);注意:
- 参数必须是数组形式,返回值会自动改为需要的形式
bind 方法
函数名.bind(this指向的对象,参数1,参数2...)
不会调用函数,而是返回一个改造后的原函数的拷贝
1
2
3
4
5
6
7
8
9var o = {
name: 1
}
function fn(a,b) {
console.log(this);
console.log(a + b);
}
var f = fn.bind(o);
f(1,2);注意: 因为不会调用原函数,所以要新建变量来接收改造后的函数
使用例子
1
2
3
4
5
6
7var btn = document.querySelector('button');
btn.onclick = function() {
btn.disabled = true;
setTimeout(function() {
this.disabled = false;
}.bind(this),3000)
}
总结
相同点
:
- 都可以改变函数内部的 this 指向.
区别点
:
- call 和 apply 会调用函数并且改变函数内部 this 指向,
- call 和 apply 传递的参数不一样, call 传递参数arg1, arg2…形式 apply 必须数组形式 [arg]
- bind 不会调用函数,可以改变函数内部 this 指向
主要应用场景
:
- call 经常做继承
- apply 经常跟数组有关系,比如借助于数学对象实现数组最大值最小值
- bind 不调用函数但是还想改变this指向,比如改变定时器内部的 this 指向.
高阶函数
对其他函数进操作的函数,接收函数作为参数或将函数作为返回值输出
1 | <script> |
函数作为一种数据类型,也可以作为参数传递给另一个函数使用,最典型的就是作为回调函数
filter
数组.filter(function(curnettValue,index){})
实现数组中满足某条件的数值的筛选,返回一个
新数组
个人理解:通过返回值的真假来判断是否添加到一个新的数组中
1
2
3
4
5
6let arr = [12,66,4,88,3,7];
let arr1 = arr.filter(function(value,index) {
// return value >= 20;
return value % 2 === 0; // 实现筛选偶数
});
console.log(arr1); // [12, 66, 4, 88]reduce
reduce(function(total, curnettValue, curnettIndex, arr), initialValue)
参数 描述 total 必需。初始值, 或者 计算结束后的返回值
。curnettValue 必需。 当前元素
curnettIndex 可选。 当前元素的索引
arr 可选。 当前元素所属的数组对象。
initialValue 可选。 传递给函数的初始值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18let arr4 = [1,2,3,4,5,6]
const sum = arr4.reduce((prev,item) => {
console.log(item);
// console.log(prev);
return item + prev;
},0) // 2 3 4 5 6
console.log(sum); // 21
/**
解释
第一次 prev: 0 item: 1
第二次 prev: 1 item: 2
第三次 prev: 3 item: 3
第四次 prev: 6 item: 4
第五次 prev: 10 item: 5
第五次 prev: 15 item: 6
最后结果为 21
**/map
数组.map(function(curnettValue,index)
改方法按照原始数组元素顺序依次处理元素、返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值
1
2
3
4
5let arr = [12,66,4,88,3,7];
let arr2 = arr.map(function(value,index) {
return value *2 ; // 实现数据*2
});
console.log(arr2); // [24, 132, 8, 176, 6, 14]
严格模式(IE10以上版本支持)
内容
- 消除了 Javascript 语法的一些不合理不严谨之处,减少了一些怪异行为。
- 消除代码运行的一些不安全之处,保证代码运行的安全。
- 提高编译器效率,增加运行速度。
- 禁用了在 ECMAScript 的未来版本中可能会定义的一-些语法,为未来新版本的 Javascript 做好铺垫。比
如一些保留字如:class
,enum
,export
,extends
,import
,super
不能做变量名
两种形式及其开启方式
为脚本开启严格模式
在脚本开头添加
'use strict'
1
2
3
4<script type="text/javascript">
// 开启脚本严格模式 下面的js代码会按照严格模式执行代码
;
</script>在立即执行函数的函数体开头添加
'use strict'
1
2
3
4
5<script type="text/javascript">
(function() {
'use strict';
})();
</script>注: 此时所有的代码按照严格模式执行
为函数开启严格模式
在函数的函数体开头添加'use strict'
1 | <script type="text/javascript"> |
注: 此时只对 fn 执行严格模式,其中的函数体按照严格模式执行
严格模式中的变化
变量规定
变量必须先声明再使用
1
2
3
num = 10; //错误
var num = 10;严禁删除已经声明的变量
1
2
delete num; //不能删除已经声明好的变量
this 指向问题
全局作用域中 this 的指向为 undefined
1
2
3
4
function x() {
console.log(this); // undefined
}定时器的 this 依旧为 window
严格模式构造函数不加 new 调用,this 会指向 undefined
函数的变化
- 函数的形参不能有重名
- 不允许在非函数(如 if , for) 代码块中定义函数
对象的变化
- 对象中不能有同名的属性
闭包
指一个嵌套函数被导出到他所定义的作用域外访问其外部函数的参数和变量,即某个作用域可以访问另一个函数内部的局部变量。也可理解为一种某个函数可以调用其他函数中变量的现象
原理
词法作用域: 函数是通过词法来划分作用域的,而不是动态的划分作用域。他们在定义的位置运行而不是调用的位置运行
一个定义在函数 f 中的函数 g。当f被调用的时候,作用域链包含了对 f 的这一调用的调用对象,后边是全局对象。g 定义在f 中,因为 f 调用时才会定义 g,所以这个作用域链接保存为 g 的定义的一部分。g 被调用时,作用域链包括3个对象: g 自己的调用对象,f 的调用对象和全局对象
当嵌套函数的引用放到全局作用域中时,这种情况下有一个对嵌套的函数的外部引用,并且嵌套的函数将它的引用保留给外围函数的调用对象,结果就是外围函数的一次特调用的调用对象依然存在,外围函数的参数和局部变量的名字和值在这个调用对象中得以维持,而他又是嵌套函数定义的一部分,所以嵌套函数的调用可以访问这些值
如:
1 | function fn() { |
调用的这个变量所在的函数被称为闭包函数,如上面的 fn 函数
注意: 闭包是为了调用一个变量,而不是他的值!!!
闭包的主要作用
作用:延伸了变量的作用范围
最常用的闭包还是函数中的函数调用其父函数的变量
1 | function fn() { |
经典案例
1 | <div class="nav"> |
递归函数
如果一个函数在内部可以调用其本身,就称为递归函数
递归函数的作用和循环效果一样
由于递归很容易发生栈溢出错误(即不停地新建栈区域),所以必须要添加退出条件 return
当遍历多层次的数据时可以用递归函数来遍历
剩余参数(用来代表剩余形参的参数 …x)
当使用箭头函数时,无法使用 arguments 代替数量不定的参数,必须用剩余参数 …args(一定要加3点,可以任意命名)
1 | const sum = (...args) => { |
用法与 arguments 基本相同,都是数组形式
剩余参数与数组解构的搭配使用(即可以用任意命名剩余参数代替参数中的剩余部分)
1 | let arr1 = [1,2,3,4,5,6]; |
正则表达式
正则表达式(regular expression)。用于匹配字符串中字符组合的模式,在 js 中也是对象
作用
用来检索、替换那些符合某个模式(规则)的文本。亦可用于过滤页面内容的一些敏感词,或从字符串中提取我们想要的特定部分
正则表达式在js中的使用
创建方式
利用 RegExp 构造函数创建( ES5 中用这个方法创建两个字面量相同的对象不是同个对象)
new RegExp(/xxxx/)
一定要加 //
1
var regexp = new RegExp(/123/);
利用字面量创建
var x = /xxx/;
1
var a = /zykj/;
测试正则表达式 test
正则表达式.test(被测字符串)
: 正则对象的方法,用于检测字符串是否符合该规则,返回布尔值
1 | var rg = /123/; |
正则表达式构成:
正则表达式可以由简单的字符组成,如 /abc/ ,也可以是简单和特殊的字符组成,比如 /ab*c/ ,其中特殊字符被称为元字符
,在正则表达式中是具有特殊意义的专用符号
锚字符(用来提示字符所处位置)
边界符 | 说明 |
---|---|
^ | 表示匹配行首的文本(以谁开始 ) |
$ | 表示匹配行尾的文本(以谁结束 ) |
说明 | |
---|---|
\b | 匹配一个单词的边界 |
\B | 匹配非单词边界的位置 |
(?=p) | 零宽正向先行断言,要求接下来的字符都与 p 匹配,但不能包括匹配 p 的字符 |
(?!p) | 零宽负向先行断言,要求接下来的字符不与 p 匹配 |
^用法
:
1 | var reg = /^abc/; |
$用法
:
1 | var reg2 = /ab$/; |
/^xxxx$/
: 表示精确匹配,只能是xxxx出现不能有其他字符
1 | var reg1 = /^abc$/; // 如果是^xx$ 则为精确匹配,要求必须是abc字符串才符合规范 |
\b
: 匹配词语的边界,可以代替 \s 实现匹配字符串前后的空格,而不用是字符串一定得有空格才能匹配
1 | var txt = 'language java'; |
\B
: 匹配非词语边界的位置
如/\B[Ss]cript/
与 "JavaScript"
和 "postscript"
匹配,但不与 "script"
和 "Scripting"
匹配
(?=p)
: 正向前声明,要求接下来的字符都与模式 p 匹配,但是不包括匹配中的那些字符
1 | var txt = 'JavaScript:the definitive guide'; |
(?!p)
: 反向前声明,要求接下来的字符不与模式p匹配
字符类
将单独的直接量字符放进方括号内就可以组成字符串类
[]用法:
[abc]
: 表示只要包含有abc其中一个就返回 true
1 | console.log(rg.test('andy')); // true |
/^[abc]$/
: 三选一 只包含 a 或 b 或 c 单个字符的才返回 true
1 | console.log(rg1.test('aac')); // false |
/^[a-z]$/
: 加了 - 号,表示 26 个英文字母返回任何一个 true
1 | console.log(rg2.test('a')); // true |
/^[a-zA-Z0-9_-]$/
: 26 个英文字母(大写和小写都可以)任何数字以及两个符号返回任何一个 true
1 | console.log(rg3.test('adada'));// false |
/^[^a-zA-Z0-9_-]$/
: 中括号里的 ^ 为取反的意思,与边界符不同
1 | console.log(rg4.test('adada')); // false |
量词符(设定某个模式出现的次数)
量词 | 量词 |
---|---|
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
? | 零次或一次 |
{n} | 重复 n 次 |
{n,} | 重复 n 次或更多次 |
{n,m} | 重复 n 到 m 次 |
都是加在规定字符后面
*用法
: 相当于a>=0 可以出现0次或者很多次
1 | var reg = /^a*$/; // 只允许出现a,a的次数可以是0或者多次 |
+用法
: 相当于>=1 可以出现1次或者很多次
1 | var reg1 = /^a+$/; //只允许出现a,a的次数可以是1次或者多次 |
?用法
: 相当于 1 | 0 只能出现1次或0次
1 | var reg2 = /^a?$/; // a出现1次或0次为true 其他为false |
利用 ? 和其他重复字符可以组成非贪婪的重复
例如/a+/
与字符串"aaa"
的三个都匹配,而/a+?/
只与第一个 a 匹配
{x}用法
: 相当于重复 3 次为 true
1 | var reg3 = /^a{3}$/; // a重复3次为true |
{x,}用法
: 相当于重复大于等于 x 次为 true
1 | var reg3 = /^a{3,}$/; // a出现大于3次为true |
{x,y}用法
: 重复大于等于 3 次小于等于 y 次为 true ,输入时中间不能有空格
1 | var reg4 = /^a{3,6}$/; |
选择分组引用
说明 | |
---|---|
| 符号 | 选择,匹配的是该符号左边的子表达式或者右边的子表达式 |
(...) | 组合,将几个项组合成为一个单元,可通过 * + ? \ | 等符号加以修饰,可以被引用 |
(?:...) | 只组合,把项组合到一个单元,但不记忆与该组相匹配的字符,不可被引用 |
\n | 和第 n 个分组第一次匹配的字符串相匹配,组是圆括号里的子表达式,从左向右算起。不包括(?: )的分组 |
选择: | 符号
1 | var reg = /ab|cd|ef/; |
注意: 选择项的匹配是从左到右,直到发现了匹配项。如果左边的匹配则忽略右边,即使它产生更好的匹配
小括号的作用:
把单独的项组合成子表达式,以便可以像处理一个独立的单元那样用 | 、* 、+ 或者 ?等来对单元内的项进行处理。例如 /Java(script)?/ 可以匹配字符串 “java”,其后可以有 “script” 也可以没有。
1
2
3var reg = /Java(script)?/;
console.log(reg.test('Java'));
console.log(reg.test('Javascript'));第二个作用是引用。即允许我们在同一正则表达式后部引用前面的子表达式。通过\后面加数字实现,数字指定了带括号的子表达式在正则表达式中的位置。这个引用不是引用模式,而是引用与那个模式匹配的文本
例:
1
2
3var txt = '232';
var reg = /([12])[3]*\1/;
console.log(reg.test(txt));1
2
3
4var reg = /^(abc){3}$/;
console.log(reg.test('abc')); // false
console.log(reg.test('abcabcabc')); // true
console.log(reg.test('abccc')); // false注: 不能在字符类中使用这种引用。
括号总结
- 大括号量词符里面表示重复次数
- 中括号字符集合。匹配方括号中的任意字符
- 小括号表示优先级
可以在线测试: https//c.runoob.com/
预定义类(某些常见模式的预定义类)
预定类 | 说明 |
---|---|
\d | 匹配 0-9 之间的任一数字,相当于[0-9] |
\D | 匹配所有 0-9 以外的字符,相当于[^0-9] |
\w | 匹配任意的字母、数字和下划线,相当于[A-Za-z0-9_] |
\W | 除所有字母、数字和下划线以外的字符,相当于[^A-Za-20-9_] |
\s | 匹配空格(包括换行符、制表符、空格符等),相等于[\t\r\n\v\f] |
\S | 匹配非空格的字符,相当于[^ \t\r\n\v\f] |
1 | var reg = /^\d{3}-\d{8} | \d{4}-\d{7}$/; // 实现座机号码验证 |
注意: 正则表达式里可以用或符号
,但是符号为|
而不是||
敏感字的替换
replace(regexp/substr,replacement)
replace 除了可以替换字符串也可以替换正则表达式
目标字符串.replace(/xxx/,'yy')
将字符串中的 xxx 替换为 yy
参数1
replace(/表达式/[switch],替换的内容)
正则表达式的参数(写在/后面):
g
: 全局匹配,可以匹配多个i
: 忽略大小写gi
: 全局匹配+忽略大小写
1 | div.innerHTML = text.value.replace(/激情|gay/g,'**'); // 实现将文本中所有关键字替换 |
注意: 不能用精确匹配模式!!!!!
参数2
$1, $2, $3, …, $n
: 依次匹配子表达式
1 | var sStr='讨论一下正则表达式中的replace的用法'; |
函数
先看arguments
的用法:
1 | var sStr='讨论一下正则表达式中的replace的用法'; |
参数分别为:
- 匹配到的字符串(此例为”正则表达式”)
- 如果正则使用了分组匹配就为多个否则无此参数。(此例的参数就分别为 “正则”, “式”)
- 匹配字符串的对应索引位置(也就是”正则表达式”的索引位置,此例为4)
- 原始字符串
for … in
for...in
是ES5标准,用来遍历对象和数组, 返回键名key
1 | // 遍历对象 |
ES6
let 和 const
let
let: 用于声明变量
特点
只在块级作用域(即{})中起作用,而 var 不具备这个特点
1
2
3
4
5
6
7if(true) {
var b = 20;
if(true) {
let c =30;
}
console.log(c); // 报错
}没有变量提升,即必须先声明再使用
1
2console.log(a);
let a = 10; // 报错具有暂时性死区特性
在块级区域用 let 声明的变量会与该区域绑定,与区域外同名变量的使用互不影响
ES6中,在代码块内,在用 let 和 const 声明变量之前,该变量都是不可用的,不可用的区域就叫做
暂时性死区
(temporal dead zone,TDZ)1
2
3
4
5
6
7
8if(true) {
//TDZ开始
k = 5;
console.log(k); // 皆报错
let k = 4; // TDZ结束
console.log(k); // 4
}
console.log(k); // 10暂时性死区的本质: 再进入一个代码块的时候,变量就已经存在,但是无法引用,只有 let 或者 const 声明时,才能使用
不允许重复声明
不允许在同一个作用域中重复声明同一个变量
let经典面试题
输出 2 2
输出 0 1
区别在于 for 循环中的i如果是 var 声明,则为全局的。如果是 let ,则有了局部作用域,每一次循环都是一个新的 i 值
注意: for 循环设置变量的部分是一个父作用域,而循环体内部是一个单独的子作用域
证明:
1 | for(let i = 0; i < 3 ;i ++) { |
输出的是 3 次 abc ,说明花括号内部的作用域和循环设置内部的作用域不是同一个,如果是同一个,会因为 let 不能重复声明而报错
const
const: 用于声明常量
特性
具有块级作用域,即只在其所在中括号内生效
const 声明的常量必须赋予初始值
不存在变量提升
const 赋值后,值/引用不能修改(即地址不能修改),不可重复声明
注意: 对于简单数据类型 num ,string 等,不能修改其中的值
1 | const p = 3.14; |
而对于复杂数据类型,数组等,其中的值可以修改,但不能修改这个常量的整体,因为前者不会改变地址,后者则会改变地址
1 | const arr = [100,200]; |
const 本质
: const 不是让变量的值不能改动,而是让变量指向的地址不能改动。对于值类型的数据,其值就保存在变量指向的地址中,所以无法改变,是一个常量。而对于引用数据类型,其变量指向的地址保存的不是值而是一个指针,这个指针指向堆中存放的具体值,所以复杂数据类型的具体值可以改变
总结
let , const , var 的区别
- 使用 var 声明的变量,其作用域为该语句所在的明数内,存在变量提升现象。
- 使用 let 声明的变量,其作用城为该语句所在的代码块内,不存在变量提升,
- 使用 const 声明的是常量,在后面出现的代明中不能再修改该常量的值。
var | let | const |
---|---|---|
函数级作用域 | 块级作用域 | 块级作用域 |
变量提升 | 不存在变量提升 | 不存在变量提升 |
值可更改 | 值可更改 | 值不可更改 |
一般常量用 const ,效率比较高
加上 es6 , js 一共有六种声明变量的方法: var
function
let
const
import
class
解构赋值
ES6 允许从数组中提取值,按照对应位置,对变量赋值,对象也可以实现解构,结构什么用就用对应的括号
数组解构(用中括号)
1
2
3
4
5let ary = [1,2,3];
let [a,b,c] = ary;
console.log(a);
console.log(b);
console.log(c);只要右边数据类型具有 Iterator 接口,就可以用数组解构
1
2const [a,b,c] = 'wwe';
console.log(a,b,c);// w w e允许给左边指定默认值
1
2
3let [a,b=true] = [1];
console.log(a);// 1
console.log(b);// true默认值生效的条件是右边对应值严格等于(===) undefined,否则不生效
1
2
3let [a,b=true] = [1,null];
console.log(a); // 1
console.log(b);// null 因为null不等于undefined允许把默认值设为函数,但是函数执行的条件是右边对应位置没有赋值
1
2
3
4
5
6
7
8
9
10function fn() {
return 'aaa';
}
let [k=fn()] = [,2];
console.log(k); // aaa
function fn() {
return 'aaa';
}
let [k=fn()] = [1];
console.log(k); // 1默认值也可以引用解构赋值中的其他变量,但是前提是该变量已经声明
1
2
3
4
5
6
7
8let [a=1,b=a] = [1];
console.log(a,b); // 1 1
let [x = 1,y = x] = [];
console.log(x,y);// 1 1
let [a = 1,b = a] = [1,2];
console.log(a,b) // 1 2
let [m = n,n = 1] = [];
console.log(m,n);// n is not defined总之,只要记住数组解构的顺序是从左到右,先默认,再赋值
对象解构(用大括号)
第一种方式
:
1 | let person = {name:'list',age:30,sex:'nan'}; |
注意: let 的属性名必须和对象中的属性名一样,本质是下面的简写
1 | let {name:name,age:age} = {name:'bruce',age:20}; |
第二种方式
:
1 | let {name:myName,age:myAge,sex:mySex} = person; |
注意: let 中属性名后的值可以任意命名,myName 可以改为其他
本质: 对象解构本质上只有第二种方式,第一种只是简写了。其内部机制是先找到同名属性,再赋值给属性中的变量。前者只是个名字,用于匹配。后者才是接收值的变量。所以对象解构赋值中属性名顺序不一定要与右边对象属性名顺序一致
也可以嵌解构赋值
1 | let obj = { |
对象解构也可以赋予默认值,生效条件与数组的一致
1 | let {a = 1} = {a:undefined}; |
使用对象解构,如果右边是基本数据类型,则将其转为包装类型,可以用同名属性获取其中内置的方法属性
1 | let {k} = 1; |
结构赋值右侧的数组所包含的元素不必和左侧的变量一一对应。左侧多余的变量的赋值为 undefined ,而右侧多于的值则会忽略。左侧的变量列表可以包含连续的逗号用以跳过右侧对应的值.此外解构赋值也适用于数组嵌套的情况,但是格式应当相同
1 | var [a,b,c] = [1,2]; // 左边对于右边 |
函数参数的解构赋值
1 | function get([a,b,c,d]) { |
扩展运算符(…变量)
可以将数组、Map 、Set、元素集合、arguments、字符串等具有 Iterator 接口的可遍历数据结构转换成逗号分隔的形式,可应用于形参或数组合并中
也可以将数组转变为非数组形式
应用于函数中
1
2
3
4
5
6let ary = [1,2,3];
console.log(...ary);
function add(a,b,c) {
console.log(a + b + c);
}
add(...ary);数组合并
1
2
3
4
5
6
7
8
9//方法1:
let ary1 = [1,2,3];
let ary2 = [2,3,4];
let ary3 = [...ary1,...ary2];
console.log(ary3);
//方法2:
ary1.push(...ary2);
console.log(ary1);将伪数组转换成真正的数组
1
2
3
4
5
6
7
8
9
10<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<script>
var divs = document.querySelectorAll('div');
console.log(divs instanceof Array); // false
divs = [...divs];
console.log(divs instanceof Array); // true
</script>将数组转为非数组
1
2let arr1 = [1,2,3];
console.log(...arr1);//1 2 3
ES6新增方法
数组方法
ES5新增数组方法
数组.forEach(function(curnettValue,index,arr){})
value
: 为数组的值index
: 为索引值arr
: 为数组本身实现数组、伪数组、set 数据的遍历
1
2
3
4
5
6
7
8
9var arr1 = [1,2,3];
var sum = 0;
arr1.forEach(function(value,index,arr) {
console.log(value);
console.log(index);
console.log(arr1);
sum += value;
})
console.log(sum);数组.filter(function(curnettValue,index,arr){})
实现数组中满足某条件的数值的筛选,返回一个
新数组
参数值同 forEach
1
2
3
4
5
6var arr = [12,66,4,88,3,7];
var arr2 = arr.filter(function(value,index) {
// return value >= 20;
return value % 2 === 0; // 实现筛选偶数
});
console.log(arr2);数组.some(function(curnettValue,index,arr){})
用于查找数组是否有满足条件的数值
返回的是布尔值,如果有满足条件的值就返回 true ,否则为 false
当查找到第一个满足条件的数值时就停止查找,退出函数
1
2
3
4
5
6
7
8
9
10
11var arr = [10,30,4];
var flag = arr.some(function(value){
// return value > 20;
return value < 3;
})
console.log(flag); // false
var arr1 = ['red','pink','blue'];
var flag1 = arr1.some(function(value) {
return value == 'pink';
})
console.log(flag1); // truesome 和其他两个区别: 利用 return true 可以使 some 停止迭代,forEach和filter不可以
数组.every(function(curnettValue,index,arr), thisValue)
用于检测数组所有元素是否都符合指定条件(通过函数提供)、在判断是否全选中好用
1
2
3
4
5
6
7
8
9
10// 检查ages数组中的所有值是否为18或更高:
var ages = [32, 33, 16, 40];
function checkAdult(age) {
return age >= 18;
}
function myFunction() {
document.getElementById("demo").innerHTML = ages.every(checkAdult); // false
}
ES6新增数组方法
Array.of()
: 与Array()构造数组,区别在于对于单个整数参数,前者返回一个第一个值为参数值的数组,后者返回一个长度等于参数值的空数组1
2
3const arr1 = Array.of(7);
const arr2 = Array(7);
console.log(arr1,arr2); // [7] [,,,,,,]Array.from方法(Array.from 方法从一个类似数组(既具有length属性)或可迭代对象(对象、字符串)中创建一个新的,浅拷贝的数组实例)
Array.from(目标对象,函数)
转换对象
时,对象里面的属性名应有双引号且必须是整数,一定要有length属性
1
2
3
4
5
6
7
8var arrayLike = {
"0":"zs",
"1":"ls",
"2":"ww",
"length":3 // 一定要有length属性
}
var arr = Array.from(arrayLike);
console.log(arr); // ['zs','ls','ww']转换伪数组
1
2
3var weishuzu = document.querySelectorAll('div');
var arr1 = Array.from(weishuzu);
console.log(arr1 instanceof Array);转换字符串
1
2
3var str = '123';
var str1 = Array.from(str);
console.log(str1);// [1,2,3]Array.from
: 中的函数用于对每个值进行操作方法还可以接受第二个参数, 作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
1
2
3
4
5
6
7let arrayLike2 = {
"0":1,
"1":2,
"length":2
}
let newary = Array.from(arrayLike2,item => item * 2);
console.log(newary);所以将数据转为对象有两种方法:
使用
扩展运算符
使用
Array.from
区别在于前者无法转换对象,后者可以
数组.find
array.find(function(item,index) {})
: 返回符合函数中条件的第一个数值
, item 是数组的每个值,index为其对应的索引值, 没有返回 undefined1
2
3let ary = [{ id:1, name:'zhangsan' }, { id:2, name:'ls' }];
let target = ary.find((item,index) => item.id == 2);
console.log(target);数组.findIndex
array.findIndex(function(value,index){} )
: find返回第一个
符合条件的数值的索引值, value 为值,index 为其索引值1
2
3let ary = [1,10,2,18];
let ary1 = ary.findIndex((value,index) => value > 9);
console.log(ary1);数组.includes
array.includes(目标值)
: 返回的是布尔值1
2console.log([1,2,3].includes(2)); // true
console.log([1,'a',3].includes('e')); // false数组.copyWithin 复制数组制定成员内容到数组内其他位置
array.copyWithin(替换的目标的起始位置,替换内容的起始位置,替换内容的结束位置(不包括该位置))
1
2
3let arr3 = [1,2,3,4,5,6,7,8];
arr3.copyWithin(4,2,5);
console.log(arr3); // [1,2,3,4,3,4,5,8]数组.fill 用给定内容替换数组内容
array.fill(替换内容,替换开始位置,替换结束位置(不包括))
: 默认全部替换1
2
3
4
5let arr2 = [1,2,3,4];
arr2.fill('a');
console.log(arr2); // [a,a,a,a]
console.log(new Array(3).fill('a')); // [a,a,a]
console.log(arr3.fill('1',3,5)); // [1,2,3,1,1,4,5,8]reduce 迭代 直接从第二项开始迭代
参数1:上一次迭代return的值
参数2: 当前值
1
2
3
4
5
6
7let arr4 = [1,2,3,4,5,6]
const sum = arr4.reduce((prev,item) => {
console.log(item);
// console.log(prev);
return item + prev;
}) // 2 3 4 5 6
console.log(sum); // 21reduceRight 跟 reduce一样 但是是从右边开始
keys 用于
for...of...
遍历数组每一项的索引值
1
2
3
4let arr6 = [1,2,3,4,5,6];
for(let k of arr6.keys()) {
console.log(k); // 0 1 2 3 4 5
}values 用于
for...of...
遍历数组每一项的值1
2
3for(let k of arr.values()) {
console.log(k); // 1 2 3 4 5 6
}entries 用于
for...of...
遍历数组每一项键值对 返回数值形式1
2
3for(let k of arr6.entries()) {
console.log(k); // [0,1] [1,2] [2,3] [3,4] [4,5] [5,6]
}注意: 所有数组方法的结束位置参数都是不包括在内 即顾前不顾后
字符串扩展
模板字符串
利用反引号``定义字符串
1 | let name = `woshizykj`; |
特点:
可以解析变量
1
2
3
4let name = `zykj`;
console.log(name instanceof String);
let sayHello = `hello my name is ${name}`;
console.log(sayHello);可以空行
1
2
3
4
5
6
7let result = { name:'zd', age:18 };
let html = `
<div>
<span>${result.name}</span>
<span>${result.age}</span>
</div>
`;可以调用函数
1
2
3const fn = () => { return '我是fn函数'; }
let html1 = `我是模板字符串 ${fn()}`; // 我是模板字符串 我是fn函数
console.log(html1);
注意: 调用外部变量和函数必须用 ${} , {} 中可以放入任意 js 表达式
String.raw
用来将模板字符串转义(包括变量)
1 | let str = 'world'; |
三种确定字符串是否包含另外一个字符串方法
ES6提供了 三种新的用来确定一个字符串是否包含了另外一个字符串的方法(原先只有 indexOf) : startsWith、endsWith、includes
判断字符串开头结尾
字符串.startsWith('xxx');
: 判断 str 字符串是否以 xxx 开头
字符串.endsWith('xxx');
: 判断 str 字符串是否以 xxx 结尾
1 | let str = 'hello myworld'; |
string.includes(查找字符,开始查找的位置)
: 判断字符串是否有某个字符
1 | const str1 = 'hello world'; |
以上三种方法都接受第二个参数,表示开始搜索的位置
重复某个字符串
返回新的字符串
字符串.repeat(n)
: 将字符串 str 重复 n 次,返回新的,不改变旧的
1 | let str = 'x'.repeat(5); |
字符串补全
padStart(补全后长度,补全的字符串)
1 | let str = 'xxx'.padStart(5,'ab'); |
padEnd(补全后长度,补全的字符串)
1 | let str1 = 'xxx'.padEnd(6,'abb'); |
若第一个参数小于原字符串长度,则返回原字符串
1 | let str1 = 'xxx'.padEnd(1,'abbaaaaa'); |
若补全的字符串长度加上原字符串长度大于第一个参数,则会截除补全后的字符串多余的部分
1 | let str1 = 'xxx'.padEnd(6,'abbaaaaa'); |
数值的扩展
ES6提供了二进制和八进制的新写法
二进制: 0b(0B)开头
八进制: 0o(0O)开头
1 | console.log(0o767 === 503)// true |
如果要将改写法的二进制和八进制转为十进制,则要调用Number方法
1 | console.log(Number(0o767))// 503 |
Number.isFinite()、Number.isNaN()
Number.isFinite
: 判断一个数值是否是有限的
Number.isNaN
: 判断一个值是否是 NaN
这两个方法与传统的全局方法在于: 传统方法会先将非数值转为数值在进行判断。而新方法只对数值有效,对于非数值一律返回 false
ES6 还将parseInt
和parseFloat
移植到了 Number 对象上面,行为保持不变
Number.isInteger()
: 判断一个数值是否是整数。
1 | console.log(Number.isInteger(1)); // true |
注意: js中 1 和 1.0 都是同样的存储方式,所以被视为同一个值
函数的扩展
函数参数设置默认值
function xx(arg1=xx,arg2=yy,...){}
1 | function Star(x=0,y=0) { |
注意: 参数默认值是不传值的,每次调用都会重新计算默认值。一旦设置了默认值,函数进行声明初始化时,参数列表会形成一个作用域。
可以解构赋值默认值配合使用
第一种: 只有赋值结构有默认值
1 | function foo({x,y=5}) { |
第二种: 参数和赋值结构都有默认值
1 | function fetch({x,y=5} = {}) { |
函数的length
函数参数如果指定了默认值,那么函数的参数只会计算第一个指定了默认值的参数前面的参数个数,第一个指定了默认值的参数及其后面的参数(不管有没有指定默认值)都不计入 length 属性中
1 | let foo = function(x,y=2,z,k=1) {} |
rest参数
用于获取函数多余的参数,形式为…变量名,变量名代表的是一个数组变量
1 | function foo(x,...args) { |
注意
rest参数必须是最后一个参数。函数的length不包括rest参数
name 属性
函数的 name 属性返回函数的名字
1 | function Big() { |
箭头函数
(参数1,参数2...) => {函数体}
特性
如果函数体只有一句代码,且代码的执行结果就是函数的返回值,函数体大括号可以省略
1
2var fn = (a,b) => a + b;
console.log(fn(1,2));箭头函数中,如果形参只有一个,形参外侧的小括号可以省略
1
2var fun = v => v;
console.log(fun(1));箭头函数不绑定 this 关键字,箭头函数中的 this 指向的是函数定义位置所在的对象中的(即箭头函数定义时所在花括号的外层的 this 的指向)
1
2
3
4
5
6
7
8
9const obj = {name: 11};
function fn () {
console.log(this);
return () => {
console.log(this);
}
}
var fun = fn(); //返回window fun();
// 返回window解析: 因为fun() 的函数体是在 fn() 中定义,所以其中的 this 即为 fn 中的 this,也就是通过 call 设定的 obj,所以输出值不是 fun 而是 obj
箭头函数里没有 arguments 对象 用 rest 参数代替
1
2
3fn = (x,y) => {
console.log(arguments);
}箭头函数不能做构造函数 不能 new 调用
1
2
3
4
5fn = (x,y) => {
this.name = x;
this.age = y;
}
const obj = new fn('bruce',1);箭头函数主要用于具名函数的声明和简化回调函数
箭头函数面试题
1 | var age = 100; |
解析: 因为 say 函数定义在 obj 中,而 obj 是个对象,不形成作用域,所以 say 的指向为 window
对象扩展
对象的简写赋值
可以直接将函数声明、变量写进对象中,属性名即为变量名,属性值即为该变量的值
1 | let name = 'bruce'; |
对象里用变量表示属性名
属性名使用 [] 里面就可以使用变量
1 | let str = 'name'; |
Object.assign
将源对象所有可枚举属性复制到目标对象(浅拷贝)
Object.assign(target,sources...)
1 | let obj3 = { |
可以利用该方法实现浅拷贝
1 | let obj5 = {} |
深拷贝
: 拷贝多层,每一级别的数据都会重新开辟地址拷贝
拷贝方法: 利用递归函数
利用 for in 和 递归函数
1 | function deepcopy(newobj,oldobj) { |
注意: 因为数组也是对象,所以要放在对象之前
对象也可以使用扩展运算符
1 | let obj11 = {name:'clark'}; |
方法
Object.defineProperty(目标对象,'修改的属性',descriptor)
其中 descriptor 以对象形式 {} 说明,包括了
value
: 设置属性的值writable
: 值是否可以重写 true | false 默认为 falseenumerable
: 目标属性是否可以被遍历 true | false 默认为 falseconfigurable
: 目标属性是否可以被删除或是否可以再次修改特性 true | false 默认为 false1
2
3
4
5
6
7
8
9
10
11
12
13
14var obj = {
id : 1,
pname : 'xiaomi',
price : 1999
} // 修改添加对象的属性
Object.defineProperty(obj,'num',{
value: 1000
})
Object.defineProperty(obj,'price',{
value: 9.9
})
Object.defineProperty(obj,'id',{
writable:false
})注意: 一开始就设置的好的属性如果没有利用该方法就不会被影响。只能修改已有属性或者新建自有属性,但不能修改继承属性
Object.getOwnPropertyDescriptor(对象名,对象属性名)
: 返回某个对象特定属性的属性描述符)1
2
3
4
5
6var i = {
num : {
num1 : 2
}
}
console.log(Object.getOwnPropertyDescriptor({x:1},"x"));//{value: 1, writable: true, enumerable: true, configurable: true}Object.keys(对象名)
获取对象的所有可枚举的自有属性属性名**,**以数组形式返回
1
2
3
4
5var arr = Object.keys(obj);
// console.log(arr);
arr.forEach(function(value) {
console.log(value);
})与 for(var k in obj){} 的区别在于后者返回字符串 前者返回数组
Object.values 返回对象的属性值 数组形式
1
2let obj = { name:'bruce', } ;
console.log(Object.values(obj)); // ['bruce']Object.entries 返回对象的属性和值 二维数组形式
1
2let obj = { name:'bruce', }
console.log(Object.entries(obj)); // [['name','bruce']]
对象的解构赋值
1 | let {x,y,...z} = {x:1,y:2,a:3,b:4}; |
对象的扩展运算符
1 | let obj = {x:1,y:2}; |
Set数据结构
类似于数组,但是会自动消除重复的数据,适用于搜索引擎。即成员的值都是唯一的,没有key 没有索引 无法通过索引获取
Set数据结构的创建
new Set()
;
参数必须是数组或者是类数组或者是有 iterable 接口
有 iterable 接口: 数组 arguments DOM 元素集合 set Map 字符串
1 | const a = new Set(); |
可以利用 Set 数据结构对数组去重
1 | const s3 = new Set([1,2,3,2,1]); |
Set数据结构方法
set.size
: 返回实例的值个数set.add(value)
: 添加某个值 返回Set解构本身1
2
3const s1 = new Set();
s1.add('pink').add('red');
console.log(s1);set.delete(value)
: 删除某个值 返回布尔值显示删除是否成功1
2
3const r1 = s1.delete('red');
console.log(s1);
console.log(r1); // trueset.has()
: 判断是否存在某个值 返回布尔值1
2const r2 = s1.has('pink');
console.log(r2);//trueset.clear()
: 删除全部数据1
2s1.clear();
console.log(s1);
遍历Set数据结构
利用forEach函数
1 | const s2 = new Set([1,2,3,4]); |
Set 实例也有 keys、values、entries 方法,由于 Set 数据类型没有索引,所以 keys 和 values 返回的是同样的值
WeakSet
与 Set 数据类型相同,也是不重复的值的集合。但是与 Set 有两个区别:
WeakSet 的成员必须是对象类型的值
1
const ws = new WeakSet([1,2,3]);//报错
WeakSet 的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,而是会继续回收该对象
垃圾回收机制依赖引用计数,当一个数值的引用次数不为 0,就无法被回收。结束该值的使用后,有时会忘记取消引用,导致该对象之前所占内存无法释放,进而可能引发内存泄漏。而 WeakSet 的引用都不计入垃圾回收机制,所以就不存在这个问题。由于这些特点,WeakSet 无法遍历也没有 size 属性,因为其成员随时可能消失。
Symbol数据类型
是一个基本数据类型 表示独一无二的值
通过Symbol()
方法直接创造 Symbol 类型值
1 | let sym1 = Symbol(); |
注意: 不能用 new 调用,因为 Symbol 是一个基本数据类型而不是函数
Symbol 函数可接受一个参数 用以描述当前 Symbol 值(相当于一个键值)
1 | let sym3 = Symbol('one'); |
Symbol 不可以进行数值运算,但是可以转为字符串和布尔值
1 | // console.log(Symbol(3) +1); 错误 |
Symbol.for(参数)
: 根据描述寻找对应值 如果之前有相同参数的 Symbol 值,则返回这个值,否则则创建一个新的 Symbol
1 | let zf1 = Symbol.for('zhufeng'); |
Symbol.keyFor(Symbol值)
: 返回 Symbol.for 找到或创造的 Symbol 值
1 | console.log(Symbol.keyFor(zf2)); |
用途: Symbol 数据类型主要用于属性名,防止对象内属性名的冲突
1 | let obj = { [sym1]:'bruce', [sym2]:'clark' } ; |
注意: Symbol 做为属性名时,不会出现在for in
for of
Object.keys
Object.getOwnPropertyNames
Map数据类型
一个构造函数,代表了键值对的集合,键的范围包括各种类型的值而不局限于字符串
创建实例
new Map([[key1,value1],[key2,value2],...])
: 参数是一个二维数组,每一项内嵌数组里有key和对应的映射值value
1 | let map1 = new Map([[1,'zhufeng'],[false,{name:'bruce'}]]); |
注意:
- 属性名不能重复,因为是映射关系,一个值只能映射一个值
方法
属性.size
: 映射对的个数
1 | console.log(map2.size); // 4 |
map.get(key)
: 获取key键的映射值,key值不用转为字符串
1 | const age = map2.get(1); |
map.set(key,value)
: 给键 key 设置值 value
1 | map2.set(1,'hz'); |
map.has(key)
: 判断 key 键有没有对应的 value 值
1 | console.log(map2.has(false)); // true |
map.delete(key)
: 删除 key 键 返回布尔值
1 | map2.delete(false); |
map2.forEach((value,index,input) => {})
: 遍历 map 数据
1 | map2.forEach((value,index) => { |
map.clear()
: 清空所有映射
map.keys()
: 获取键值 用于遍历
1 | for(let k of map2.keys()) { |
map.values()
: 获取映射值 用于遍历
1 | for(let k of map2.values()) { |
map.entries()
: 获取映射对
1 | for(let k of map2.entries()) { |
WeakMap
与 Map 基本相同,但有两个区别:
键名只能用对象
键名所指向的对象不计入垃圾回收机制
由于这些特性,WeakMap 数据同样没有 size 属性也无法遍历
注意: WeakMap 弱引用的只是键名而不是键值。
Proxy
用于代理对象,拦截/改写对对象的操作
创建实例
new Proxy(target,handler)
:
第一个参数: 代理的对象也可以是函数
第二个参数: 配置对象,对于每一个拦截的操作,都需要提供一个对应的处理函数,该函数将拦截对应的操作
1 | let proxy1 = new Proxy(obj,{ |
注意: 必须是对 Proxy 实例操作其代理的对象才能执行拦截的函数
Proxy拦截对象操作
get: 用于拦截对对象属性值的读取,三个参数
参数:
target
: 原对象
propKey
: 读取的属性名
receiver
: 调用的 proxy 对象
利用 get 拦截操作实现数组读取负数索引
1 | let arr = [1,2,3,4]; |
set: 用于拦截对对象属性值的值的设置,四个参数
参数:
target
: 原对象
propKey
: 读取的属性名
value
: 设置的值
receiver
: 调用的 proxy 对象
set 实现属性设置的限制
1 | let obj = { |
has: 拦截in运算符
参数:
target
: 原对象
propKey
: 读取的属性名
1 | let obj = {name:'zhufeng'}; |
Proxy拦截函数操作
apply: 用于拦截函数的执行,三个参数
参数:
target
: 原函数
object
: 函数中 this 指向
args
: 参数
1 | let proxy2 = new Proxy(getObj,{ |
Promise
视频教程:Promise 的介绍和使用 、 文章教程: 怎么理解 JS Promise
用于处理异步函数的执行顺序、主要用于
网络请求
基础
Promise 是一个容器,一个对象,里面存放着异步事件,里面的异步事件有三种状态:
pending (进行中)
fulfilled (已成功)
rejected (已失败)
Promise 容器可以获取根据异步事件的结果将其转为完成状态或失败状态,并将事件结果传送出去
Promise 对象的特点:
对象的状态不受外界影响,只要异步操作的结果可以决定是哪一种状态
一旦状态改变就不会再变,会一直保持这个状态,成为 Resolved
步骤:
创建 promise 容器 : 即创建一个承诺变量 里面封装了异步任务 会自动调用
promise 本身不是异步
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21var p1 = new Promise((resolve,reject) => {
fs.readFile('./data/a.txt','utf8',(err,data) => {
if(err) {
// 如果失败
// console.log(err);
// 调用reject 将容器的状态由pending改为Rejected、然后执行catch
reject(err);
} else {
// 如果成功
// console.log(data);
// 调用resolve 将容器的状态由pending改为Resolved、然后执行then
resolve(data);
}
})
}).then((data)=>{
// resolve(data); 会执行到这里
console.log(data)
}).catch((error)=>{
// reject(err); 会执行到这里
console.log("error")
})设置承诺了之后调用
then
方法设置承诺里面回调函数的
resolve/reject
事件:- 参数 1 就是上面的函数
resolved
- 参数 2 就是函数
rejected
这里是在设置resolve
和reject
的具体函数
在
resolve
里面最后要 return 接下去要执行的异步事件所在的 Promise 对象实例,形成链式编程,实现顺序执行1
2
3
4
5
6
7
8
9
10
11
12
13
14p1.then((data) => {
console.log(data);
// return 一个Promise对象进行设置 链式编程
return p2;
},(err) => {
console.log('failed');
}).then((data) => {
// 这是定义p2的resolved
console.log(data + 'haha');
return p3;
},(err) => {
// 这是定义p2的reject
console.log(err);
})- 参数 1 就是上面的函数
注意:
即先创造保证,再设置异步事件完成后具体做什么
实现同步输出的原理就是: new Promise 是同步的,里面的函数依旧是异步的,所以继续往下进行 resolved 和 reject 函数的定义,第一个定义的 promise 的异步函数执行完后,会调用 resolved 或者 reject 函数,所以会找到定义之处进行执行,由于另外一个 promise 的 resolved 和 reject 是在第一个定义之后 return 出来再定义的,所以这个 promise 只能在第一个 promise 执行之后再定义,所以其 reject 和 resolve 函数也只能在其之后调用,依次递归形成同步
then 方法只有在当前脚本所有同步任务执行完毕后才会执行
resolve 函数只能有一个参数
catch方法
: 捕获 promise 实例和 then 中 resolve 的错误 一般会在最后使用 catch1
2
3
4
5
6pro1.then((mes) => {
console.log(mes);
let a1 = 2;
}).catch((e) => {
console.log(e);
})Promise.all()
: 用于将多个 promise 实例整合成一个 promise 实例 参数为数组形式1
let pro2 = Promise.all([p1,p2,p3])
整合的实例的状态有两种情况:
- p1 , p2 , p3 的状态都为 fulfilled,p 才为 fulfilled。此时 p1 , p2 , p3 的结果组成一个数组,传递给 p 的回调函数
- 只要 p1 , p2 , p3 中有一个进入 rejected 状态,p 就会进入 rejected 状态。此时第一个进入 rejected 的子实例的结果就会传给 p 的回调函数
执行错误则停止执行并通过 catch 方法输出
1
2
3
4
5pro2.then((res) => {
console.log(res);
}).catch((e) => {
console.log(e);
}) // ['OK1','OK2','OK3']Promsie.race()
: 与 all 方法类似,但只会输出第一个执行成功的回调函数的 resolve 的结果1
2
3
4
5Promise.race([p1,p2,p3]).then((res) => {
console.log(res);
}).catch((e) => {
console.log(e);
}) // 'OK1'
mongoose 中使用 promise
mongoose 中已经包装了 promise ,数据库操作事件后面可以直接调用 then 方法
1 | User.findOne({author:'clark'}).then(function(data) { |
async函数和await
Generator 函数的语法糖,加在函数声明前方,async 函数返回一个 Promise 对象
async 函数可以看做由多个 promise 包装成的 Promise 对象, await 则是内部 promise 的 then 命令的语法糖
async 即把其后面的函数包装成一个 promise 对象,可以使用 then 方法添加回调函数。async 函数内 return 的值会作为 then 方法回调函数的参数
1 | async function getName() { |
当 async 遇到 await 时,会等待其函数执行完毕后再继续执行后面的函数体.
await 后面一般是跟 Promise 对象
await promise 实例
1 | async function whatName(ms) { |
async 函数返回的 Promise 状态由函数内 await 后面的 promise 实例的状态决定。如果 async 内部发生错误或者某一个 await 发生错误,那么该错误就会被 async 函数的 Pomise 实例的 catch 方法捕获。并且发生错误的await后面的函数体不再执行。只有 async 函数内部所有异步操作执行完,才会执行 then 方法指定的回调函数
1 | async function fn() { |
Iterator
Iterator(遍历器): 是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构,只要部署了Iterator 接口,就可以完成遍历操作
Iterator作用:
为各种数据结构提供统一的访问接口
- 使得数据结构的成员能够按照某种次序排列
- 供 for…of 消费
Iterator的遍历过程:
- 创建一个指针对象,指向当前数据结构的起始位置
- 第一次调用指针对象的 next 方法,将指针指向数据结构第一个成员
- 第二次调用指针对象的 next 方法,将指针指向数据结构第二个成员
- 不断调用 next 方法,直到遍历结束
next方法会返回数据结构的当前成员的信息。即一个包含 value 和 done 属性的对象。value 是当前成员的值, done 属性是一个布尔值,表示遍历是否结束
默认 Iterator 接口
数据结构只要部署了 Iterator 接口,就是可遍历的
许多数据结构都默认部署了 iterator 接口,包括:
Array
Map
Set
NodeList对象
String
arguments
TyperArray
默认的 Iterator 接口都部署在数据结构的 Symbol.iterator 属性中。
Symbol.iterator
是一个表达式,返回 Symbol 对象的 iterator 属性。该属性是个函数,会返回一个指针对象
1 | const obj = { |
for…of
for...of
是ES6标准,用来遍历value值,遍历数组,不能遍历普通对象
1 | // 遍历数组 |
for...of
不能遍历普通对象的原因原因是:普通对象没有Symbol.iterator属性,如果一个对象拥有Symbol.iterator属性,那么就可以使用for…of遍历
ES6的module
ES6的模块化的基本规则或特点:
每一个模块只加载一次, 每一个JS只执行一次, 如果下次再去加载同目录下同文件,直接从内存中读取。 一个模块就是一个单例,或者说就是一个对象;
每一个模块内声明的变量都是局部变量, 不会污染全局作用域;
模块内部的变量或者函数可以通过export导出;
一个模块可以导入别的模块
export
导出 import
导入
1 | export var c = 18; |
ES6的模块化实现
如何实现模块化,在html中需要使用type='module'
属性。
1 | <script src="aaa.js" type="module"></script> |
此时表示aaa.js是一个单独的模块,此模块是有作用域的。如果要使用aaa.js内的变量,需要在aaa.js中先导出变量,再在需要使用的地方导出变量。
直接导出
1 | export let name = '小明' |
使用
1 | import {name} from './aaa.js' |
./aaa.js
表示aaa.js和mmm.js在同级目录。
统一导出
1 | var age = 22 |
使用
import {name,flag,sum} from './aaa.js'
导入多个变量
1 | import {name,flag,sum} from './aaa.js' |
使用{}将需要的变量放置进去
导出函数/类
在aaa.js中添加
1 | //3.导出函数/类 |
在mmm.js中添加
1 | import {name,flag,sum,say,Person} from './aaa.js' |
导入 export default
导出
1 | export default { |
导入
1 | //4.默认导入 export default |
注意:使用默认导出会将所有需要导出的变量打包成一个对象,此时导出一个对象,此时我在
mmm.js
中导入变量时候命名为aaa,如果要调用变量需要使用aaa.变量。
统一全部导入
使用
import * as aaa from './aaa.js'
统一全部导入
1 | // 5.统一全部导入 |
前端模块化
随着前端项目越来越大,团队人数越来越多,多人协调开发一个项目成为常态。
例如现在小明和小张共同开发一个项目,小明定义一个aaa.js,小张定义了一个bbb.js。
1 | //小明开发 |
此时小明的sum
是没有问题的。
1 | //小红 |
此时小明和小红各自用各自的flag
你变量没问题。
1 | //小明 |
在index.html页面导入这些js文件
1 | <script src="aaa.js" ></script> |
此时小明知道自己在aaa.js中定义的flag
是true
,认为打印没有问题,但是不知道小红的bbb.js中也定义了flag
为true
,所以mmm.js文件并没有打印出“flag是true”。
这就是全局变量同名问题。
使用导出全局变量模块解决全局变量同名问题
aaa.js
1 | //模块对象 |
mmm.js
1 | //小明 |
这样直接使用aaa.js导出的moduleA变量获取小明自己定义的flag
。
CommonJS的模块化实现
CommonJS需要nodeJS的依支持。
aaa.js
1 | //CommonJS需要nodeJS支持 |
使用module.exports = {}
导出需要的对象。
mmm.js
1 | //导入对象,nodejs语法,需要node支持,从aaa.js取出对象 |
使用 var {flag,sum} = require("./aaa")
获取已经导出的对象中自己所需要的对象。