上一篇:了解ES5.1
ECMAScript概述
JS历史
-
JavaScript 诞生于 1995 年。NetScape发布NetScape Navigator2浏览器,提供了免费开发工具LiveScript,设计的主要目的是处理以前由服务器端语言负责的一些输入验证操作。在人们普遍使用电话拔号上网的年代,能够在客户端完成一些基本的验证任务绝对很不容易。因为Java流行,所以改名为JavaScript。这个就是JavaScript1.0版本。
-
因为JavaScript很受欢迎,Netspace Navigator3浏览器发布了JavaScript1.1版本。不久IE3也加入的脚本编程的功能,为了避免纠纷,命名为JScript。
-
1997年,ECMA以JavaScript1.1为基础制定了脚本语言标准:ECMA-262,并命名为ECMAScript。浏览器厂商使用ECMAScript作为各自的JavaScript实现的规范标准。
Java 是 Sun 公司的商标,根据授权协议,只有 Netscape 公司可以合法地使用 JavaScript 这个名字,且 JavaScript 本身也已经被 Netscape 公司注册为商标。二是想体现这门语言的制定者是 ECMA,不是 Netscape,这样有利于保证这门语言的开放性和中立性。
ECMAScript
1997年,ECMA发布262号标准文件(ECMA-262)第一版,规定了脚本语言的实现标准,并将这种标准命名为ECMAScript,这个就是ES1.0版本。ECMAScript是JavaScript语言的规范标准,JavaScript是ECMAScript的一种实现方式。在一些语境中是可以互换的。
ECMAScript版本
- 1998年6月, ECMAScriysxbzx pt2.0版发布
- 1999年12月, ECMAScript3.0版发布,并成为 JavaScript的通用标准,获得广泛支持
- 2007年10月, ECMAScript4.0版草案发布,对3.0版做了大幅升级。由于4.0版的目标过于激进各方对于是否通过这个标准产生了严重分歧,2008年7月,ECMA中止ECMAScript4.0的开发,将其中涉及现有功能改善的一小部分发布为ECMAScript3.1.不久, ECMAScript3.1改名为 ECMAScript5
- 2009年12月, ECMAScrip5.0版正式发布
- 2011年6月, ECMAScript 5.1版发布
- 2013年12月, ECMAScrip6版草案发布
- 2015年6月, ECMAScript6发布正式版本,并更名为 ECMAScript2015. Mozilla在这个标准的基础上推出了 JavaScript2.0
- 从此以后, JavaScript开始以年份命名,新版本将按照“ ECMAScript+年份”的形式发布。目前最新正式版本为 ECMAScript2019,于2019年7月正式发布
ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准
文档地址
https://es6.ruanyifeng.com/#docs/intro
关键字扩展(重要)
let和块级作用域
作用域分类
- 全局作用域
- 局部作用域
- 函数作用域
- 块级作用域 ==》 ES5中没有,ES6才开始有
什么是块级作用域?
-
由非函数{}包裹的变量有效区域,{}之内变量才可见, {}之外变量是不可见的
-
这个{}包括:一般的{}, if、while、for的{}
/*ES6之前没有块作用域*/ { var a1 = 2 // a1是全局变量 } console.log(a1) // 2 {}块外可见 if (true) { var a2 = 3 // a2是全局变量 } console.log(a2) // 3 if块外可见
ES5 没有块级作用域,这带来很多不合理的场景
-
if块中想内部使用的变量外部也可访问了
var flag = true if (fag) { var name = 'abc' // 只想内部使用 } console.log(name) // 外部也可访问了,
-
用来计数的循环变量泄露为全局变量。
//变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i。每一次循环,变量i的值都会发生改变 for (var i = 0; i < 5; i++) { setTimeout(function () { console.log(i);//5 5 5 5 5 }) }
let关键字
ES6 新增了let
命令,用来声明变量。它的用法类似于var
,但是所声明的变量,只在let
命令所在的代码块内有效,也就是增加了块级作用域。
-
使用块级作用域(let定义的变量属于块级作用域) 防止全局变量污染
/* 在{}块中使用let */ { let b1 = 20 // b1是块级局部变量 } // console.log(b1) // ReferenceError: b1 is not defined /* 在if块中使用let */ if (true) { let b2 = 30 // b2是块级局部变量 } console.log(b2) // ReferenceError: b2 is not defined /* 在while块中使用let */ var flag = true while (flag) { let b3 = 4 if (Date.now()%2===0) break } console.log(b3) // ReferenceError: b3 is not defined
-
块级作用域可以任意嵌套
//外层作用域无法读取内层作用域的变量 { { let a = 1; { console.log(a); //1 } } console.log(a);//Uncaught ReferenceError: a is not defined }
-
for循环的计数器,就很合适使用let命令
//计数器i只在for循环体内有效,在循环体外引用就会报错 /* 在for块中使用let 有2个特别之处: 1. 定义在()中的变量理解为在{}中定义的 2. 每循环一次产生一个新的块作用域 */ for (let i = 0; i < 5; i++) { setTimeout(() => { console.log(i) }) } // console.log(i) // Uncaught ReferenceError: i is not defined
-
练习
//练习1: var a = []; for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 练习2: var a = []; for (let i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6]();
let关键字特点
-
有块级作用域
-
没有变量(声明)提升
/* 细节: 在let声明语句之前: 已经存在了, 但还没有初始化为undefined, 不能访问 let声明语句之前的区域称为 "暂时性死区" 问题: let变量到底有没有变量提升? */ // console.log(a) // ReferenceError(引用错误): Cannot access 'a' before initialization let a = 2
-
不允许重名
let b = 3 let b = 4 // SyntaxError: Identifier 'b' has already been declared
-
全局变量不会成为window的属性
let c = 5 console.log(window.c) // undefined
const关键字
常量:不会变化的数据,有些时候有的数据是不允许修改的,所以需要定义常量。
-
const
声明的变量只读, 不能修改 ==》 常量 -
注意: 引用类型const常量所指向的对象内部可以修改
-
const常量必须声明时同时赋值
/* 只读,不能修改 ==》 const常量 */ const a = 3 // a = 4 // TypeError: Assignment to constant variable /* 注意: 引用类型const常量所指向的对象内部可以修改 */ const b = {m: 1} // b = {m: 2} // TypeError: Assignment to constant variable. b.m = 2 console.log(b.m) /* const常量必须声明时同时赋值 */ const c // SyntaxError: Missing initializer in const declaration c = 4
-
const
常量拥有let变量的所有特性- 有块级作用域
- 没有变量(声明)提升
- 不允许重名
- 全局变量不会成为window的属性
-
项目编码经验
- const常量名可以选择用大写
- 在定义变量时, 优先考虑用const, 如果确认需要修改时再改为let
块级作用域中定义函数 (了解)
函数声明一般常用的是两种,一种是function声明,一种是函数表达式。
//函数声明 及 两种声明的区别
f1()//可以使用
f2();//f2 is not a function
function f1() {}
var f2 = function () {
}
函数能不能在块级作用域之中声明?这是一个相当令人混淆的问题。
-
ES5 规定,函数只能在顶层作用域和函数作用域之中声明。
-
ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似于
let
,在块级作用域之外不可引用。 -
ES6 在附录 B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式
-
考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。
//尽量避免以下写法 fn1(); if (true) { function fn1() { alert(1); } } fn1(); //如果真的想在声明块级作用域函数,使用函数表达式 if (true) { let fn1 = function () { alert(1); } }
变量解构赋值(重要)
什么是变量的解构赋值
-
理解:一次性从对象或数组中提取值并赋值给n个变量
-
好处:简化编码
对象的解构赋值
-
可以解构对象自身或原型链上的属性/方法
-
注意: 变量名需要与属性名一致,不要求顺序
-
解构变量取别名: 一旦同名变量已存在, 需要取别名
-
解构不存在的属性得到undefined
-
解构变量可以指定默认值
-
支持嵌套解构
/* 解构对象 */ const obj = { name: 'tom', age: 12, foo: function () {}, bar: 13 } // 需求: 取出对象的部分或所有属性/方法, 以便后面分别使用 // 不使用解构 const n = obj.name const a = obj.age const f = obj.foo // 使用解构 /* 解构对象自身或原型链上的属性/方法 注意: 变量名需要与属性名一致 */ const {name, age, foo, toString} = obj console.log(name, age, foo, toString) /* 解构变量取别名: 一旦同名变量已存在, 需要取别名 */ let bar = 'abc' const {bar: b} = obj console.log(b) /* 解构不存在的属性得到undefined */ const {name2} = obj console.log(name2) /* 解构变量默认值 */ const {name3='abc'} = obj console.log(name3) /* 支持嵌套解构 */ const obj2 = { a1: 1, a2: { a3: 2, a4: 3 } } const {a1, a2: {a3, a4}} = obj2 console.log(a1, a3, a4)
数组的解构赋值
-
数组解构是按顺序取, 变量名不要求
-
解构失败得到undefined
-
解构变量可以指定默认值
-
支持嵌套解构
const arr = [1, 3, 5] /* 解构是按顺序取, 变量名不要求 */ const [a1, a2, a3] = arr console.log(a1, a2, a3) //1,3,5 /* - 解构失败得到undefined */ const [a4, a5] = [1] console.log(a5) // undefined /* - 解构变量可以指定默认值 */ const [a6, a7=1] = [2] console.log(a6, a7) // 2 1 /* - 支持嵌套解构 */ const arr2 = [1, [3, 5], {m: 7}] const [_, [a8, a9], {m}] = arr2 console.log(a8, a9, m) //3,5,7
解构赋值的应用
-
应用1:换2个变量
//应用1:交换两个变量 let m = 1 let n = 2; [m,n]=[n,m] // [m,n] = [2,1] console.log(m, n)
-
应用2:解构函数返回的数组或对象
//应用2:解构函数返回的数组或对象 function fn() { return [1, 2, 3, 4] } const [a, b, c, d] = fn() console.log(a, b, c, d) function fn2() { return { foo: "hello", hoo: "word" } } const { foo } = fn2() console.log(foo)
-
应用3:解构形参
//应用3:解构形参 function fn3([x,y,z]) { console.log(x, y, z) } fn3([1, 2, 3]) function fn4({ x, y, z }) { console.log(x, y, z) } fn4({ y: 2, x: 1, z: 3 })
字符串的扩展
模版字符串(重要)
-
ES6之前拼接多个字符串比较麻烦
const data = { message: { title: "今天天气真的很好", todo: "打台球", time: "时间2020.3.25" } } const wrapDiv = document.getElementById("wrap") /* 不使用模板字符串 */ const str = '<div class="box"><h2> ' + data.message.title + '</h2><p>' + data.message.todo + '</p><p>' + data.message.time + '</p></div> ' wrapDiv.innerHTML = str
-
模板字符串(template string)是增强版的字符串,用反引号(`)标识。可以嵌套变量,可以换行,可以包含单引号和双引号。
-
模板字符串中嵌入表达式,需要将表达式写在
${}
之中。/* 使用模板字符串 */ str = `<div class="box"> <h1>${data.message.title}</h1> <p>${data.message.todo}</p> <p>${data.message.time}</p> </div>`
-
模板字符串之中还能调用函数。
function fn(){ return "hello"; } let str = `${fn()} world`; console.log(str);//hello world
字符串新增的方法
-
去空格
-
trim():删除字符串两端的空白符
-
trimStart() 去除首部的空格
-
trimEnd() 去除尾部的空格
//字符串是基本类型值,方法只能返回新值,而不能改变字符串 const {log} = console let message = " hello world " log(message) log(message.trim()) // ? log(message.trimStart()) // ? log(message.trimEnd()) // ?
-
-
判断
-
startsWith();判断开头有没有包含某个字符串
-
endsWith();判断结尾有没有包含某个字符串
-
includes()判断字符串是否包含某个字符串
const {log} = console const message = "hello world" log(message) log(message.startsWith("hello"))// ? log(message.endsWith("world"))// ? log(message.endsWith("ld"))// ? log(message.includes("ld"))// ? log(message.includes("or"))// ? log(message.includes("你好啊"))// ?
-
-
repeat重复当前的字符串,可以规定次数
const message = "晶哥,你好s啊" log(message.repeat(10))
-
补充字符
-
padStart()当字符串不够某个长度的时候,在前边补充任意字符
-
padEnd(),//当字符串不够某个长度的时候,在后边补充任意字符
let message = "这个是密码" log(message.padStart(8, '#')) log(message.padEnd(8, '#')) log(message.padStart(8, '#*2222'))
-
数值的扩展(了解)
指数运算符
-
在Math中提供了 pow的方法 用来计算一个值的n次方
-
es2016中提出了新的方法求一个值的n次方 那就是 ** 操作符
/* 指数运算符 */ //求 一个数字的 n次方 log(Math.pow(3, 3)) log(Math.pow(30, 7)) //es2016 提出了新的方法求一个值的n次方 那就是 ** 操作符 log(3 ** 3) log(30 ** 10)
进制写法
-
二进制数字
-
八进制方法
/* 进制写法 */ const n1 = 0b00001111 // 二进制数 const n2 = 0o17 // 八进制数 log(n1, n2);//15 15
Math的新增方法
-
Math.trunc()方法会将数字的小数部分去掉,只保留整数部分
-
Math.sign() 判断一个数字的正数还是负数 还是0 或者是NaN
-
Math.sqrt()平方根
-
Math.cbrt()立方根
-
Math.hypot() 求所有参数平方和的平方根
const { log } = console /* Math.trunc()去除小数部分 */ log(Math.floor(1.33)) //1 log(Math.ceil(1.33)) //2 log(Math.trunc(1.33)) //1 log(Math.floor(-1.33)) //-2 log(Math.ceil(-1.33)) //-1 log(Math.trunc(-1.33)) //-1 /* Math.sign() 判断一个数字的正数还是负数 还是0 或者是NaN 如果说是正数 则返回1 负数返回-1 0 返回0 NaN返回NaN */ log(Math.sign(2.3)) //1 log(Math.sign(-1.3)) //-1 log(Math.sign(0)) //0 log(Math.sign(NaN)) //NaN log(Math.sign(Infinity)) //1 /* Math.sqrt()平方根 Math.cbrt()立方根 Math.hypot() 求所有参数平方和的平方根 */ log(Math.sqrt(9)) //3 log(Math.cbrt(27)) //3 log(Math.hypot(2, 3, 4)) //Math.sqrt(2*2 + 3*3 + 4*4) //勾股定理 a的2次方 + b的2次方 = c的2次方 // 已知直角三角形两个直角边的长度是 3 和 4 求斜边的长度 log(Math.hypot(3, 4)) //5
Number扩展
- Number.isFinite(i) : 判断是否是有限大的数
- Number.isNaN(i) : 判断是否是NaN
- Number.isInteger(i) : 判断是否是整数
- Number.parseInt(str) : 将字符串转换为对应的数值
函数的扩展 (重要)
箭头函数
什么是箭头函数
ES6 允许使用“箭头”(=>
)定义匿名函数
箭头函数的写法
箭头函数分为以下几种情况
-
只有一个参数,可以忽略小括号 .没有参数或者多个参数的时候,参数的括号不能省略
-
函数体是一句话的时候, 可以省略 {}, 此时 =>还有return返回的作用
-
当函数体不是一句话的时候, 必须写{}
// 函数表达式写法 const fn = function (a) { return a + 1 } // 箭头函数--完整写法 const fn2 = (a) => { return a + 1 } // 箭头函数--简洁写法 /* 用箭头代替了function 省略了 () 和 {} 省略了 return */ const fn3 = a => a + 1 console.log(fn(2), fn2(2), fn3(2)) // 没有形参的写法 const fn4 = () => { console.log('fn4()') } // 有多个形参的写法 const fn5 = (a, b, c) => { const result = a + b + c return result }
箭头函数用于回调函数
/* 箭头函数用于回调函数 */
setTimeout(() => {
console.log('abc')
}, 1000)
[1, 3, 5].forEach(item => {
console.log(item)
})
区别箭头函数与一般函数
-
关于this 箭头函数内部没有this,在箭头函数中读取this会沿着作用域链去外部查找this
/* 关于this 箭头函数内部没有this,在箭头函数中读取this会沿着作用域链去外部查找this */ document.getElementById('btn1').onclick = function () { console.log(this) } document.getElementById('btn2').onclick = () => { console.log(this); } document.getElementById('btn3').onclick = function () { console.log(this) const f = () => { console.log(this) } f() } document.getElementById('btn4').onclick = () => { console.log(this) const f = () => { console.log(this) } f() }
-
箭头函数不能作为构造函数 new 调用创建实例对象
/* 箭头函数不能作为构造函数 new 调用创建实例对象 */ const F2 = () => { } new F2() //TypeError: F2 is not a constructor
-
箭头函数没有arguments对象
/* 箭头函数没有arguments对象 */ const f3 = () => { console.log(arguments) // ReferenceError: arguments is not defined } f3(1, 2, 3)
-
箭头函数不能使用yield命令,意味着不能当作gennerator函数(后边会讲)
形参默认值
ES6 默认参数
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
function fn2(a, b = "world") {
console.log(a, b);
}
fn2("hello", "");
fn2("hello");
ES5中实现参数默认值效果
ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法。
function fn2 (a, b) {
b = b || 'Atguigu'
console.log(a, b)
}
fn2('Hello', 'China')
fn2('Hello')
rest参数(剩余参数)
-
ES6 引入 rest 参数(形式为
...变量名
),用于获取函数的多余参数,这样就不需要使用arguments
对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。 -
rest参数只能是最后一个参数
function fn (a, ...args) { console.log(a, args) } fn(1, 2, 3, 4) function fn2(...args) { console.log(args) } fn2(1, 2, 3, 4) function fn3(...args, a) { // SyntaxError: Rest parameter must be last formal parameter }
数组扩展
扩展运算符 (重要)
什么是扩展运算符
扩展运算符(spread)是三个点(...
),可以将数组或对象中的数据拆解出来分别使用
const arr = [1, 3, 5]
const arr2 = ['a', 'b', 'c']
function fn (x, y, z) {
console.log(x, y, z)
}
// fn(arr)
fn(...arr)
// console.log(arr)
console.log(...arr)
其他应用
-
复制/浅拷贝数组
-
合并数组
-
字符串转换为数组
/* - 复制/浅拷贝数组 */ const arr = [1, 3, 5] const arr2 = [...arr] console.log(arr2, arr) /* - 合并数组 */ const arr3 = [6, 8] const arr4 = [...arr, ...arr3] console.log(arr4) // - 字符串转换为数组 const arr5 = [...'abc'] console.log(arr5)
数组的新方法
Array的静态方法 (次重要)
-
Array.from() 把伪数组转换成数组(可以使用数组的方法)
-
Array.of() 将多个数据封装成数组
/* Array.from 把伪数组转换成数组(可以使用数组的方法) */ const lis = document.getElementsByTagName('li') // lis是伪数组 console.log(Array.isArray(lis)) // false,说明不能通过forEach遍历元素 const lis2 = Array.from(lis) // lis2是真数组 lis2.forEach(li => li.style.color = 'red') // 可以通过forEach遍历 /* Array.of() 将多个数据封装成数组 */ const arr6 = Array.of(1, 'abc', true) console.log(arr6)
Array的实例方法
find() 和 findIndex() (重要)
-
find(): 查找并返回第一个满足条件的, 如果一个都没有返回undefined
-
findIndex(): 查找并返回第一个满足条件的元素下标, 如果一个都没有返回-1
const arr = [5, 3, 4, 2, 6] /* 找出小于4的元素 */ console.log(arr.find(item => item<4)) /* 找出小于4的元素的下标 */ console.log(arr.findIndex(item => item<4))
includes() (重要)
-
includes()方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的
includes
方法类似const arr = [1, 2, 3, ["a"]] console.log(arr.includes(2)) //true console.log(arr.includes(["a"])) //false
flat() (次重要)
-
将嵌套的数组“拉平”,变成更低维的数组
-
默认只拉低一维, 也可以指定多维,或者直接拉为一维数组
const arr = [1, [2, [3, [4, 5]]]] console.log(arr.flat()) // [1, 2, [3, [4, 5]]] console.log(arr.flat(2)) // [1, 2, 3, [4, 5]] console.log(arr.flat(Infinity)) // [1, 2, 3, 4, 5]
注意: 还有一些其它的方法,万一需要查文档
对象的扩展(重要)
对象的属性与方法简写
ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
/* 对象的属性与方法简写 */
const name = 'tom'
// 不简写
const p = {
name: name,
setName: function (name) {
this.name = name
}
}
// 简写
const p2 = {
name, // 属性名与变量名同名,中需指定属性名
setName (name) { // 方法省略 `:function`
this.name = name
}
}
p2.setName('Jack')
console.log(p2.name)
属性名表达式
JavaScript 定义对象的属性,有两种方法:点运算符和中括号运算符
但是,如果使用字面量方式定义对象(使用大括号),在 ES5 中只能使用标识符,而不能使用变量定义属性。
也就是说在ES5中 key/value key是固定不变的,在ES6中,支持属性表达式,支持key发生变化
/* 属性名表达式 */
//在ES5中 key/value key是固定不变的
const obj1 = {
name: "A",
sex: "女"
}
// 在ES6中,支持属性名是可变的
const a = 'name'
const b = 'sex'
const c = 'getInfo'
const obj2 = {
[a]: 'B',
[b.toUpperCase()]: '男',
[c] () {
return this[a] + '-' + this[b.toUpperCase()]
}
}
console.log(obj2[c]())
对象的扩展运算符
ES2018 将这个运算符引入了对象。
const obj1 = {a: 1, b: 2}
const obj2 = {c: 2}
const obj3 = {...obj1}
const obj4 = {...obj1, ...obj2}
const {a, ...obj5} = obj4
console.log(obj3) // {a: 1, b: 2}
console.log(obj4) // {a: 1, b: 2, c: 2}
console.log(obj5) // {b: 2, c: 3}
对象新增的方法(了解)
-
Object.is(v1, v2): 判断2个值是否相等 相当于===, 但修复NaN不等自己的问题
-
Object.assign(obj1, …objs): 将任意多个对象合并到obj1中, 并返回obj1
console.log(Object.is(1, 1)) console.log(Object.is({}, {})) console.log(NaN===NaN, Object.is(NaN, NaN)) const obj1 = { a: 1 }; const obj2 = { b: 2 }; const obj3 = { c: 3 }; const result = Object.assign(obj1, obj2, obj3); console.log(obj1, result===obj1)
下一篇:ES6+学习(二)