ES5&ES6
1 ES5
1.1 严格模式
比普通js运行机制要求更严格的模式。几乎所有js都要运行在严格模式下,js语言有广受诟病的很多缺陷,严格模式将不合理的行为直接变为错误;提高效率、增加运行速度。
在所有js文件/script标签顶部: “use strict”;在函数定义内的顶部添加:“use strict”,匿名函数可作为script开启严格模式的变通实现;
严格模式要求:
- 禁止给未声明的变量赋值:
- 静默失败升级为错误:
静默失败: 执行不成功,也不报错 - 禁用了arguments.callee:
arguments.callee专门在调用函数时,获得当前函数本身
禁用是因为递归重复计算量太大,效率极低,所以禁用递归中的arguments.callee,用循环代替 - 普通函数调用中的this不再指向window而是undefined
1.2 保护对象
1.2.1 保护属性
普通js中对象的属性值可以是任何值,也可以随意修改
属性可分为:命名属性(可直接用.访问的属性,可分为数据属性和访问器属性)和内部属性(__ proto__、constructor、class)
数据属性:
属性: {
value: 实际存储属性值,
writable: true/false, //控制是否可修改
enumerable: true/false, //控制是否可被for in遍历
//只能防住for in,但是用.依然可访问
configurable: true/false //控制是否可删除该属性
//还控制是否可修改另外两个特性
//一旦改为false,不可逆!修改其它两个开关同时修改configurable:false作为双保险。
}
获取一个属性的四大特性:
var miniObj=Object.getOwnPropertyDescriptor(obj, "属性名");
修改一个属性的四大特性:
Object.defineProperty(obj,"属性名",{
开关: true/false,
... : ...
})
修改多个属性的四大特性:
Object.defineProperties(obj,{
属性名:{
开关:true/false,
... : ...
},
... : {
}
})
四大特性除value外,默认值为false
数据属性开关不能用自定义规则保护属性,使用访问器属性:
访问器属性: 不实际存储属性值,仅提供对其他数据属性的保护;访问器属性不能用直接量定义,只能用defineProperty/defineProperties方法添加
使用隐藏的数据属性实际存储数据,再添加一个访问器属性来保护隐藏的数据属性:
Object.defineProperty(obj,"访问器属性名",{
get:function(){ return this.受保护的数据属性 },//获取属性值自动调用get()
set:function(value){//修改属性值自动调用set()
//value可接住将来要赋的新值
//如果value符合条件
//才this.受保护的数据属性=value
//否则
//报错throw new Error("")
},
enumerable:true, //让访问器属性代替数据属性抛头露面
configurable:false
})
可以绕过访问器直接修改受保护的数据属性值_age,所以需要将受保护的属性值保存在闭包中
构造函数可以同时创建保护多个属性值的对象
function Emp(eid,ename,salary,age){
Object.defineProperties(this,{
eid:{
...
},
_age:{
writable:true,
enumerable:false
}
})
this.age=age;//将传入的参数age指向原型对象,否则跳过条件判断直接赋给_age
}
//Emp.prototype.age={//访问器属性只能通过defineProperty定义
Object.defineProperty(Emp.prototype,"age",{
get:function(){ return this._age; },
set:function(value){
if(value>=18&&value<=65)
this._age=value;
else throw Error("年龄超范围");
},
enumerable:true,
configurable:false
});
可以将受保护的属性值保存在闭包中,以防止绕过访问器属性,直接修改属性的值,这样一来,访问器属性不能放在原型对象中,最终ES6还是将访问器属性放在原型对象中,默认程序员不随意使用以下划线开头的变量名
1.2.2 保护结构
构造函数创建的对象,保护结构的语句写在构造函数内部,Eg. Object.seal(this)
- 防扩展: 禁止给当前对象添加新属性
Object.preventExtensions(obj) - 密封: 在兼具防扩展的同时,又禁止删除现有属性
Object.seal(obj)
原理:
1. Object.preventExtensions(obj)
2. 自动将所有属性的configurable都设置为false - 冻结: 在兼具密封的基础上,又禁止修改属性值
Object.freeze(obj)
原理:
1. Object.seal(obj)
2. 又自动将所有属性的writable都设置为false
1.3 Object
1.3.1 Object.create()
Object.create(): 创建新对象继承现有的指定父对象,同时为新对象添加自有属性
var child=Object.create(father,{
//为child添加新自有属性
//语法相当于defineProperties
属性名:{
value:属性值,
开关: true/false,
... : ...
}
})
原理: 1. 创建一个空对象
2. 让空对象继承father
3. 为新对象添加自有属性
1.3.2 替换this:
如果一个函数调用时,其中的this不是想要的,就可替换为想要的对象。有三种方式:
- call/apply:强行调用指定函数,并临时替换一次this:
call(): 要求传入函数的参数分别传入
apply(): 要求传入函数的参数必须放在一个数组中整体传入。
指定函数.call(替换this的对象,参数值,…) - bind():基于原函数,创建新函数副本,并永久绑定this:
var 新函数=原函数.bind(替换this的对象, 其他参数…)
当需要替换回调函数中的this时使用
1.4 数组API
- 查找元素indexOf: 查找一个指定元素在数组中的位置
var i=arr.indexOf(elem[, starti ])
用法同str.indexOf() - 判断: 数组中的元素是否符合要求:
- 判断数组中的元素是否都符合要求:
var bool=arr.every(function(val, i, arr){
//val->当前正在遍历到的元素值
//i->当前正在遍历的位置
//arr->当前正在遍历的数组对象
return 判断条件;
})
依次判断arr中每个元素,是否都符合“判断条件”的要求
执行过程: every自动拿着回调函数,在每个元素上执行一次
每次执行时,都自动传入: 当前元素值,当前位置, 当前数组对象
每次执行回调函数后,都返回对当前元素的判断结果。
只有所有元素经过判断都返回true时,every才返回true - 判断数组中是否包含符合要求的元素:
var bool=arr.some(function(elem,i,arr){
return 判断条件
})
- 判断数组中的元素是否都符合要求:
- 遍历: 依次对数组中每个元素执行相同的操作
- 对原数组中的元素执行相同的操作:
arr.forEach(function(elem,i,arr){
操作
})
会自动拿着回调函数,在每个元素上执行一次! - 复制出原数组中的元素,执行相同操作后,放入新数组中,原数组保持不变。
var 新数组=arr.map(function(elem, i, arr){
return 新元素值;
})
map: 3件事:- 创建一个新的空数组
- 自动在每个元素上调用一次回调函数,将返回的新值,放入新数组中相同位置
- 返回新数组
- 对原数组中的元素执行相同的操作:
- 过滤和汇总:
- 过滤: 复制出原数组中符合条件的元素,组成新数组返回
var 新数组=arr.filter(function(elem,i,arr){
return 判断条件;
})
执行过程: filter会自动拿着回调函数在每个元素上执行一次
每次执行时,如果当前元素符合“判断条件”要求,就复制到新数组中。 - 汇总: 将数组中的所有元素,经过统计,得出一个最终结论
var result=arr.reduce(function(prev,elem,i,arr){
return 将当前元素的内容汇总到prev中
},start);
//start: 开始的基础值
//prev: 截止目前位置,之前的元素的临时汇总值
- 过滤: 复制出原数组中符合条件的元素,组成新数组返回
2 ES6
ES6: 不改变原理的基础上,尽量简化代码
2.1 块级作用域
let: 代替var声明变量
js有两个广受诟病的缺陷: 声明提前&没有块级作用域
if while do…while for 都是一级作用域
let块级作用域不会声明提升也不能重复声明
实现原理:块级作用域内自调用匿名函数;变量名前加下划线防止变量名冲突
2.2 参数增强
-
默认参数: 即使不提供实参,形参也有备用的参数值
function fun(形参1, …, 形参n=默认值)
强调: 带默认值的参数必须定义在参数列表的末尾 -
剩余参数: 代替arguments,接收不确定个数的参数值
arguments的问题:- 不是纯正的数组类型,无法使用数组的API
- 只能获得全部参数值,无法有选择的获得部分
定义函数: function fun(形参1,…数组名){ … }
执行时: …可自动获得除形参1之外的剩余参数,保存在一个数组中。
1. …后的数组名获得的是一个纯正的数组
2. 可有选择的获得部分参数值 -
散播spread: 专门执行打散数组传入函数的操作(apply的本职是替换this,顺便打散数组)
调用函数fun(…数组)
执行时 …会先将数组打散为单个元素,再分别传入fun中
;当不允许使用时,fun.apply(null, 数组)
2.3 箭头函数
箭头函数: 用于简化回调函数和匿名函数的
- 去function,改为=>
- 如果函数体只有一句话,则可省略{}
如果仅有的一句话还是return,则必须省略return - 如果只有一个形参,则可省略()
箭头函数特点:
1)一旦改为箭头函数后,内外this一致,如果不希望内外this相同时,就不能用箭头函数;
2)function可以定义构造函数,而箭头函数不能;
3)当不是作为参数时,箭头函数定义需要关键字var(let,const),且不会发生变量提升,故需要定义在调用之前
2.4 模板字符串
动态生成内容提前定义的字符串模板,取代字符串拼接
用反引号``包裹字符串模板,支持换行,不和单双引号冲突,动态内容用${}包裹
2.5 解构
解构: 从一个大的对象中,提取出想要的成员,单独使用
- 数组解构: 将一个大的数组中的元素提取出来单独使用:
var [变量1, 变量2, …]=[元素1, 元素2, …]
结果: 变量1=元素1; 变量2=元素2; - 对象解构: 从一个对象中提取出想要的成员单独使用
var {属性1:变量1, 属性2:变量2, …}=
{属性1:值1, 属性2:值2, …}
结果: 变量1=值1; 变量2=值2;
如果保存对象的属性值和变量值相等,可以只写var {变量1,变量2,…} - 参数解构: 其实是对象解构在函数传参时的应用
传统参数定义是固定个数,固定先后顺序,无法灵活选择需要传入的参数- 定义时: 将参数列表定义为对象语法:
//function fun({属性1:形参1,属性2:形参2,…})
function fun({形参1,形参2,…}){ … }//简写,对象解构中变量值和属性值保持一致 - 调用时: 将传入的参数放在一个对象中整体传入
fun({
属性1: 值1, …
})
执行: fun将整个实参对象传给形参对象,形参对象通过解构,从实参对象中抽取对应的参数值。 如果找不到对应的,则形参值默认为undefined,在函数内部定义port=port||"3306"则未传值的情况下赋值3306。
- 定义时: 将参数列表定义为对象语法:
2.6 遍历
for…of…: 最简化的遍历数组和类数组对象的方法
-
for循环: 最灵活的
for(var i=0;i<arr.length;i++){ var elem=arr[i]; }
-
forEach:
arr.forEach((elem,i,arr)=>{ elem })
局限: 无法控制循环的方向和步调,只能顺序依次遍历每个元素 -
for of: 只能获得元素值,无法获得当前位置
for(var elem of arr){ //of会依次获得arr中每个元素的值 }
for(var key in obj/关联数组)——自定义下标名
for(var elem of arr/类数组对象)——数字下标
2.7 class类
class: ES6对整个面向对象语法的简化:
-
封装:对象直接量的简化:
var sname="Li Lei", sage=11; var lilei={ sname, //sname: sname, sage, //sage: sage, intr(){ ... } //:function(){ ... } }
-
对创建类型的简化:
//1. 用class{}包裹构造函数和原型对象 class Student{ //2. 构造函数名提升为类型名,构造函数统一更名为 constructor constructor(sname,sage){ this.sname=sname; this.sage=sage; } //3. 所有原型对象方法可直接写在class中 intr(){ console.log(`I'm ${this.sname}, I'm ${this.sage}`); } }
-
继承:
//1.让子类型继承父类型: Plane extends Enemy //不再需要Object.setPrototypeOf class Plane extends Enemy{ constructor(fname,speed,score){ //2.用super(fname,speed)调用father.constructor //不再需要call,不再需要传this super(fname,speed); this.score=score;//写在构造函数内 } }
-
静态方法:
class 类名{ constructor(){ } 方法(){ ... } static 静态方法(){ ... } } //调用:类名.静态方法()
-
访问器属性:
class Emp{ constructor(eid,ename,age){ this.eid=eid; this.ename=ename; Object.defineProperty(this,"_age",{ writable:true, enumerable:false, configurable:false }) this.age=age; } get age(){return this._age} set age(value){ if(value>=18&&value<=65) this._age=value; else throw Error("年龄超范围"); } }
2.8 Promise
代替异步调用中的回调函数(可能导致callback hell:函数不过早地提交给上一个函数)
只要多个异步调用有先后执行顺序时就需要使用promise
-
定义函数
在原函数内,用new Promise()包裹所有原代码,再在源代码外层套一层function(){}
function()中必须接收Promise自带的open开关,在当前函数异步任务调用后,自动打开开关open();function fun(){ return new Promise(function(open){ 异步任务 异步任务执行完:open() }) }
-
串联多个任务
第一个函数().then(第二个函数).then(…)
中间的then中的函数不要加(),因为不是立刻执行,且中间的函数必须支持Promise
错误处理:
1.new Promise(function(open,err){
err(“错误消息”) //正常执行调用open(),出错调用err()
})
2.在函数1().then().then().catch(function(errMsg){ … })
无论中间哪个then出错,都会执行最后的catch,并将then中err(“错误消息”)传给errMsg。