章节:
Part 1 · JavaScript 深度剖析:
模块二:ES 新特性与 TypeScript、JS 性能优化
概要:
- ECMAScript与JavaScript
- ECMAScript的发展过程
- ECMAScript2015的新特性
ECMAScript通常看做JavaScript的标准化规范
实际上JavaScript是ECMAScript的扩展语言
ECMAScript只提供了最基本的语法(例如,怎么定义变量常量)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jvoeP8q8-1605762522991)(C:\Users\hp\AppData\Roaming\Typora\typora-user-images\image-20201110132619521.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4B3f9fB4-1605762522995)(C:\Users\hp\AppData\Roaming\Typora\typora-user-images\image-20201110132658528.png)]
JavaScript语言本身指的就是ECMAScript
ES2015=ES6
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ogS4pYn3-1605762522997)(C:\Users\hp\AppData\Roaming\Typora\typora-user-images\image-20201110132840084.png)]
“ES6”——有泛指ES2015在内往后的新版本的意思,注意分辨文中是指ES2015还是新版本
ES2015新特性(在ES5.1基础之上的变化,主要分4大类):
**运行环境:**NodeJS和最新Chrome
NodeJS小工具:Nodemon——修改完代码后自动执行代码
命令使用:
node --version
code .\01-02-01-01-es2015-spec\
yarn init
yarn add nodemon --dev
yarn nodemon 01-test.js
**作用域:**某个成员能够起作用的范围
ES2015之前,有两种作用域:
ES2015新增:
块级作用域:
if (true) {
console.log('haha,me')
}
for (var i = 0; i< 10; i++){
console.log('haha,me')
}
// var--ES6以前块没有独立的作用域,导致块内定义的成员,外部也能访问到
if (true) {
var foo = 'hhh';
}
console.log(foo); // hhh
// let-- 声明的成员只会在所声明的块中生效
if (true) {
let foo = 'hhh';
}
console.log(foo); // foo is not defined
在 for 循环中的表现(应用场景:计数器)
// var--变量同名,外层for循环受影响
for (var i = 0; i < 3; i++) {
for (var i = 0; i < 3; i++) {
console.log(i)
}
console.log('内层结束 i = ' + i)
}
/*
0
1
2
内层结束 i = 3
*/
// let--变量同名,外层for循环无影响
for (var i = 0; i < 3; i++) {
for (let i = 0; i < 3; i++) {
console.log(i)
}
console.log('内层结束 i = ' + i)
}
/*
0
1
2
内层结束 i = 0
0
1
2
内层结束 i = 1
0
1
2
内层结束 i = 2
*/
let 应用场景:循环绑定事件,事件处理函数中获取正确索引
// var
var elements = [{}, {}, {}]
for (var i = 0; i < elements.length; i++) {
elements[i].onclick = function () {
console.log(i)
}
}
elements[2].onclick(); // 3 原因:for循环结束后i=3,i是全局变量。
// 闭包解决--借助函数作用域摆脱全局作用域的影响?
var elements = [{}, {}, {}]
for (var i = 0; i < elements.length; i++) {
elements[i].onclick = (function (i) {
return function () {
console.log(i)
}
})(i)
}
elements[0].onclick()
// let--块级作用域(本质也是闭包机制)
var elements = [{}, {}, {}]
for (let i = 0; i < elements.length; i++) {
elements[i].onclick = function () {
console.log(i)
}
}
elements[0].onclick()
// for 循环会产生两层作用域 ----------------------------------
for (let i = 0; i < 3; i++) {
let i = 'foo'
console.log(i)
}
/*
foo
foo
foo
*/
// 理解-for 循环会产生两层作用域
let i = 0
if (i < 3) {
let i = 'foo'
console.log(i)
}
i++
if (i < 3) {
let i = 'foo'
console.log(i)
}
i++
if (i < 3) {
let i = 'foo'
console.log(i)
}
i++
/*
foo
foo
foo
*/
// let 修复了变量声明提升现象 --------------------------------------------
console.log(foo); // 变量声明提升:不报错,已声名,未定义
var foo = 'zce'
/*
undefined
*/
// console.log(foo); // 报错-不允许使用未声明的变量
// let foo = 'zce'
/*
ReferenceError: Cannot access 'foo' before initialization
*/
const:
const name = 'zce'
// 恒量声明过后不允许重新赋值
name = 'jack' // TypeError: Assignment to constant variable. (赋值给常量变量。)
// 恒量要求声明同时赋值
const name
name = 'zce' // SyntaxError: Missing initializer in const declaration(const声明中缺少初始化式)
// 恒量只是要求内层指向不允许被修改
const obj = {}
obj = {} // TypeError: Assignment to constant variable.
// 对于数据成员的修改是没有问题的
obj.name = 'zce'
console.log(obj); // { name: 'zce' }
最佳实践:不用var,主用const,配合let
**作用:**根据位置快速提取数组中的值
// 数组的解构
const arr = [100, 200, 300]
// ES6前的获取数组元素
// const foo = arr[0]
// const bar = arr[1]
// const baz = arr[2]
// console.log(foo, bar, baz)
// 解构获取全部
const [foo, bar, baz] = arr
console.log('-----',foo, bar, baz)
const [, , baz] = arr
console.log(baz)
const [foo, ...rest] = arr
console.log(rest)
const [foo, bar] = arr
console.log(arr.length, foo, bar);
const [foo, bar, baz, more] = arr
console.log(arr.length, more) *// 3 undefined
const [foo, bar, baz = 123, more = 'default value'] = arr
console.log(foo, bar, baz, more) *// 100 200 300 default value
const path = '/foo/bar/baz'
// const tmp = path.split('/')
// console.log(tmp)
// const rootdir = tmp[1]
const [, rootdir] = path.split('/')
console.log(rootdir)
**作用:**根据属性名匹配提取对象中相应属性的值,属性名也是提取出的数据存放的变量名
**优点:**简化编写,减少代码体积
const obj = { name: 'zce', age: 18 }
// 对象的解构
const { name } = obj
console.log(name)
const { log } = console
log('foo')
log('bar')
log('123')
const obj = { name: 'hhh', age: 18 }
const { gender = 'moren' } = obj
console.log(gender)
const obj = { name: 'zce', age: 18 }
const { name: objName = 'jack' } = obj
console.log(objName)
作用:定义字符串
语法:反引号
const str = `hello es2015, this is a string`
支持换行
const str = `hello es2015,
this is a \`string\``
console.log(str)
插值表达式——可以通过 ${} 插入表达式,表达式的执行结果将会输出到对应位置
const name = 'tom'
const msg = `hey, ${name} --- ${1 + 2} ---- ${Math.random()}`
console.log(msg)
作用: 加工模板字符串、文本多语言化、检查模板字符串中是否存在不安全、小型引擎?
**语法:**模板字符串的标签就是一个特殊的函数,使用这个标签就是调用这个函数
const name = 'tom'
const gender = false
function myTagFunc (strings, name, gender) {
// console.log(strings, name, gender)
// return '123'
const sex = gender ? 'man' : 'woman'
return strings[0] + name + strings[1] + sex + strings[2]
}
const result = myTagFunc`hey, ${name} is a ${gender}.`
console.log(result)
作用:字符串查找
const message = 'Error: foo is not defined.'
console.log(
// message.startsWith('Error') // 是否是以Error开头
// message.endsWith('.') // 是否是以.结尾
message.includes('foo') // 是否包含foo
)
**作用:**设置参数默认值
语法:
// function foo (enable) {
// // 短路运算很多情况下是不适合判断默认参数的,例如 0 '' false null
// // enable = enable || true
// enable = enable === undefined ? true : enable
// console.log('foo invoked - enable: ')
// console.log(enable)
// }
// 默认参数一定是在形参列表的最后
function foo (enable = true) {
console.log('foo invoked - enable: ')
console.log(enable)
}
foo(false)
作用:函数参数不确定时
语法:只能出现在形参最后一位,只能使用一次
// function foo () {
// console.log(arguments)
// }
function foo (first, ...args) {
console.log(args)
}
foo(1, 2, 3, 4)// [ 2, 3, 4 ]
作用:数组元素个数不固定
语法:
const arr = ['foo', 'bar', 'baz']
// console.log(
// arr[0],
// arr[1],
// arr[2],
// )
// 以前用apply(this, arr)
// console.log.apply(console, arr)
console.log(...arr)
**作用:**简化函数定义,新增特性。。。
语法:
Fira Code:编程专用的连体字体
// (完整参数列表)=>{函数体多条语句,返回值需 return}
// 一个参数 => 一条语句直接返回console.log('inc invoked')
const inc = (n, m) => {
console.log('inc invoked')
return n + 1
}
**场景:**简化回调函数编写
const arr = [1, 2, 3, 4, 5, 6, 7]
// arr.filter(function (item) {
// return item % 2
// })
// 常用场景,回调函数
arr.filter(i => i % 2)
箭头函数不会改变this指向
普通函数中this时钟指向调用该函数的对象
箭头函数外边this是什么,里边拿到的this就是什么
// 箭头函数与 this
// 箭头函数不会改变 this 指向
const person = {
name: 'tom',
// sayHi: function () {
// console.log(`hi, my name is ${this.name}`)
// }
sayHi: () => {
this.a = 'zuoyongyu';
console.log('sayHi', this); // *****当前作用域 是sayHi{作用域}?
console.log(`hi, my name is ${this.name}`)
},
sayHiAsync: function () {
// const _this = this // 保存当前作用域的this,闭包机制
// setTimeout(function () {
// console.log(_this.name) // 这块执行作用域是全局,拿到的是全局this
// }, 1000)
console.log(this)
setTimeout(() => { // 箭头函数中this始终指向当前作用域的this,当前作用域person{}?
// console.log(this.name)
console.log('sayHiAsync', this)
}, 1000)
}
}
person.sayHi()
person.sayHiAsync()
const bar = '345'
const obj = {
foo: 123,
// bar: bar
// 1、---属性名与变量名相同,可以省略 : bar
bar,
// method1: function () {
// console.log('method111')
// }
// 2、---方法可以省略 : function
method1 () {
console.log('method111')
// ****这种方法就是普通的函数,同样影响 this 指向。
console.log(this)
},
// Math.random(): 123 // 不允许
// 3、---计算属性名:[任意表达式] 结果作为属性名
[`a${bar}`]: 123
}
// obj[Math.random()] = 123
console.log(obj) // { foo: 123, bar: '345', method1: [Function: method1], a345: 123 }
obj.method1() // method111 { foo: 123, bar: '345', method1: [Function: method1], a345: 123 }
**作用:**后面对象的值覆盖前面对象的值;复制对象;浅拷贝–只对比最外层
**语法:**Object.assign(目标对象, 源对象1, 源对象…)
const source1 = {
a: 123,
b: 123
}
const source2 = {
b: 789,
d: 789
}
const target = {
a: 456,
c: 456
}
const result = Object.assign(target, source1, source2)
console.log(target) // { a: 123, c: 456, b: 789, d: 789 }
console.log(result === target) // true
作用:两个值是否相等
== 自动转换类型
=== 仅比较值
console.log(
// 0 == false // => true
// 0 === false // => false
// +0 === -0 // => true
// NaN === NaN // => false
Object.is(+0, -0), // => false
Object.is(NaN, NaN) // => true
)
**作用:**监视对象的读写过程
ES5:Object.defineProperty
ES2015:Proxy
语法:
const person = {
name: 'zce',
age: 20
}
const personProxy = new Proxy(person, {
// 监视属性读取
get (target, property) {
return property in target ? target[property] : 'default'
// console.log(target, property)
// return '读取'
},
// 监视属性设置
set (target, property, value) {
if (property === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError(`${value} is not an int`)
}
}
target[property] = value
console.log('设置', target, property, value)
}
})
personProxy.age = 66 // => 设置 { name: 'zce', age: 66 } age 66
personProxy.gender = true // => 设置 { name: 'zce', age: 66, gender: true } gender true
console.log(personProxy.name) // => zce
console.log(personProxy.xxx) // => default
作用:
操作对象时做特殊逻辑处理
统一操作方式?
场景:?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G9nqxrxD-1605762523001)(C:\Users\hp\AppData\Roaming\Typora\typora-user-images\image-20201117135145310.png)]
const person = {
name: 'zce',
age: 20
}
const personProxy = new Proxy(person, {
deleteProperty (target, property) {
console.log('delete', property)
delete target[property]
}
})
delete personProxy.age // => delete age
console.log(person) // => { name: 'zce' }
const list = []
const listProxy = new Proxy(list, {
set (target, property, value) {
console.log('set', property, value)
target[property] = value
return true // 表示设置成功
}
})
listProxy.push(77) // => set 0 77 ?自动显示数组长度set length 1
listProxy.push(100) // => set 1 100 自动显示数组长度set length 2
console.log('list---', list) // => list--- [ 77, 100 ]
const person = {}
Object.defineProperty(person, 'name', {
get () {
console.log('name 被访问')
return person._name
},
set (value) {
console.log('name 被设置')
person._name = value
}
})
Object.defineProperty(person, 'age', {
get () {
console.log('age 被访问')
return person._age
},
set (value) {
console.log('age 被设置')
person._age = value
}
})
person.name = 'jack' // => name 被设置
console.log(person.name) // => name 被访问 jack
Reflect:
Reflect统一的对象操作API
Reflect属于一个静态类(不能new,只能调用其方法)
Reflect内部封装了一系列对对象的底层操作(13个)
Reflect成员方法就是Proxy处理对象的默认实现
作用:
const obj = {
name: 'zce',
age: 18
}
// 传统:需使用操作符、方法等
console.log('name' in obj)
console.log(delete obj['age'])
console.log(Object.keys(obj))
// Reflect:统一用Reflect API
console.log(Reflect.has(obj, 'name'))
console.log(Reflect.deleteProperty(obj, 'age'))
console.log(Reflect.ownKeys(obj))