es6阮一峰老师文档:es6.ruanyifeng.com/
ES6 新特性
1.let变量声明以及声明特性
<script>
// 声明变量
let a;
let b,c,d;
let e = 100, g = 'love', f = [];
// 1.变量不能重复声明--报错
let str = '小小'
let str = '亮亮'
// 2.块级作用域(全局,函数,eval)
// 在 if else while for循环中都是局部有效
{
let girl = '杨燕'
}
console.log(girl) // 报错
// 3.不存在变量提升
consol.log(song) // 报错
let song = '恋爱达人' // 如果是 var 定义,输出结果是 undefined,不报错,所以var存在变量提升
// 4.不影响作用域链
{
let school = '尚硅谷'
function fn(){
console.log(school)
}
fn() // 在fn中找不到school会往上找
}
</script>
//5.暂时性死区
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。
总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”。
“暂时性死区”也意味着typeof不再是一个百分之百安全的操作。
typeof x; // ReferenceError
let x;
作为比较,如果一个变量根本没有被声明,使用typeof反而不会报错。
typeof undeclared_variable // "undefined"
上面代码中,undeclared_variable是一个不存在的变量名,结果返回“undefined”。所以,在没有let之前,typeof运算符是百分之百安全的,永远不会报错。现在这一点不成立了。
2. const 声明变量以及特点
const 声明常量,常量就是值(内存地址)不能变化的量。
<script>
const SCHOOL = '尚硅谷'
// 1.一定要赋初始值
const A; // 报错
// 2.一般常量使用大写(潜规则)
const A = 10
// 3.常量的值不能修改
SCHOOL = 'atguigu' //报错
// 4.块级作用域,只在声明所在的块级作用域内有效。
// 5.对于数组和对象的元素修改,不算是对常量的修改,不会报错
const ARR = ['tom','daming','tony']
ARR.push('liming')
// 6.const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
</script>
3.变量的解构赋值
es6 允许按照一定模式从数组和对象中提取值,对变量进行赋值,这被称为解构赋值。
<script>
// 1.数组的结构
const F4 = ['小沈阳','刘能','赵四','宋小宝']
let [one,two,three,four] = F4
console.log(one)
console.log(two)
console.log(three)
console.log(four)
// 2.对象的解构
const zhao = {
name:'赵四',
age:60,
xiaopin:function(){
console.log('我可以演小品')
}
}
let {name,age,xiaopin} = zhao
console.log(name) // 赵四
console.log(age) // 60
console.log(xiaopin)
xiaopin() // 我可以演小品
let {name:myName, age:Myage} = zhao // myName myage属于别名
console.log(myName)
// 也可以单独解构一个变量
const {name} = zhao
</script>
如果解构不成功,变量的值就等于undefined。
4.模板字符串
es6引入新的声明字符串的方式 ``
<script>
// 1.声明
let str = `I am a str`
console.log(str,typeof str) //I am a str string
// 2.内容中可以直接出现换行
let str = `<ul>
<li>沈腾</li>
<li>玛丽</li>
<li>魏翔</li>
<li>艾伦</li>
</ul>`;
// 3.变量拼接
let lovest = '赵丽颖'
let out = `${lovest}是最棒的` // 模板字符串可以解析变量
</script>
模板字符串中嵌入变量,需要将变量名写在${}之中。
5.简化对象写法
es6允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。
<script>
let name = 'atguigu'
let change = function(){
console.log('改变自己')
}
cosnt school = {
name, // 这里其实是 name:name,简写了
change,
// improve:function(){
// console.log('提高')
//}
// 方法简写
improve(){
console.log('提高')
}
}
</script>
6.箭头函数以及声明特点
ES6 允许使用 箭头 (=>) 定义函数
<script>
//声明一个函数
// let fn = function(){
// }
let fn = (a,b) => {
return a + b;
}
//调用函数
let result = fn(1, 2);
console.log(result);
// 1.this是静态的,this始终指向函数声明时所在作用于下的 this 的值
function getName(){
console.log(this.name)
}
let getName2 = () => {
console.log(this.name);
}
//设置 window 对象的 name 属性
window.name = '尚硅谷'
const school = {
name:"ATGUIGU"
}
//直接调用,结果都是尚硅谷
// 因为这两个函数都定义在全局作用域下
// getName();
// getName2();
// call方法调用,不能改变箭头函数的this指向
getName.call(school) // ATGUIGU
getName2.call(school) // 尚硅谷
// 2. 不能作为构造实例化对象
let Person = (name, age) => {
this.name = name;
this.age = age
}
let me = new Person('xiao',30)
console.log(me) // 报错
// 3.不能使用 arguments 变量
let fun = () => {
console.log(arguments)
}
fn(1,2,3) // 报错
// 4.箭头函数的简写
// (1) 省略小括号,当形参有且只有一个的时候
// (2) 省略花括号,当代码只有一条语句的时候,此时 return 必须省略
// 而且语句的执行结果就是函数的返回值
let result = n => n * n
console.log(pow(8))
</script>
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
6.1箭头函数使用注意点
(1)箭头函数没有自己的this对象(详见下文)。
(2)不可以当作构造函数,也就是说,不可以对箭头函数使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
上面四点中,最重要的是第一点。对于普通函数来说,内部的this指向函数运行时所在的对象,但是这一点对箭头函数不成立。它没有自己的this对象,内部的this就是定义时上层作用域中的this。也就是说,箭头函数内部的this指向是固定的,相比之下,普通函数的this指向是可变的。
举例说明:
var name = '南玖'
var person = {
name: 'nanjiu',
say: function() {
console.log('say:',this.name)
},
say2: () => {
console.log('say2:',this.name)
}
}
person.say() // say: nanjiu
person.say2() // say2: 南玖
这里第一个say定义的是一个普通函数,并且它是作为对象person的方法来进行调用的,所以它的this指向的就是person,所以它应该会输出say: nanjiu
而第二个say2定义的是一个箭头函数,我们知道箭头函数本身没有this,它的this永远指向它定义时所在的上层作用域,所以say2的this应该指向的是全局window,所以它会输出say2: 南玖
我们也可以通过Babel 转箭头函数产生的 ES5 代码来证明箭头函数没有自己的this,而是引用的上层作用域中this。
// ES6
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
// ES5
function foo() {
var _this = this;
setTimeout(function () {
console.log('id:', _this.id);
}, 100);
}
作者:前端南玖
链接:juejin.cn/post/706994…
6.2箭头函数实践
setTimeout是window的方法。setTimeout函数调用的代码运行在与所在函数完全分离的执行环境上,这会使得this指向的是window对象。
如果想要让setTimeout中的this不指向window,常用方法如下:
方法一:使用之前重新指向到一个变量,然后操作这个新的变量
方法二:使用ES6的箭头函数
最后一句话写错了!!应该是:这个this指向定义时所在的上层作用域,也就是function。
案例2
总结:
- 箭头函数适合与 this 无关的回调,比如定时器,数组的方法回调
- 箭头函数不适合 与 this 有关的回调,比如事件回调,对象的方法
7.函数参数的默认值设置
es6允许给函数参数赋值初始值。
<script>
// 1. 形参初始值,具有默认值的参数,一般位置要靠后(潜规则)
function add(a,b,c=10){
return a + b + c
}
let res = add(1,2)
console.log(res) // 13
// 2.与解构赋值结合
// 如果传了实参的值就用实参的值,如果没有传就用默认形参的值
// 这里解构赋值,不用考虑顺序
function connect({host="127.0.0", username, password, port}){
console.log(host,username,password,port)
}
connect({
host:'localhost',
username:'root',
passwordL:'123',
port:'8080'
})
</script>
8.rest 参数(剩余参数)
es6引入 rest 参数,用于获取函数的实参,用来代替 arguments
<script>
// es5 获取实参的方式
function date(){
console.log(arguments)
}
date('白纸','墨水')
// rest 参数
function date2(...args){
console.log(args) //结果是数组
// 所以可以使用filter some every map等方法
}
date2('白纸','墨水')
// 而且 rest参数必须放到参数最后
function fn(a, b, ...args){
console.log(a) // 1
console.log(b) // 2
console.log(args) // [3,4,5,6]
}
fn(1,2,3,4,5,6)
</script>
9.扩展运算符之三个点 ...
扩展运算符(spread)是三个点(
...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
<script>
// 声明一个数组
cosnt tfboys = ['王源','王俊凯','易烊千玺']
// 声明一个函数
function chunwan(){
console.log(arguments)
}
chunwan(tfboys) // ['王源','王俊凯','易烊千玺']
chunwan(...tfboys) // 0:"王源" 1:"王俊凯" 2:"易烊千玺"
// 等价于 chunwan('王源','王俊凯','易烊千玺')
</script>
扩展运算符应用
9.1 数组的合并
let kuaizi = ['晓阳','旺旺']
let fenghuang = ['曾毅','玲花']
let kf = [...kuaizi,...fenghuang]
console.log(kf) // ['晓阳','旺旺','曾毅','玲花']
// 浅拷贝,使用的时候需要注意
// 方法二
kuaizi.push(...fenghuang)
举例--合并数组:
const a1 = [{ foo: 1 }];
const a2 = [{ bar: 2 }];
// ES5 的合并数组
const a3 = a1.concat(a2);
// ES6 的合并数组
const a4 = [...a1, ...a2];
console.log(a3[0] === a1[0] ) // true
console.log(a4[0] === a1[0] ) // true
a3[0].bar = 3
console.log(a3[0]) //{ foo: 1, bar: 3 }
console.log(a1[0]) //{ foo: 1, bar: 3 }
上面代码中,a3和a4是用两种不同方法合并而成的新数组,但是它们的成员都是对原数组成员的引用,这就是浅拷贝。如果修改了引用指向的值,会同步反映到新数组。
补充:如果数组里面有引用类型的话,扩展运算符也只是浅拷贝。
浅拷贝与深拷贝的区别:www.cnblogs.com/yy1234/p/73…
另一篇文章:浅拷贝与深拷贝的区别
9.2 数组的克隆
const str = ['E','F','G']
const sstr = [...str] //等价于 ['E','F','G']
const [...sstr] = str;
console.log(sstr) // ['E','F','G']
console.log(str === sstr) // false
上面的两种写法,sstr 都是 str 的克隆,再修改 sstr 不会对 str 产生影响。克隆是完全创造出一个新的对象,有自己的新的内存地址,只不过初始信息是和原对象是一样的。
9.3 将伪数组转成真正的数组
const divs = document.querySelectorAll('div') // NodeList(3)[div,div,div] 原型是Object
const divArr = [...divs]
console.log(divArr) // [div,div,div]
10. Symbol的介绍与创建
es6引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JS语言的第七种数据类型,是一种类似于字符串的数据类型。
10.1 symbol的创建
Symbol特点
- Symbol 的值是唯一的,用来解决命名冲突的问题
- Symbol 值不能与其他数据进行运算
- Symbol 定义的对象属性不能使用 for…in 循环遍历,但是可以使用 Reflect.ownKeys 来获取对象的所有键名
<script>
// 创建 Symbol
let s = Symbol()
console.log(s, typeof s) // Symbol() "symbol"
let s2 = Symbol('尚硅谷')
let s3 = Symbol('尚硅谷')
console.log(s2 === s3) // false
// 第二种创建方法 Symbol.for
let s4 = Symbol.for('尚硅谷')
let s5 = Symbol.for('尚硅谷')
console.log(s4, typeof s4) // Symbol(尚硅谷) "symbol"
console.log(s4 === s5) // true
// 重新使用同一个 Symbol 值,Symbol.for()方法可以做到这一点
// Symbol.for()与Symbol()这两种写法,都会生成新的 Symbol。
// 它们的区别是,前者会被登记在全局环境中供搜索,后者不会。
// Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。
// 不能与其他数据进行运算
// let res = s + 100 // 报错
// let res = s > 100 // 报错
</script>
Symbol()函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。主要是为了在控制台显示,或者转为字符串时,比较容易区分。
javascript 七种数据类型口诀:USONB(you are so niubility)
- u——undefined
- s——string、symbol
- o——object
- n——null、number
- b——boolean
10.2 对象添加Symbol类型的属性
给对象添加方法方式一: symbol保证了up和down的属性名是独一无二的,所以添加进去也不怕有属性名冲突。
给对象添加方法方式二:
10.3 Symbol的内置属性
除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。
Symbol.hasInstance:当其他对象使用 instanceof 运算符,判断是否为该对象的实例时,会调用这个方法。
Symbol.isConcatSpreadable:对象的 Symbol.isConcatSpreadable 属性等于的是一个布尔值,表示该对象用于Array.prototype.concat()时,是否可以展开。
11.迭代器介绍
11.1 Iterator(遍历器)概念
JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6又添加了Map和Set。这样就有了四种数据集合,还可以组合使用它们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。
工作原理:
- 先创建一个指针对象,指向当前数据结构的起始位置
- 第一次调用对象的next方法,指针自动指向数据结构的第一个成员
- 接下来不断调用next方法,指针一直往后移动,直到指向最后一个成员
- 每次调用next方法就会返回一个包含value和done属性的对象(
value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。)
11.2默认 Iterator 接口
Iterator 接口的目的,就是为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
原生具备 Iterator 接口的数据结构如下。
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
变量xiyou是一个数组,原生就具有遍历器接口,部署在xiyou的Symbol.iterator属性上面。所以,调用这个属性,就得到遍历器对象。
注:需要自定义遍历数据的时候,要想到迭代器。
对于原生部署 Iterator 接口的数据结构,不用自己写遍历器生成函数,for...of循环会自动遍历它们。除此之外,其他数据结构(主要是对象)的 Iterator 接口,都需要自己在Symbol.iterator属性上面部署,这样才会被for...of循环遍历。
11.3迭代器自定义遍历对象
//声明一个对象
const data={
name:'zzl',
lis:[
'wxl',
'll',
'hll'
],
//自己给某些结构加上iterator接口
[Symbol.iterator](){
//索引变量
let index=0;
let _this=this;
return {//返回一个指针对象,即创建一个指针对象
next:function(){ //创建对象的next方法
// 返回一个包含value和done属性(是否完成)的对象
if(index < _this.lis.length){
const result = { value:_this.lis[index], done:false};
index ++;
return result;
}else{
return { value:undefined, done:true};
}
}
};
}
}
//自定义遍历这个对象
// 如果不用迭代器自定义遍历对象的话,下面方法会报错,因为data是Object对象,不能遍历
for(let v of data){
console.log(v);
}
12.生成器函数声明与调用
生成器就是一个特殊的函数,是异步编程新的解决方案。分别通过以下代码可知运行逻辑。
<script>
function * gen(){
console.log('hello generator')
}
let iterator = gen()
iterator.next() // hello generator
</script>
12.1生成器函数的参数传递
next方法可以传入实参。
第二次调用next的实参将作为第一个yield的整体返回结果。
12.2生成器函数实例1
// 1s 后输出111 2s后输出222 3s后输出333
// 回调地狱
// setTimeout(() => {
// console.log(111);
// setTimeout(() => {
// console.log(222);
// setTimeout(() => {
// console.log(333);
// }, 3000);
// }, 2000);
// }, 1000);
function one(){
setTimeout(()=>{
console.log(111);
a.next();//定时器运行完调用下一个,实现了异步编程
},1000)
}
function two(){
setTimeout(()=>{
console.log(222);
a.next();
},2000)
}
function three(){
setTimeout(()=>{
console.log(333);
a.next();
},3000)
}
function * fun(){
yield one();
yield two();
yield three();
}
//调用生成器函数
let a=fun();
a.next();
12.3生成器函数实例2
function one(){
setTimeout(()=>{
let data='用户数据';
a.next(data);//第二次调用next的实参将作为第一个yield的整体返回结果
},1000)
}
function two(){
setTimeout(()=>{
let data='订单数据';
a.next(data);
},1000)
}
function three(){
setTimeout(()=>{
let data='商品数据';
a.next(data);
},1000)
}
function *fun(){
let users= yield one(); //用户数据 作为第一个yield的整体返回结果
console.log(users);
let orders= yield two();
console.log(orders);
let goods= yield three();
console.log(goods);
}
//调用生成器函数
let a=fun();
a.next();
13.Promise
Promise是es6引入的异步编程的新解决方案。语法上,Promise是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果。
- Promise.prototype.then 方法
then方法是定义在原型对象
Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即
then方法后面再调用另一个then方法。
- Promise.prototype.catch 方法
它是
.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
13.1 promise基本语法
const p = new Promise(function(resolve, reject){
setTimeout(function(){
// let data = '成功'
// resolve(data)
let err = '失败'
reject(err)
},2000)
})
//调用 promise 对象的 then方法
p.then(function(value){
console.log(value) //promise状态变为成功后then会调用第一个回调函数
},function(reason){
console.log(reason) //promise状态变为失败后then会调用第二个回调函数
})
13.2 Promise 读取文件
// 1.引入 fs 模块
const fs = require('fs')
// 2.调用方法读取文件
fs.readFile('./res.md',(err,data)=>{
// 如果失败,则抛出错误
if (err) throw err
// 如果没有出错,则输出内容
console.log(data.toString())
})
// 3.使用 Promise 封装
const p = new Promise(function(resolve, reject){
fs.readFile('./res.md',(err,data)=>{
if (err) {
reject(err)
}
resolve(data)
})
})
//调用 promise 对象的 then方法
p.then(function(value){
console.log(value.toString())
},function(reason){
console.log('读取失败')
})
13.3 Promise 封装 Ajax请求
const p = new Promise(function(resolve,reject){
// 1.创建对象
const xhr = new XMLHttpRequest();
// 2.初始化
xhr.open('GET','https://apiopen.top/getJoke')
// 3.发送
xhr.send()
// 4.绑定事件
xhr.onreadystatechange = function(){
// 判断
if(xhr.readyState === 4){
// 判断响应状态码 200 - 299
if(xhr.status >= 200 && xhr.status < 300){
//表示成功
resolve(xhr.response)
}else{
//表示失败
reject(xhr.status)
}
}
}
})
p.then(function(value){
console.log(value)
},function(reason){
console.log(reason)
})
14. Set 集合 与 API
Es6 提供了新的数据结构 set(集合),它类似于数组,但成员的值都是唯一的,集合实现了iterator 接口,所以可以使用 扩展运算符 和 for…of… 进行遍历,集合的属性和方法:
(1)size 返回集合的元素个数
(2)add 增加一个新元素,返回当前集合
(3)delete 删除元素,返回 boolean 值
(4)has 检测集合中是否包含某个元素,返回 boolean
<script>
let s = new Set()
let s2 = new Set(['大事儿','小事儿','喜事儿','小事儿'])
// 元素个数
console.log(s2.size) // Set(3)
// 添加新元素
s2.add('好事儿')
// 删除元素
s2.delete('大事儿')
// 检测元素
console.log(s2.has('喜事儿'))
// 清空集合
s2.clear()
for(let v of s2){
console.log(v)
}
// set解构的实例与数组一样,也有forEach方法
// 用于对每个成员执行某些操作,没有返回值
s2.forEach(item => console.log(item))
</script>
集合实践
14.1 数组去重
<script>
let arr1 = [1, 2, 2, 3, 3, 3, 4, 1, 2];
let res1 = [...new Set(arr1)];
console.log(res1); // [ 1, 2, 3, 4 ]
</script>
14.2交集
<script>
let arr1 = [4,5,6,5,6];
let arr2 = [1,2,3,4,6];
// 将arr2转成数组,用filter方法
let result = [...new Set(arr1)].filter( item => {
// s2还是set类型数据,用has方法做判断
let s2 = new Set(arr2)
if(s2.has(item)){
return true
}else{
return false
}
})
// 简化写法
let result = [...new Set(arr1)].filter( v => new Set(arr2).has(v))
console.log(result)
</script>
14.3并集
<script>
let arr1 = [4,5,6,5,6];
let arr2 = [1,2,3,4];
let arr3 = [...new Set([...arr1, ...arr2])]
console.log(arr3) // [4,5,6,1,2,3]
</script>
4.差集
<script>
let arr1 = [4,5,6,5,6];
let arr2 = [1,2,3,4,6];
// 主体不同结果不同
let result = [...new Set(arr1)].filter( v => !(new Set(arr2).has(v)))
console.log(result) // [5]
let result2 = [...new Set(arr2)].filter( v => !(new Set(arr1).has(v)))
console.log(result2) // [1,2,3]
</script>
15. Map的介绍与API
ES6提供了Map数据结构。它类似于对象,也是键值对的集合。但是‘键’的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map也实现了 iterator 接口,所以可以使用 扩展运算符 和 for…of… 进行遍历。Map的属性和方法:
(1)size 返回Map的元素个数
(2)set 增加一个新元素,返回当前Map
(3)get 返回键名对象的键值
(4)has 检测Map中是否包含某个元素,返回 boolean值
(5)clear 清空集合,返回 undefined
<script>
let m = new Map()
// 添加元素
m.set('name','尚硅谷')
m.set('change',function(){
console.log('改变世界')
})
let key = {
school:'atguigu'
}
m.set(key,['北京','上海','广州'])
console.log(m)
</script>
for(let v of m){
console.log(v)
}
16. class类
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。
基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
function phone(brand, price){
this.brand = brand
this.price = price
}
phone.prototype.call = function(){
conosle.log('打电话')
}
let p = new phone('华为',5999)
console.log(p)
等价于:
class phone{
constructor(brand, price){
this.brand = brand
this.price = price
}
// 方法必须使用该语法,不能使用 es5 的对象完整形式
call(){
conosle.log('打电话')
}
}
let p = new phone('华为',5999)
console.log(p)
16.1 class静态成员
等价于:
16.2 ES5 构造函数继承
function phone(brand, price){
this.brand = brand
this.price = price
}
phone.prototype.call = function(){
conosle.log('打电话')
}
// 只能手机
function SmartPhone(brand, price, color, size){
phone.call(this, brand, price) //改变this指向,此时this指向SmartPhone
this.color = color
this.size = size
}
// 设置子级构造函数的原型
SmartPhone.prototype = new phone()
SmartPhone.prototype.constructor = SmartPhone //可写可不写
// 声明子类的方法
SmartPhone.prototype.photo = function(){
console.log('拍照')
}
const s = new SmartPhone('小米',3999,'紫色','128G')
console.log(s)
16.3 ES6 class的类继承
class phone{
// 构造方法
constructor( brand, price){
this.brand = brand
this.price = price
}
// 父类的成员属性
call(){
conosle.log('打电话')
}
}
class SmartPhone extends phone{
// 构造方法
constructor( brand, price, color, size){
super( brand, price) //等价于phone.call(this, brand, price)
this.color = color
this.size = size
}
photo(){
console.log('拍照')
}
// ES6 子类对父类方法的重写(完全重写)
call(){
console.log('我可以进行视频通话')
}
}
const s = new SmartPhone('小米',3999,'紫色','128G')
console.log(s)
s.call()
16.4 ES6 class中的getter和setter方法
17. ES6的数值扩展
<script>
// Number.EPSILON 是js表示的最小精度
function equal(a, b){
if(Math.abs(a - b) < Number.EPSILON){
return true
}else{
return false
}
}
console.log( 0.1 + 0.2 === 0.3) // false
console.log( equal(0.1 + 0.2, 0.3)) // true
// 1. 二进制 与 八进制
let b = 0b1010 // 10
let o = 0o7777 // 511
let d = 100
let x = 0xff // 255
// 2. NuMBER.isFinite 检测一个数值是否为有限数
console.log(Number.isFinite(100)) // true
console.log(Number.isFinite(100 / 0)) // false
// 3. Number.isNaN 检测一个数值是否为NaN
console.log(Number.isNaN(123)) // false
// 4. Number.parseInt Number.parseFloat 字符串转整数
console.log(Number.parseInt('12.88256756876')) // 12
console.log(Number.parseFloat('12.8825675aaa')) // 12.8825675
// 5. Number.isInteger 判断一个数是否为整数
// 6. Math.trunc 将数字的小数部分抹掉
Math.trunc('123.456') // 123
Math.trunc(true) //1
Math.trunc(false) // 0
Math.trunc(null) // 0
// 7. Math.sign 判断一个数到底为正数 负数 还是 0
Math.sign(-5) // -1
Math.sign(5) // +1
Math.sign(0) // +0
Math.sign(-0) // -0
Math.sign(NaN) // NaN
</script>
18. ES6的对象方法扩展
18.1 Object.is()
ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
ES6 提出同值相等算法,用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
Object.is('foo', 'foo') // true
Object.is({}, {}) // false
不同之处只有两个:一是+0不等于-0,二是NaN等于自身。
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
18.2 Object.assign()
Object.assign()方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
console.log(target) // {a:1, b:2, c:3}
Object.assign()方法的第一个参数是目标对象,后面的参数都是源对象。
注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
注意点
(1)浅拷贝
Object.assign()方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
const obj1 = {a: {b: 1}, c:1, d:true};
const obj2 = Object.assign({}, obj1);
obj1.a.b = 2;
console.log(obj2.a.b) // 2
console.log(obj2.c) // 1
console.log(obj2.d) // true
上面代码中,源对象obj1的a属性的值是一个对象,Object.assign()拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。
(2)同名属性的替换
对于这种嵌套的对象,一旦遇到同名属性,Object.assign()的处理方法是替换,而不是添加。
const target = { a: { b: 'c', d: 'e' } }
const source = { a: { b: 'hello' } }
console.log(Object.assign(target, source))// { a: { b: 'hello' } }
一些函数库提供Object.assign()的定制版本(比如 Lodash 的_.defaultsDeep()方法),可以得到深拷贝的合并。
18.3 Object.setPrototypeOf()
Object.setPrototypeOf方法的作用与__proto__相同,用来设置一个对象的原型对象(prototype),返回参数对象本身。
const obj = { a:10 }
const cities = {
xiaoqu:['北京','上海']
}
Object.setPrototypeOf( obj, cities)
console.log(Object.getPrototypeOf(obj))
console.log(obj)
19. 模块化
19.1模块化的好处
- 防止命名冲突
- 代码复用
- 高维护性
19.2模块化规范产品
ES6 之前的模块化规范有:
- CommonJS => NodeJS、Browserify
- AMD => requireJS
- CMD => seaJS
19.3ES6 模块化语法
模块功能主要由两个命令构成:export 和 import。
export命令用于规定模块的对外接口import命令用于输入其他模块提供的功能
19.3.1 模块导出数据语法
(1)分别暴露
export let uname = 'Rick';
export let sayHello = function () {
console.log('Hi, bro!');
}
(2)统一暴露
let uname = 'Rick';
let sayHello = function () {
console.log('Hi, bro!');
}
// 合并导出
export { uname, sayHello };
(3)默认暴露
export default {
uname: 'Rick',
sayHello: function () {
console.log('Hi, bro!');
}
}
19.3.2 模块导入数据语法
(1)通用导入方式
import * as m1 from './js/m1.js';
(2)解构赋值导入
import { uname, sayHello } from './js/m1.js';
// 有重复名可以设置别名
import { uname as uname2, sayHello as sayHello2 } from './js/m2.js';
// 配合默认暴露
import {default as m3} from "./src/js/m3.js";
(3)简便方式导入,针对默认暴露
import m3 from "./src/js/m3.js";
19.3.3 ES6 使用模块化方式二
将文件导入都写进一个 app.js 文件中,然后在里面写入要导入的模块。app.js 中的内容如下:
import * as m1 from './js/m1.js';
import * as m2 from './js/m2.js';
import * as m3 from './js/m3.js';
console.log(m1);
console.log(m2);
console.log(m3);
在 index.html 中引入 app.js 文件内容:
<script src="./app.js" type="module"></script>
19.4 babel对ES6模块化代码转化
它能将js的新语法转化为旧语法,以便更好的兼容。babel转码器
ES7 新特性
1. Array.prototype.includes
includes 方法用来检测数组中是否包含某个元素,返回布尔值。
实例方法 : includes( )
没有该方法之前,通常使用数组的indexOf方法,检查是否包含某个值。
if (arr.indexOf(el) !== -1) {
// ...
}
[NaN].indexOf(NaN) // -1
indexOf方法有两个缺点,一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1,表达起来不够直观。二是,它内部使用严格相等运算符(===)进行判断,这会导致对NaN的误判。
2. 指数运算符
在 ES7 中引入指数运算符 **,用来实现幂运算,功能与 Math.pow(a, b) 结果相同。
2 ** 3 // 8
Math.pow(2, 3) // 8
ES8 新特性
1. async 和 await
async 和 await 两种语法结合可以让异步代码像同步代码一样
1.1 async函数
- async 函数的返回值为 promise 对象
- promise 对象的结果由async函数执行的返回值决定
// async 函数
async function fn(){
// 1.返回一个字符串
// return '尚硅谷'
// 只要返回结果不是一个 Promise 类型的对象
// 则函数返回结果就是一个成功的 Promise <resolved>
// 2. 抛出错误,返回的结果是一个失败的 Promise <rejected>
// throw new Error('出错了')
// 3. 返回结果是 Promise 对象
return new Promise((resolve,reject)=>{
// resolve('成功的回调')
reject('失败的回调')
})
}
const res = fn()
// 哪怕是字符串,返回值也是 Promise 对象
console.log(res)
res.then(value => {
console.log(value)
},err => {
console.warn(err)
})
1.2 await表达式
- await 必须写在 async 函数中
- await 右侧的表达式一般为 promise 对象
- await 返回的是 promise 成功的值
- await 的 promise 失败了,就会抛出异常,需要通过 try…catch 捕获异常
- 当右接一个
Promise对象,则await表达式会阻塞后面的代码,等待当前Promise对象resolve的值。
const p = new Promise((resolve,reject)=>{
// resolve('成功的回调')
reject('失败的回调')
})
async function main(){
try{
let response = await p
console.log(response)
}catch (e){
console.log(e)
}
}
main()
综合 async 和 await 而言。await 必须结合 async 使用,而 async 则不一定需要 await。 async 会将其后的函数的返回值封装成一个 Promise 对象,而 await 会等待这个 Promise 完成,然后返回 resolve 的结果。当这个 Promise 失败或者抛出异常时,需要时使用 try-catch 捕获处理。
1.3 async 和 await结合读取文件内容
需求:先读取用户数据 user,然后读取订单数据 order,最后读取商品数据 goods。
对于这种异步操作很容易想到使用 Promise,代码如下:
const fs = require('fs')
let p = new Promise((resolve, reject) => {
fs.readFile('./files/user.md', (err, data) => {
if (err) reject(err)
resolve(data)
})
})
p.then(value => {
return new Promise((resolve, rejecet) => {
fs.readFile('./files/order.md', (err, data) => {
if (err) rejecet(err)
resolve([value, data])
})
})
}, reason => {
console.log(reason)
}).then(value => {
return new Promise((resolve, reject) => {
fs.readFile('./files/goods.md', (err, data) => {
if (err) reject(err)
value.push(data)
resolve(value)
})
})
}, reason => {
console.log(reason)
}).then(value => {
console.log(value.join('\n'))
}, reason => {
console.log(reason)
})
但是,使用 Promise 链式调用虽然避免了回调地狱,但这种链式调用过多难免引起代码复杂,看起来不直观。可以使用 async 和 await 方法优化,代码如下:
const fs = require('fs')
function readUser() {
return new Promise((resolve, reject) => {
fs.readFile('./files/user.md', (err, data) => {
if (err) reject(err)
resolve(data)
})
})
}
function readOrder() {
return new Promise((resolve, reject) => {
fs.readFile('./files/order.md', (err, data) => {
if (err) reject(err)
resolve(data)
})
})
}
function readGoods() {
return new Promise((resolve, reject) => {
fs.readFile('./files/goods.md', (err, data) => {
if (err) reject(err)
resolve(data)
})
})
}
async function read() {
let user = await readUser()
let order = await readOrder()
let goods = await readGoods()
console.log([user, order, goods].join('\n'))
}
read()
1.4 async 和 await发送 ajax请求
function sendAjax(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('get', url)
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.response))
}
reject(xhr.status)
}
}
})
}
async function main() {
let res = await sendAjax('http://poetry.apiopen.top/sentences')
let poem = res.result.name + '——' + res.result.from
document.body.innerText = poem
}
main()
这里封装的ajax还不能体现 async-await 的作用所在,因为没有出现多个 ajax 请求。在有多个 ajax 请求并且后续的请求依赖于前一个请求的结果的时候,async-await 的优点就体现出来了。
2. Object.values 和 Object.entries
ES5中,Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。
var obj = { foo: 'bar', baz: 42 };
Object.keys(obj) // ["foo", "baz"]
Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。
const obj = { foo: 'bar', baz: 42 };
Object.values(obj) // ["bar", 42]
Object.entries()方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
const obj = { foo: 'bar', baz: 42 };
Object.entries(obj) // [ ["foo", "bar"], ["baz", 42] ]
返回的是一个数组,这样就可以使用 for...of 遍历了。
for(let [k,v] of Object.entries(obj)){
console.log([k,v])
}
ES9 新特性(暂时未学习)
1.扩展运算符与rest参数
2.正则扩展-命名捕获分组
3.正则扩展-反向断言
4.正则扩展-dotAll模式
ES10 新特性
1.对象扩展方法 Object.fromEntries
<script>
// 二维数组转化为对象
const result = Object.fromEntries([
['name','尚硅谷'],
['course','软件工程']
])
console.log(result)
// Map数据结构
const m = new Map()
m.set('name','ATGUIGU')
const result2 = Object.fromEntries(m)
console.log(result2)
// 对象转化为二维数组
const arr = Object.entries({
name:'尚硅谷'
})
console.log(arr)
</script>
2.字符串方法扩展 trimStart-trimEnd
trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。
const s = ' abc ';
s.trim() // "abc"
s.trimStart() // "abc "
s.trimEnd() // " abc"
除了空格键,这两个方法对字符串头部(或尾部)的 tab 键、换行符等不可见的空白符号也有效。
2.数组方法扩展 flat和flatMap
数组的成员有时还是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。
[1, 2, [3, 4]].flat() // [1, 2, 3, 4]
[1, 2, [3, [4, 5]]].flat(2) // [1, 2, 3, 4, 5]
flat()默认只会“拉平”一层,参数即为深度,默认为1。
const arr = [1,2,3,4]
const result = arr.flatMap( item => [item * 10])
console.log(result) // [ 10, 20, 30, 40]
flatMap()方法对原数组的每个成员执行一个函数(相当于执行Array.prototype.map()),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。
3. Symbol.prototype.description
Symbol 值的实例属性description,直接返回 Symbol 值的描述。
<script>
// 创建 Symbol
const sym = Symbol('foo');
console.log(sym.description) // "foo"
</script>
ES11 新特性
1. 私有属性
class添加了私有属性,方法是在属性名之前使用#表示。
<script>
class Person{
// 公有属性
name;
// 私有属性
#age;
#weight;
// 构造方法
constructor(name, age, weight){
this.name = name
this.#age = age
this.#weight = weight
};
log(){
console.log(this.#age)
}
}
const girl = new Person('小小',18,'46kg')
console.log(girl)
// console.log(girl.#age) // 报错,不能访问私有属性
girl.log() // 18
</script>
2. Promise.allSettled方法
Promise.allSettled()方法接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled还是rejected),返回的 Promise 对象才会发生状态变更,且都是成功状态。
<script>
const p1 = new Promise((resolve, reject)=>{
setTimeout(()=>{
resolve('成功')
},1000)
})
const p2 = new Promise((resolve, reject)=>{
setTimeout(()=>{
reject('失败')
},1000)
})
// 调用 allSettled方法
const res = Promise.allSettled([p1, p2])
console.log(res)
// all方法,全部成功返回结果才成功,有一个失败则返回结果是失败
const res2 = Promise.all([p1, p2])
console.log(res2)
</script>
3. String.prototype.matchAll方法(正则-暂时未学习)
阮一峰 String.prototype.matchAll( )
4. 可选链操作符
可选链 ?. 是一种访问嵌套对象属性的安全的方式。即使中间的属性不存在,也不会出现错误。 原则:如果可选链 ?. 前面的部分是 undefined 或者 null,它会停止运算并返回该部分。
<script>
let user = {
address: {
}
}
console.log( user?.address?.street ); // undefined(不报错)
</script>
5. 动态 input
const btn = document.getElementById('btn');
// import()返回一个promise对象,返回的是所有暴露的方法
btn.onclick = function(){
import('./hello.js').then(module => {
module.hello();
}
5. BigInt类型
6.绝对全局对象globalThis
全局对象提供可在任何地方使用的变量和函数。默认情况下,这些全局变量内置于语言或环境中。 在浏览器中,它的名字是 window,对 Node.js 而言,它的名字是 global,其它环境可能用的是别的名字。 ES11中 globalThis 被作为全局对象的标准名称加入到了 JavaScript 中,所有环境都应该支持该名称。所有主流浏览器都支持它。 使用场景: 假设我们的环境是浏览器,我们将使用 window。如果你的脚本可能会用来在其他环境中运行,则最好使用 globalThis。
Array的扩展方法
(一)构造函数方法:Array.from()
(1)将类数组或可遍历对象转换为真正的数组。
(2)方法还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
var arraylist = {
'0':'张三',
'1':'里斯',
'2':'汪芜',
'length':3,
}
var arr = Array.from(arraylist)
console.log(arr) // ['张三','里斯','汪芜']
var list = {
'0':1,
'1':2,
'2':3,
'length':3,
}
var newArr = Array.from(list, item => item*2)
console.log(newArr) // [2,4,6]
(二)实例方法:find()
用于找出第一个符合条件的数组成员,如果没有找到就返回undefined。
let arr = [{
id: 1,
name:'张三'
},{
id: 2,
name:'李四'
}]
let newArr = arr.find((item,index) => item.id ==2 )
console.log(newArr)
(三)实例方法:findIndex()
用于找出第一个符合条件的数组成员的位置,如果没有返回-1。
let arr = [2,1,3,5]
let index = arr.findIndex((item,index) => item>4)
console.log(index) // 3
(四)实例方法:includes()
表示某个数组是否包含给定的值,返回布尔值。
let arr = [2,4,6,1,3]
let flag = arr.includes(3)
console.log(flag) // true
String的扩展方法
(一)startsWith()
表示参数字符串是否在原字符串的头部,返回布尔值。
(二)endsWith()
表示参数字符串是否在原字符串的尾部,返回布尔值。
let str = 'hello world!'
console.log(str.startsWith('he')) // true
console.log(str.endsWith('!')) // true
(三)repeat()
repeat方法表示将原字符串重复n次,返回一个新的字符串。
let str = 'hello'
let newStr = str.repeat(3)
console.log(newStr)