版本历史
ES5 浏览器可用性
https://www.caniuse.com/#search=es5
ES6 浏览器可用性
https://www.caniuse.com/#search=es6
JS
包含三个部分:
ECMAScript(核心)
,
DOM(文档对象模型)
,
BOM(浏览器对象模型)
。
ECMAScript
是
JS
语言的基础。
ECMAScript
的最新版是第六版
ECMAScript 6
,于2015年6月17日发布,截止发布日期,
JavaScript
的官方名称是
ECMAScript 2015
,是当前最新的正式规范。
ECMAScript
的各个版本:(从第三版开始说)
-
第三版
ECMAScript3
新增了对正则表达式、新控制语句、try-catch异常处理的支持,修改了字符处理、错误定义和数值输出等内容。标志着ECMAScript成为了一门真正的编程语言。 - 第四版于2008年7月发布前被废弃。
-
第五版
ECMAScript5
力求澄清第3版中的歧义,并添加了新的功能。 新功能包括:原生JSON对象、继承的方法、高级属性的定义以及引入严格模式。 -
第六版
ECMAScript6
是继ES5之后的一次主要改进。 增添了许多必要的特性,例如:模块和类以及一些实用特性,Maps、Sets、Promises、生成器(Generators)等。
ES5
支持IE9及以上
最常用的就是JSON对象了,早期的浏览器就要加载js插件来实现。
严格模式
为整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句
"use strict";
(或
'use strict';
)
// 整个脚本都开启严格模式的语法
"use strict";
var v = "Hi! I'm a strict mode script!";
严格模式,严格模式,重要其实就是es5中弃用了很多以前版本的语法,你再用就提示错误。
一句话说明了这个的用途,我们只要知道哪些语法被弃用了(es3,es2,es1),就知道哪些会在使用这个模式下报错了。
简单罗列弃用限制和es5不符合语法定义限制:
- 不使用var声明变量严格模式中将不通过
-
任何使用
eval
的操作都会被禁止 - eval作用域
- with被禁用
- caller/callee 被禁用
- 对禁止扩展的对象添加新属性会报错
- 删除系统内置的属性会报错
- delete使用var声明的变量或挂在window上的变量报错
- delete不可删除属性(isSealed或isFrozen)的对象时报错
- 对一个对象的只读属性进行赋值将报错
- 对象有重名的属性将报错
- 函数有重名的参数将报错
- 八进制表示法被禁用
- arguments严格定义为参数,不再与形参绑定
- 函数必须声明在顶层
- ES5里新增的关键字不能当做变量标示符使用,如implements, interface, let, package, private, protected, public, static, yield
- call/apply的第一个参数直接传入不包装为对象
- call/apply的第一个参数为null/undefined时,this为null/undefined
- bind的第一个参数为null/undefined时,this为null/undefined
JSON对象
// 声明一个“JSON格式”的String
var jsonStr = '{"a":"1","b":2,"c":"13"}';
// JSON.parse把String转成Object了
var jsonObj = JSON.parse(jsonStr);
console.log(jsonStr);
console.log(jsonObj);
console.log(JSON.stringify(jsonObj));
// 转换JSON时转换类型
var jsonObj2 = JSON.parse(jsonStr, function (key, value) {
if (typeof value == 'string') {
return parseInt(value);
} else {
return value;
console.log(jsonObj2);
// JSON.stringify 可以用来对Json对象进行简单过滤
var jsonStr2 = JSON.stringify(jsonObj, function (key, value) {
if (value == 13) {
return undefined; //使用undefined,则键值对就不会包含在最终的JSON中
} else {
return value;
console.log("这里就少了值为13的键值");
console.log(jsonStr2);
// JSON.stringify还可以用来做Json格式化
var jsonStr3 = JSON.stringify(jsonObj, null, 4);
console.log(jsonStr3);
Object对象
方法有很多,但常用的是最后一个
-
Object.getPrototypeOf
返回对象的原型 -
Object.getOwnPropertyDescriptor
返回对象自有属性的属性描述符 -
Object.getOwnPropertyNames
返回一个数组,包括对象所有自有属性名称集合(包括不可枚举的属性) -
Object.create
创建一个拥有置顶原型和若干个指定属性的对象 -
Object.defineProperty
给对象定义一个新属性,或者修改已有的属性,并返回 -
Object.defineProperties
在一个对象上添加或修改一个或者多个自有属性,并返回该对象 -
Object.seal
锁定对象。阻止修改现有属性的特性,并阻止添加新属性。但是可以修改已有属性的值 -
Object.freeze
冻结对象,阻止对对象的一切操作。冻结对象将永远不可变。 -
Object.preventExtensions
让一个对象变的不可扩展,也就是永远不能再添加新的属性 -
Object.isSealed
判断对象是否被锁定 -
Object.isFrozen
判断对象是否被冻结 -
Object.isExtensible
判断对象是否可以被扩展 -
Object.keys
返回一个由给定对象的所有可枚举自身属性的属性名组成的数组
举个例子怎么用的,这样就可以简单遍历对象了,又贴代码了
var obj = {
a: "1",
b: 2,
c: "13"
console.log(typeof obj);
let mkeys = Object.keys(obj);
for (var i = 0; i < mkeys.length; i++) {
console.log(obj[mkeys[i]]);
}
bind/apply/call
都能起到改变this的效果
"use strict"
function locate(){
console.log(this.location);
function Maru(location){
this.location = location;
var kitty = new Maru("cardboard box");
var locateMaru = locate.bind(kitty);
locateMaru();
//apply和call 立即执行,而bind如果不调用是不执行的
locate.apply(kitty)
locate.call(kitty)
Array对象
indexOf/lastIndexOf
indexOf
返回根据给定元素找到的第一个索引值,否则返回-1
lastIndexOf
方法返回指定元素在数组中的最后一个的索引,如果不存在则返回 -1
isArray
判断是否为数组
"use strict"
var str = "aa";
var arr = [1,2,3,4,5,6];
console.info(Array.isArray(str));
console.log(Array.isArray(arr));
forEach
使用foreach遍历数组的话,使用break不能中断循环,使用return也不能返回到外层函数。
遍历循环数组
//这样就方便计算数组中数据了
arr.forEach(function(item,index,array){
console.log("索引项:"+index+" 数组项:"+item);
})
map
处理原数组,返回处理后的值。
var arrObj = [
{name:"one",age:1},
{name:"two",age:2},
{name:"three",age:3},
{name:"four",age:4},
{name:"five",age:5},
{name:"six",age:6},
{name:"seven",age:7},
var ages = arrObj.map(function(obj){
return obj.age;//返回值
console.log(ages);
filter
过滤器,根据自己的条件进行过滤,filter和上面的map还是有点小区别的,自己发现吧。
"use strict"
var arrObj = [
{name:"one",age:1},
{name:"two",age:2},
{name:"three",age:3},
{name:"four",age:4},
{name:"five",age:5},
{name:"six",age:6},
{name:"seven",age:7},
var arrFilter = arrObj.filter(function(obj){
return obj.age>5;//返回值的判断条件
console.log(arrFilter);
some
判断数组中是否有满足需要的值,返回true/false
"use strict"
var arrObj = [
{name:"one",age:1},
{name:"two",age:2},
{name:"three",age:3},
{name:"four",age:4},
{name:"five",age:5},
{name:"six",age:6},
{name:"seven",age:7},
var somefour = arrObj.some(function(obj){
return obj.age>4;//判断age有没有大于4的,如果有返回true,没有返回false
var someten = arrObj.some(function(obj){
return obj.age>10;//判断age有没有大于10的,如果有返回true,没有返回false
console.log("判断有没有大于4的:"+somefour);
console.log("判断有没有大于10的:"+someten);
every
判断数组中是否所有值都满足需要,返回true/false
"use strict"
var arrObj = [
{name:"one",age:1},
{name:"two",age:2},
{name:"three",age:3},
{name:"four",age:4},
{name:"five",age:5},
{name:"six",age:6},
{name:"seven",age:7},
var everyfour = arrObj.every(function(obj){
return obj>4;
var everyten = arrObj.some(function(obj){
return obj.age>10;//判断age有没有大于10的,如果有返回true,没有返回false
console.log("判断是不是所有都大于4:"+everyfour);
console.log("判断是不是所有都大于10:"+everyten);
ES6
IE压根不支持,Edge支持
箭头操作符
方便写回调,不改变this的引用。
var array = [1, 2, 3];
//传统写法
array.forEach(function(item, index, arr) {
console.log(item);
//ES6
array.forEach(item = > console.log(item));
变量定义let和const
- let声明变量(块级作用域),let是更完美的var,它声明的全局变量不是全局属性widow的变量,这便解决了for循环中变量覆盖的问题
- const声明常量(块级作用域)
字符串新增方法
let str = 'happy';
console.log(str.includes('ha')) // true
console.log(str.repeat(3)) // 'happyhappyhappy'
console.log(str.startsWith('ha')); // true,参数2为查找的位置
console.log(str.endsWith('p', 4)); // true,参数2为查找的字符串长度
字符串模版
ES6之前
通过
\
和
+
来构建模板
$("body").html("This demonstrates the output of HTML \
content to the page, including student's\
" + name + ", " + seatNumber + ", " + sex + " and so on.");
ES6之后
-
基本的字符串格式化。将表达式嵌入字符串中进行拼接。用
${}
来界定; - ES6反引号直接搞定;
$("body").html(`This demonstrates the output of HTML content to the page,
including student's ${name}, ${seatNumber}, ${sex} and so on.`);
函数参数默认值
// ES6之前,当未传入参数时要实现:text = 'default';
function printText(text) {
text = text || 'default';
console.log(text);
// ES6;
function printText(text = 'default') {
console.log(text);
printText('hello'); // hello
printText();// default
对象合并Object.assign()
const obj1 = {
const obj2 = {
const obj = Object.assign(obj1, obj2)
console.log(obj); // {a: 1, c: 2}
console.log(obj === obj1); // true
键值对重名简写
function people(name, age) {
return {
name,
}
对象字面量简写
const people = {
name: 'lux',
getName () { // 省略冒号(:)和function关键字
console.log(this.name)
}
Spread / Rest 操作符
Spread / Rest 操作符指的是
...
,具体是 Spread 还是 Rest 需要看上下文语境。
当被用于迭代器中时,它是一个 Spread 操作符:
function foo(x,y,z) {
console.log(x,y,z);
let arr = [1,2,3];
foo(...arr); // 1 2 3
当被用于函数传参时,是一个 Rest 操作符:当被用于函数传参时,是一个 Rest 操作符:
function foo(...args) {
console.log(args);
foo( 1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]
对象和数组解构和Rest操作符
// 对象解构
const people = {
name: 'xiaoming',
age: 25
const {
name,
} = people; // 'xiaoming', 25
// rest参数,返回的是一个对象
const obj = {
a: 2,
b: 3,
c: 4,
const {
...rest
} = obj; // 2 { b: 3, c: 4, d: 5 }
console.log(rest);
// 数组解构
const arr = [1, 3, 4];
const [x, y, z] = arr; // 1, 3, 4
const [x2, ...rest2] = arr; // 1, [3,4]
console.log(rest2);
Promise
非常好用
Set
Set作为ES6新的数据解构(类数组),它的成员都是唯一的,因为最直接的使用场景便是去重、并、差、交集的使用。它使用的算法叫做“Same-value-zero equality”,类似精确运算的===,主要是NaN,这里它将两个视为相等。
// Set实例的常用方法和属性add,delete,clear,has、size
const s = new Set(['A', 'B', 'C']);
console.log(s); // Set { 'A', 'B', 'C' }
console.log(s.has('A')) // true,bool值
console.log(s.size) // 3
console.log(s.clear()) // Set {}
console.log(s.delete('A')) // true,bool值
同时Set实例配合常用遍历方法,实现并、交、差集。
const a =[1, 2, 3]
const b = [2, 3, 4];
// 并集
const s = Array.from(new Set([...a, ...b])); // [ 1, 2, 3, 4 ]
// 交集、差集
const bSet = new Set(b);
const interestSet = a.filter(v => bSet.has(v)); // [ 2, 3 ]
const interestSet = a.filter(v => !bSet.has(v)); // [ 1 ]
二进制和八进制字面量
ES6 支持二进制和八进制的字面量,通过在数字前面添加 0o 或者0O 即可将其转换为八进制值:
let oValue = 0o10;
console.log(oValue); // 8
let bValue = 0b10; // 二进制使用 `0b` 或者 `0B`
console.log(bValue); // 2
for…of 和 for…in
for in遍历的是数组的索引(即键名),而for of遍历的是数组元素值。
for…of:
let letters = ['a', 'b', 'c'];
for (let letter of letters) {
console.log(letter);
// 结果:a,b,c
for…in:
let letters = ['a', 'b', 'c'];
for (let letter in letters) {
console.log(letter);
// 结果:0,1,2
对象超类
ES6 允许在对象中使用 super 方法:
var parent = {
foo() {
console.log("Hello from the Parent");
var child = {
foo() {
super.foo();
console.log("Hello from the Child");
Object.setPrototypeOf(child, parent);
child.foo(); // Hello from the Parent
// Hello from the Child
类
ES6 中支持 class 语法,不过,ES6的class不是新的对象继承模型,它只是原型链的语法糖表现形式。
函数中使用 static 关键词定义构造函数的的方法和属性:
class Student {
constructor() {
console.log("I'm a student.");
study() {
console.log('study!');
static read() {
console.log("Reading Now.");
console.log(typeof Student); // function
let stu = new Student(); // "I'm a student."
stu.study(); // "study!"
stu.read(); // "Reading Now."
类中的继承和超集:
class Phone {
constructor() {
console.log("I'm a phone.");
class MI extends Phone {
constructor() {
super();
console.log("I'm a phone designed by xiaomi");
let mi8 = new MI();
extends 允许一个子类继承父类,需要注意的是,子类的constructor 函数中需要执行 super() 函数。 当然,你也可以在子类方法中调用父类的方法,如super.parentMethodName()。
有几点值得注意的是:
- 类的声明不会提升(hoisting),如果你要使用某个 Class,那你必须在使用之前定义它,否则会抛出一个 ReferenceError 的错误
- 在类中定义函数不需要使用 function 关键词
Generator
实际应用中大都用ES7的async/await来替代
yield
由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。
- 遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
- 下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
- 如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
- 如果该函数没有return语句或者执行完return之后再运行next的时候,则返回的对象的value属性值为undefined,done为true。
需要注意
- yield表达式只能用在 Generator 函数里面,用在其他地方都会报错。
- yield表达式如果用在另一个表达式之中,必须放在圆括号里面
- yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号。
-
yield放在表达式中的时候,
let s = (yield 1+2)
,s其值将会是undefined, 而1+2这个等于3的值将会作为next返回对象的value的值
next
- yield表达式本身没有返回值,或者说总是返回undefined。
- next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的。 V8 引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。
yield*
如果在 Generator 函数内部,调用另一个 Generator 函数,默认情况下是没有效果的。
这个就需要用到yield*表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数。
function* inner() {
yield 'hello!';
function* outer1() {
yield 'open';
yield inner();
yield 'close';
var gen = outer1()
gen.next().value // "open"
gen.next().value // 返回一个遍历器对象
gen.next().value // "close"
function* outer2() {
yield 'open'
yield* inner()
yield 'close'
var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"
示例1
不传值
function* test() {
var str1 = yield "hello";
var str2 = yield str1 + " world";
return str2;
var t = test();
console.log(t.next()); // {value: "hello", done: false}
console.log(t.next()); // {value: "undefined world", done: false}
console.log(t.next()); // {value: undefined, done: true}
传值
function* test() {
var str1 = yield "hello";
var str2 = yield str1 + " world";
return str2;
var t = test();
var t1 = t.next();
var t2 = t.next(t1.value);
var t3 = t.next(t2.value);
console.log(t1); // {value: "hello", done: false}
console.log(t2); // {value: "hello world", done: false}
console.log(t3); // {value: "hello world", done: true}
上面的两个例子可以发现 yield 修饰的表达式的返回值为 undefined ,所以我们一定要把之前的结果传进去。
示例2
function* test01(x) {
var y = yield x + 1;
return y;
var t1 = test01(1);
console.log(t1.next()) // {value: 2, done: false}
console.log(t1.next(3)) // {value: 3, done: true}
第一个 next 方法的 value 属性,返回表达式 x + 1 的值(2)。
第二个 next 方法带有参数3,这个参数可以传入 Generator 函数,作为上个阶段异步任务的返回结果,被函数体内的变量 y 接收。因此,这一步的 value 属性,返回的就是3(变量 y 的值)。
示例3
Generator 函数内部还可以部署错误处理代码,捕获函数体外抛出的错误。
function* test02(x) {
try {
var y = yield x + 2;
} catch (e) {
console.log(e);
return y;
var t2 = test02(1);
console.log(t2.next());
console.log(t2.throw("出错了"));
上面代码的最后一行,Generator 函数体外,使用指针对象的 throw 方法抛出的错误,可以被函数体内的
try ... catch
代码块捕获。
这意味着,出错的代码与处理错误的代码,实现了时间和空间上的分离,这对于异步编程无疑是很重要的。
示例4
下面看看如何使用 Generator 函数,执行一个真实的异步任务。
function loaddata() {
var request = new XMLHttpRequest();
request.open('GET', 'login.json', false);
request.send(null);
if (request.status === 200) {
return request.responseText;
function* mytest() {
let data = yield loaddata();
console.log(data);
var mtest = mytest();
let mn1 = mtest.next()
console.info(mn1);
let mn2 = mtest.next(mn1.value)
console.info(mn2);
输出的结果
{value: “{“code”: 0, “msg”: “success”}”, done: false} { “code”: 0, “msg”: “success”} {value: undefined, done: true}
这三行的输出分别对应上面的最后三行代码
上面代码中,Generator 函数封装了一个异步操作,该操作先读取一个远程接口,然后从 JSON 格式的数据解析信息。
就像前面说过的,这段代码非常像同步操作,除了加上了 yield 命令。
ES7
async/await
非常好用
原来的写法
update_click:function(){
this.$nextTick(function(){
// ...
}
新写法
async update_click:function(){
// ...
await this.$nextTick();
// ...
}
求幂运算符
Math.pow(3, 2) === 3 ** 2 // 9
Array.prototype.includes()
数组原型的方法,查找一个数值是否在数组中,只能判断一些简单类型的数据,对于复杂类型的数据无法判断。该方法接受两个参数,分别是查询的数据和初始的查询索引值。
[1, 2, 3].indexOf(3) > -1 // true
// 等同于:
[1, 2, 3].includes(3) // true
两者的优缺点和使用场景
- 简便性 includes方法略胜一筹,直接返回bool。indexOf需要返回数组下标,我们需要对下标值在进行操作,进而判断是否在数组中。
- 精确性 两者这都是通过===进行数据处理,但是对NaN数值的处理行为不同。includes对NaN的处理不会遵循严格模式去处理,所以返回true。indexOf会按照严格模式去处理,返回-1。 1, 2, NaN.includes(NaN) // true 1, 2, NaN.indexOf(NaN) // -1
- 使用场景 如果仅仅查找数据是否在数组中,建议使用includes,如果是查找数据的索引位置,建议使用indexOf更好一些