ES
JS包含三个部分:ECMAScript(核心)、扩展浏览器端、扩展服务器端
1. ECMAScript:2009年发布ES5,2015年发布ES6,2016年发布ES7
2. 扩展浏览器端:BOM(浏览器对象模型)、DOM(文档对象模型)
3. 扩展服务器端:Node.js
ES5
1. 严格模式:除了正常运行模型(混杂模型),ES5增加了严格模式(strict mode)
1. 消除JS语法的一些不合理、不严谨、不安全之处,减少一些怪异行为;
2. 使用:在全局/函数的首行定义'use strict'; 即使不支持,也没有任何副作用;
2. 严格模式的语法和行为:
1. 变量必须用var声明,混杂模式下,如果不使用,则升级为全局变量,即添加给window
2. 禁止自定义函数中的this指向window
function Person(name) { this.name = name; }
Person('Mack') --> 直接调用构造函数,则this指向window,严格模式不允许
new Person('Mack') --> 创建实例对象,则this指向当前的实例对象
3. 创建eval作用域:var str = 'Mack';
eval('var str = "Any"; alert(str);'); --> var str = "Any"; alert(str);
alert(str); --> 非严格模式下,str='Any',那么就可以借助eval()攻击
4. 对象不能有重名的属性。
3. 为Object扩展一些静态方法,如create()、defineProperties()、defineProperty()
var obj = { name: 'Mack', age: 30 }
1. create(prototype, [descriptors]):以指定对象为原型,创建新的对象;
var obj2 = {}; var obj4 = {};
obj2 = Object.create(obj); --> obj2仍是空的,但其隐式原型指向obj
obj4 = Object.create(obj, {
sex: { --------------> 扩展属性
value: 'Male', ---> 属性值
writale: true, ---> 当前属性值是否可修改,默认为false
configurable: true, ---> 当前属性是否能被删除,默认为false
enumerable: true ------> 当前属性能否被for-in遍历,默认为false
}
})
2. defineProperties(object, props):为指定对象定义扩展属性。
Object.defineProperties(obj, {
job: { ---> 扩展新的属性
get: function() { --> 访问扩展属性时回调,计算当前属性值
return this.name + ':' + this.age;
},
set: function(data) { --> 修改属性时回调,用于监视属性的变化
var n = data.split(':')
this.name = n[0]; this.age = n[1]
},
}
})
console.log(obj.job) --> 回调get():'Mack:30'
obj.job = 'Any:16' --> 回调set():name='Any',age=16
2. getter和setter都是惰性执行的,只有在访问/修改属性时才会调用,直接打印obj并不会
直接显示job的属性值。
3. 对象本身也有getter和setter,其实defineProperties()中调用的也是它们;
var obj = { name: 'Mack', age: 30,
get job() {
return this.name + ':' + this.age;
},
set job(data) {
var n = data.split(':')
this.name = n[0]; this.age = n[1]
}
}
4. defineProperty(obj, prop, descriptor):参数3为将被定义或修改的属性描述符
4. 数组Array扩展的方法
1. indexOf(value)/lastIndexOf(value):返回元素的索引;
2. forEach(function(item, index){ ... }):遍历数组;
3. map(callback):计算每一个数组元素,并不会影响原数组,而是返回一个新的数组;
4. reduce(function(arg1, arg2){ ... }):两两计算,返回最终结果;
5. filter(callback):过滤数组的元素,返回一个新的数组;
6. find(callback)、findIndex(callback):返回第一个为true的数组元素、数组角标;
7. sort(callback)、every(callback)、some(callback);
5. 函数扩展的方法:call(),apply(),bind()
1. call/apply/bind()都能修改函数中的this指向;
2. bind(obj):让函数内的this指向obj,但不会执行函数,而是将函数返回;
3. bind()传递参数的方式与call()相同,通常用于修改回调函数中的this指向。
ES6
1. 声明变量:let、const
1. let:块级作用域,不能重复声明,不会预处理,不存在变量提升;
2. const:与let类似,但它定义的是常量,不能修改;
2. 变量的结构解析(解构赋值):从对象/数组中提取数据,并赋值给一个/多个变量;
1. 对象:let obj = { name: 'Mack', age: 12 }
let {name, age} = obj; -->变量名必须与对象中的属性名保持一致,位置可以任意排列
console.log(name, age); --> name='Mack', age=12
let {name} = obj; --> 需要哪些属性,就获取哪些属性
2. 数组:let arr = [ 1, 3, 5, false, 'hello' ]
let [ a, b ] = arr; --> 变量值与位置有关,与数组的下标对应
let [,,, a, b] = arr; --> a=false, b='hello'
3. 模板字符串:``和${xxx}
1. `` 是模板字符串的标识,${xxx} 是变化的部分,简化字符串的拼接
2. let str = `My name is ${obj.name}` --> 'My name is Mack'
4. 对象属性/方法的简写方式:let name = 'Mack'
let obj = { name, ---> 等同于 name: name,同名属性可以省略
getName() { ... }, --> 等同于 getName: function() { ... }
}
5. 形参默认值:function test(x=1, y=2, z) { ... }
箭头函数
1. 箭头函数:(arg1, arg2,) => { ... },类似于lambda表达式
1. 无参数:let fun = () => console.log('Hello JS');
fun(); --> fun指向箭头函数,fun()调用箭头函数
2. "=>"后是单条语句时,可省略"{}",且该语句执行的结果会作为函数的返回值;
3. 一个参数:let fun = a => console.log(a);
4. 多个参数:let fun = (a, b) => console.log(a, b);
5. 多条语句时,"{}"不能省略,函数的返回值仍默认为undefined
let fun = () => {
return 'Hello World'; --> 手动指定返回值
}
2. 箭头函数的this:
1. 箭头函数没有自己的this,它的this不是调用时决定的,而是在定义时所处的环境决定的;
2. 如果箭头函数直接定义在一个对象/函数中,那么箭头函数的this就是该对象/函数的this,
否则,箭头函数的this指向window;
3. 也就是说,箭头函数的this取决于箭头函数所定义的外层空间;
element.onclick = function() { ...// this就是element对象 }
element.onclick = () => {
// 箭头函数定义在全局作用域,也即定义在window中,那么this指向window
}
3. 可变参数:...rest,用于取代arguments
1. arguments只是类似于数组,但并不具备数组的方法,如forEach()
2. ...rest只接收多余的实参,而且是一个数组;
function(a, b, ...rest) { --> 必须放在参数的最后位置
rest.forEach((item, index) => { ... }) --> 遍历多余参数
}
3. 扩展应用-三点运算符:let arr = [2, 3, 4];
let arr2 = [1, ...arr, 5] --> [1, 2, 3, 4, 5]
console.log(...arr2) --> 1 2 3 4 5,三点运算符会自动遍历数组元素
4. 三点运算符还可以浅拷贝复制数组和对象
let copyArr = [...arr]
let obj = { name:'Mack' } ---> let copyObj = { ...obj }
Promise
1. Promise对象:表示未来某个将要发生的事件,通常是一个异步操作;
2. Promise可以将异步操作以同步的方式表达出来,避免了层层嵌套的回调(回调地狱);
3. 使用Promise:Promise是ES6的一个构造函数
1. 创建Promise对象:
let promise = new Promise((resolve, reject) => {
//Promise为初始化状态:pending
...... //执行异步操作
if(异步操作成功) { resolve(value); } -->修改Promise为成功状态:fullfilled
else { reject(errMsg); } --> 修改Promise为失败状态:rejected
});
2. 调用Promise的then()
promise.then(result => { -->执行resolve(),则回调then()的第一个函数
console.log(result);
}, errMsg => { --> 执行reject(),则回调then()的第二个函数
console.log(errMsg);
}
) --> 回调参数result、errMsg分别是resolve()、reject()传递的value、errMsg
3. 封装AJAX请求:
fuction getNews(url) {
let promise = new Promise((resolve, reject) => {
let http = new XMLHttpResponse();
http.onreadystatechange = function() {
if(http.readyState!==4) return;
if(http.status == 200) {
resolve(http.responseText); -->请求成功,修改状态
} else {
reject('暂时没有数据'); --->请求失败,修改状态
}
};
http.open('GET', url); http.send(); -->发送请求
});
return promise;
}
getNews('http://').then(res => { ······
return getNews(res.url); -->发送第二次AJAX请求,必须返回一个
}, error => { ······ } --------> Promise对象,then()可以链式回调;
).then(res => { ----> 回调第二次AJAX的结果
······
}, error => { ······ });
Symbol属性
1. ES5中对象的属性名都是字符串,容易重名,污染环境;
2. Symbol是ES6新增的一种数据类型,它对应的值是唯一的;
3. 不能与其他数据计算,包括字符串拼接,for-in/for-of不会遍历Symbol属性;
let sym = Symbol(); --> Symbol不是构造函数,不用new创建
let obj = { name: 'Mack' }
obj[sym] = 'Hello'; --> 为对象添加Symbol属性,其属性值为'Hello'
4. 创建Symbol时还可以传递参数,作为Symbol的标识;
let sym = Symbol('Hello')
const SYM = Symbol('key') ---> 定义为常量
5. ES6还提供了11个内置的Symbol值,指向内部使用方法,如Symbol.iterator
Iterator
1. Iterator:一种接口机制,为各种不同的数据结构提供统一的访问机制;
2. ES6提供了一种新的遍历方式:for-of,Iterator主要供for-of消费;
3. 模拟Iterator的原理:let arr = [1, 4, 27, 'abc']
function myIterator(arr) {
let index = 0; ---> 记录指针的位置
return { -----> 遍历器对象
next: function() {
return index < arr.length ? {value:arr[index++], done:false}
: { value: undefined, done: true }
}
}
}
let iter = myIterator(arr);
console.log(iter.next()); --> { value: 1, done: false }
......
console.log(iter.next()); --> { value: 'abc', done: false }
console.log(iter.next()); --> { value: undefined, done: true }
4. 将Iterator接口部署到指定的数据类型上,则可以用for-of遍历;
1. JS的原生数据类型中具备Iterator接口的有:String、Array、arguments、Set、Map
2. for(let i of [1,2,3]) { ...//i就是每次循环的元素 }
5. Symbol.iterator
1. String、Array、arguments等对象具备Iterator接口,但普通对象并没有Iterator接口,
所以不能使用for-of遍历;
2. 为普通对象添加Symbol.iterator属性,指向其默认遍历器方法,for-of就能遍历该对象;
3. Symbol.iterator属性添加给对象,就等同于为该对象部署了Iterator接口;
let data = {
[Symbol.iterator]: function() {
let index = 0; ---> 记录指针的位置
return { ... } ---> 遍历器对象
}
}
4. 三点运算符和解构赋值,默认调用的就是Iterator接口。
Generator
1. Generator函数:ES6提供的解决异步编程的方案之一;
function* test() {
yield 'test1'
yield 'test2'
}
2. 调用Generator函数,返回一个指针对象,由该对象调用next(),遇到yield返回;
let gen = test();
console.log(gen.next()); --> {value: "test1", done: false}
console.log(gen.next()); --> {value: "test2", done: false}
console.log(gen.next()); --> {value: undefined, done: true}
3. 为普通对象添加Symbol.iterator属性,指向一个Generator函数;
let obj = { name: 'Mack' }
obj[Symbol.iterator] = function* gen() { -->为对象部署Iterator接口
yield 1
yield 2
}
for(let i of obj) { ---> 具备Iterator接口的对象,for-of都可以遍历
console.log(i) --> 1 2
}
4. let result = yield 'test1' --> yield的返回值默认是undefined
5. next(arg):参数arg会先赋值给当前指针所在的yield,再向下移动;
function getNews(url) {
$.get(url, function(data) { --> jQuery-GET请求成功的回调
HTTP.next(data.url); ---> 获取新的URL,发送第二次请求
});
}
function* sendHttp() {
let url = yield getNews('http://...') --> 第一次next()
yield getNews(url) --> 第二次next(),需要传递参数
}
let HTTP = sendHttp(); ---> 获取遍历器对象
HTTP.next(); --> 发送第一次GET请求
async
1. async函数:真正意义上解决回调地狱的问题,同步的方式表达异步操作;
2. async函数的本质是Generator的语法糖,源自ES2016(ES7);
1. async函数返回的总是Promise对象,用then()进行下一步操作;
async function test() { ---> 函数test变成一个异步函数
return 'hello async';
}
test().then(res=>{
console.log(res); -->hello async
})
2. async取代Generator函数的"*",await取代Generator函数的yield;
async function test() {
await 异步操作; --------> 该异步操作总是返回一个Promise对象
await 异步操作;
}
3. async函数不需要像Generator函数那样去调用next(),遇到await就会等待,当前的异步操作
完成后,则继续向下执行。
3. async的使用方式
1. 基本使用
async function test() { return 'hello async'; }
async function exec() {
let t = await test(); --> 阻塞程序,等待异步方法test() 执行完成
console.log(t)
}
2. async函数返回一个Pormise对象,await总是配合Promise使用,返回值由resolve()决定
async function test() {
let res = await new Promise((resolve, reject) => {
setTimeout(() => { resolve('Mack'); }, 2000);
})
console.log(res); --> 2000ms之后打印 Mack
}
3. await语句默认返回undefined,resolve()可以为awiat传递参数,reject()表示异步任务
失败,会终止继续向下执行。
4. 发送AJAX请求:
function getNews(url) {
return new Promise((resolve, reject) => {
$.ajax({ method: 'GET', url,
success: data => resolve(data), --> GET请求成功
error: error => resolve(error) --> 请求失败不使用reject()
}) ------> 那么await会继续向下执行,由此判断成功还是失败
})
}
async function sendHttp() {
let res = await getNews('http://...')
if(res) { --> 判断res的状态
res = await getNews(res.url) --> 继续执行异步任务
} else { alert('请求失败') --> 请求失败,则提示给用户 }
}
sendHttp();
其他新增
1. class:可以定义类、实现类的继承,通过类中的constructor定义构造方法;
1. new 创建类的实例对象,extends实现类的继承,super调用父类的构造方法;
class Person {
constructor(name, age) { ---> 构造函数
this.name = name; this.age = age; --> 定义属性
}
showName() { -------------> 类的方法必须是简写形式
console.log(this.name);
}
static area = 2; // 静态属性
static run() { // 静态方法
console.log('static method');
}
}
2. 创建实例对象:let p = new Person('Mack', 18)
3. 静态方法/属性只能通过类名调用:Person.run(); Person.area;
4. extends:类的继承
class Stu extends Person {
constructor(name, age, job) {
super(name, age); ------> 必须先初始化父类的构造方法
this.job = job;
}
showName() { ...... } ---> 重写父类的方法
}
5. new.target:保存了通过 new 关键字调用的类或函数,以此判断抽象基类
class Person { // 模拟抽象基类
constructor() {
if(new.target === Person) {
throw new Error("我是抽象基类,不允许new实例");
}
if(!this.foo) {
throw new Error("子类必须定义方法 foo");
}
}
}
class Stu extends Person {
foo() { } // 如果没有该方法,也会报错
} // 派生类
let p = new Person(); // 报错
let s = new Stu (); // 通过
2. 字符串的扩展
1. includes(str):判断是否包含某个字符串;
2. startsWidth(str)/endsWidth(str):判断是否以某个字符串开头/结尾;
3. repeat(count):把当前字符串拼接count次,并返回;
3. number的扩展
1. 二进制与八进制的数值表示:0b、0o
2. Number.isFinite(num):判断num是否是一个有限大的是数字;
3. Number.isNaN(num)/isInteger(num):判断num是否是NaN/整数;
4. Number.parseInt(str):把字符串转为数值;
5. Math.trunc(num):取整,去除小数部分。
4. Array的扩展
1. Array.from(v):将伪数组/可遍历对象转为真实的数组,包括Set、Map、字符串
低于ES6的版本:Array.prototype.slice.call(v)
2. Array.of(v1, v2, v3):将一系列的值转为一个数组[v1, v2, v3]
3. find/findIndex((value, index, arr) => { return true })
查找第一个满足条件返回true的元素/元素下标:let arr = [2, 3, 5, 7, 1]
let res = arr.find(function(v, i) { return v > 4 }) --> 5
5. 对象方法的扩展
1. Object.is(v1, v2):判断2个数据是否完全相等;,Object.is(NaN, NaN) -->true
Object.is(0, -0) --> false,其实该方法是按照字符串的标准比较的
2. Object.assign(target, s1,s2,...):将源对象s1、s2...的属性复制给目标对象target
let s1 = { name:'Mack', job:'Teacher' }; let s2 = { name:'JJJ', age:20 };
let target1 = {}
let target2 = Object.assign(target, s1,s2); ---> s2的name属性会覆盖s1的name属性
--> target1和target2:{ name:'JJJ', age:20, job:'Teacher' }
3. 直接操作隐式原型__proto__,ES6之前不能访问隐式原型。
6. 深度克隆
1. 浅拷贝:拷贝的是引用,修改拷贝后的数据,会影响原数据;
2. 深拷贝对象/数组:JSON.parse(JSON.stringify(data))
3. 三点运算符、Object.assign(target, s1,s2) 都是浅拷贝
Set、Map、WeakSet、WeakMap
1. Set:无序、不可重复的、多个value的集合体;
1. let set = new Set(); --> set.size:value的个数
2. let set = new Set(array); -->会去除array中的重复数据;
3. 添加、删除、清空:add(value)、delete(value)、clear()
4. has(value):判断value是否存在。
2. Map:无序的、key不重复的、多个key-value的集合体;
1. let map = new Map(); --> set.size:key的个数
2. new Map([[1], [4,5], [7,8,9]]) -->{ 1=>undefined, 4=>5, 7=>8 }
3. 添加/修改、获取value、删除:set(key, value)、get(key)、delete(key)
4. has(key):判断key是否存在; clear():清空。
3. WeakSet和WeakMap设计的目的是不干扰垃圾回收机制,避免内存泄漏。
1. WeakSet存放的元素、WeakMap的键 都必须是对象,且它们对该对象的引用是弱引用。
2. 正常情况下,当引用一个对象时,垃圾回收机制的计数器会 +1 ,但弱引用不会!
let dom = document.getElementById('example') // Dom对象的引用计数器 +1 --> 1
let set = new Set()
set.add(dom) // Dom对象的引用计数器 +1 --> 2
let weakset = new WeakSet()
weakset.add(dom) // Dom对象的引用计数器不变 --> 2
4. 当访问弱引用指向的对象时,该对象可能已经被回收了。正因如此,WeakSet和WeakMap是不可枚举的(不可遍历)!
ES7
1. 指数运算符:3**3 = 27
2. Array.prototype.includes(value):判断数组中是否包含value