1 ECMA新描述概念
2 let、const的使用
3 let、const和var区别
4 块级作用域的使用
5 模板字符串的详解
6 ES6函数的增强用法
一个执行上下文关联两个环境。词法环境和变量环境。
词法环境是由let和const创建;变量环境是由var创建的。
Document
// 1.var声明的变量会进行作用域的提升 // console.log(message) // var message = "Hello World" // 2.let/const声明的变量: 没有作用域提升 // console.log(address) console.log(address) let address = "广州市" const info = {}
// 1.暂时性死区 // function foo() { // console.log(bar, baz) // console.log("Hello World") // console.log("你好 世界") // 暂时性死区是let声明变量之前的范围。 // let bar = "bar" // let baz = "baz" // } // foo() // 2.暂时性死区和定义的位置没有关系, 和代码执行的顺序有关系 // function foo() { // console.log(message) // } // let message = "Hello World" // foo() // console.log(message) // 3.暂时性死区形成之后, 在该区域内这个标识符不能访问 let message = "Hello World" function foo() { // 这里因为函数体里面有message,打印的地方又在message之前,会报错。 // 如果函数里面没有message,那就会去找函数外面的message。 console.log(message) const message = "哈哈哈哈" } foo()
1
// 1.var定义的变量是会默认添加到window上的 // var message = "Hello World" // var address = "广州市" // console.log(window.message) // console.log(window.address) // 2.let/const定义的变量不会添加到window上的 // let message = "Hello World" // let address = "广州市" // console.log(window.message) // console.log(window.address) // 3.let/var分别声明变量 var message = "Hello World" let adress = "广州市" function foo() { debugger } foo()
// 1.在ES5以及之前, 只有全局和函数会形成自己的作用域 // 代码块 // function foo() { // console.log("Hello World") // } // 下面这个是没有作用域的,包括if(){}也是没有自己的作用域。所以在{}外面可以拿到里面的参数值 // { // var message = "Hello World" // } // console.log(message) // 2.从ES6开始, 使用let/const/function/class声明的变量是有块级作用域 // console.log(message) // foo() { var message = "Hello World" let age = 18 const height = 1.88 class Person {} function foo() { console.log("foo function") } } // console.log(age) // console.log(height) // const p = new Person() foo()
一旦块级作用域里面定义的let变量执行结束后就会销毁掉
上图块级作用域里面的height还能查找到,但是title和info不能查找到了。
下图这里为什么${i}只会返回4? 是因为for这里没有作用域,${i}在function里面查找不到,会去上级作用域查找,这里上级作用域是window,在这里查找到的i是在for循环结束之后的,值已经是4了。
上图解决办法:
3、let变量,通过词法环境会放到环境记录了吗保存每次i的值。
// 1.形成的词法环境 // var message = "Hello World" // var age = 18 // function foo() {} // let address = "广州市" // { // var height = 1.88 // let title = "教师" // let info = "了解真相~" // } // 2.监听按钮的点击 const btnEls = document.querySelectorAll("button") // [btn1, btn2, btn3, btn4] // for (var i = 0; i < btnEls.length; i++) { // var btnEl = btnEls[i]; // // btnEl.index = i // (function(m) { // btnEl.onclick = function() { // debugger // console.log(`点击了${m}按钮`) // } // })(i) // } for (let i = 0; i < btnEls.length; i++) { const btnEl = btnEls[i]; btnEl.onclick = function() { console.log(`点击了${i}按钮`) } } // console.log(i)
1
// 1.基本用法 // 1.1.ES6之前 // const info = "my name is" + name + ", age is " + age // 1.2.ES6之后 const info = `my name is ${name}, age is ${age}` console.log(info) // 2.标签模板字符串的用法 function foo(...args) { console.log("参数:", args) } // foo("why", 18, 1.88) foo`my name is ${name}, age is ${age}, height is ${1.88}`
// 注意: 默认参数是不会对null进行处理的 function foo(arg1 = "我是默认值", arg2 = "我也是默认值") { // 1.两种写法不严谨 // 默认值写法一: // arg1 = arg1 ? arg1: "我是默认值" // 默认值写法二: // arg1 = arg1 || "我是默认值" // 2.严谨的写法 // 三元运算符 // arg1 = (arg1 === undefined || arg1 === null) ? "我是默认值": arg1 // ES6之后新增语法: ?? // arg1 = arg1 ?? "我是默认值" // 3.简便的写法: 默认参数 console.log(arg1) } foo(123, 321) foo() foo(0) foo("") foo(false) foo(null) foo(undefined)
// 1.注意一: 有默认参数的形参尽量写到后面 // 2.有默认参数的形参, 是不会计算在length之内(并且后面所有的参数都不会计算在length之内) // 3.剩余参数也是放到后面(默认参数放到剩余参数的前面) function foo(age, name = "why", ...args) { console.log(name, age, args) } foo(18, "abc", "cba", "nba") console.log(foo.length)
// 1.解构的回顾 // const obj = { name: "why" } // const { name = "kobe", age = 18 } = obj // 2.函数的默认值是一个对象 // function foo(obj = { name: "why", age: 18 }) { // console.log(obj.name, obj.age) // } function foo({ name, age } = { name: "why", age: 18 }) { console.log(name, age) } function foo({ name = "why", age = 18 } = {}) { console.log(name, age) } foo()
// 1.function定义的函数是有两个原型的: // function foo() {} // console.log(foo.prototype) // new foo() -> f.__proto__ = foo.prototype // console.log(foo.__proto__) // -> Function.prototype // 2.箭头函数是没有显式原型 // 在ES6之后, 定义一个类要使用class定义 var bar = () => {} // console.log(bar.__proto__ === Function.prototype) // 没有显式原型 // console.log(bar.prototype) // var b = new bar()
注意区分什么是剩余参数,什么是展开运算符
// 1.基本演练 // ES6 const names = ["abc", "cba", "nba", "mba"] const str = "Hello" // const newNames = [...names, "aaa", "bbb"] // console.log(newNames) function foo(name1, name2, ...args) { console.log(name1, name2, args) } foo(...names) foo(...str) // ES9(ES2018) const obj = { name: "why", age: 18 } // 不可以这样来使用 // foo(...obj) // 在函数的调用时, 用展开运算符, 将对应的展开数据, 进行迭代 // 可迭代对象: 数组/string/arguments const info = { ...obj, height: 1.88, address: "广州市" } console.log(info)
浅拷贝的意思就是只拷贝一份新的,包含第一层的参数,第二层开始的参数就没有创建新的。通过下图可以看得出来。
深拷贝顾名思义就是对象里面的对象也一样创建一份新的出来,两个对象内容一样,但是修改其中一个对象的值不会影响另外一个的参数。
const obj = { name: "why", age: 18, height: 1.88, friend: { name: "curry" } } // 1.引用赋值 // const info1 = obj // 2.浅拷贝 // const info2 = { // ...obj // } // info2.name = "kobe" // console.log(obj.name) // console.log(info2.name) // info2.friend.name = "james" // console.log(obj.friend.name) // 3.深拷贝 // 方式一: 第三方库 // 方式二: 自己实现 // function deepCopy(obj) {} // 方式三: 利用先有的js机制, 实现深拷贝JSON const info3 = JSON.parse(JSON.stringify(obj)) console.log(info3.friend.name) info3.friend.name = "james" console.log("info3.friend.name:", info3.friend.name) console.log(obj.friend.name)
// 1.进制 console.log(100) console.log(0b100) console.log(0o100) console.log(0x100) // 2.长数字的表示 const money = 100_00_00_0000_00_00
// ES6之前存在的问题 // const obj = { // name: "why", // fn: "aaa" // } // // 添加一个新的属性 name // function foo(obj) { // obj.why = function() {} // } // foo(obj) // console.log(obj.fn) // ES6之后可以使用Symbol生成一个独一无二的值 const s1 = Symbol() // const info = { name: "why" } const obj = { [s1]: "aaa" } console.log(obj) const s2 = Symbol() obj[s2] = "bbb" console.log(obj) function foo(obj) { const sKey = Symbol() obj[sKey] = function() {} delete obj[sKey] } foo(obj)
const s1 = Symbol() // aaa const s2 = Symbol() // bbb // 1.加入对象中 const obj = { name: "why", age: 18, [s1]: "aaa", [s2]: "bbb" } // const obj1 = {} // obj1[s1] = "aaa" // obj2[s2] = "bbb" // const obj2 = {} // Object.defineProperty(obj, s1, { // value: "aaa" // }) // 2.获取symbol对应的key和对应的值 console.log(Object.keys(obj)) console.log(Object.getOwnPropertySymbols(obj)) const symbolKeys = Object.getOwnPropertySymbols(obj) for (const key of symbolKeys) { console.log(obj[key]) } // 3.description // 3.1.Symbol函数直接生成的值, 都是独一无二 const s3 = Symbol("ccc") console.log(s3.description) const s4 = Symbol(s3.description) console.log(s3 === s4) // 3.2. 如果相同的key, 通过Symbol.for可以生成相同的Symbol值 const s5 = Symbol.for("ddd") const s6 = Symbol.for("ddd") console.log(s5 === s6) // 获取传入的key console.log(Symbol.keyFor(s5))
set可以用在数组去重上面。
// 1.创建Set const set = new Set() console.log(set) // 2.添加元素 set.add(10) set.add(22) set.add(35) set.add(22) console.log(set) const info = {} const obj = {name: "obj"} set.add(info) set.add(obj) set.add(obj) console.log(set) // 3.应用场景: 数组的去重 const names = ["abc", "cba", "nba", "cba", "nba"] // const newNames = [] // for (const item of names) { // if (!newNames.includes(item)) { // newNames.push(item) // } // } // console.log(newNames) const newNamesSet = new Set(names) // 转成数组形式 const newNames = Array.from(newNamesSet) console.log(newNames) // 4.Set的其他属性和方法 // 属性 console.log(set.size) // 方法 // 4.1. add方法 set.add(100) console.log(set) // 4.2. delete方法 set.delete(obj) console.log(set) // 4.3. has方法 console.log(set.has(info)) // 4.4. clear方法 // set.clear() // console.log(set) // 4.5. forEach set.forEach(item => console.log(item)) // 5.set支持for...of for (const item of set) { console.log(item) }
由于数组里面有对三个对象的引用,所以垃圾回收不会因为三个对象赋值null而清除掉。通过weakset可以解决上面的问题。但是weakset不能枚举遍历。单纯set找不到。
// 1.Weak Reference(弱引用)和Strong Reference(强引用) let obj1 = { name: "why" } let obj2 = { name: "kobe" } let obj3 = { name: "jame" } // let arr = [obj1, obj2, obj3] // obj1 = null // obj2 = null // obj3 = null // const set = new Set(arr) // arr = null // 2.WeakSet的用法 // 2.1.和Set的区别一: 只能存放对象类型 const weakSet = new WeakSet() weakSet.add(obj1) weakSet.add(obj2) weakSet.add(obj3) // 2.2.和Set的区别二: 对对象的引用都是弱引用 // 3.WeakSet的应用 const pWeakSet = new WeakSet() class Person { constructor() { pWeakSet.add(this) } running() { if (!pWeakSet.has(this)) { console.log("Type error: 调用的方式不对") return } console.log("running~") } } let p = new Person() // p = null p.running() const runFn = p.running runFn() const obj = { run: runFn } obj.run()
const info = { name: "why" } const info2 = { age: 18 } // 1.对象类型的局限性: 不可以使用复杂类型作为key // const obj = { // address: "北京市", // [info]: "哈哈哈", // [info2]: "呵呵呵" // } // console.log(obj) // 2.Map映射类型 const map = new Map() map.set(info, "aaaa") map.set(info2, "bbbb") console.log(map) // 3.Map的常见属性和方法 // console.log(map.size) // 3.1. set方法, 设置内容 map.set(info, "cccc") console.log(map) // 3.2. get方法, 获取内容 // console.log(map.get(info)) // 3.3. delete方法, 删除内容 // map.delete(info) // console.log(map) // 3.4. has方法, 判断内容 // console.log(map.has(info2)) // 3.5. clear方法, 清空内容 // map.clear() // console.log(map) // 3.6. forEach方法 // map.forEach(item => console.log(item)) // 4.for...of遍历 for (const item of map) { const [key, value] = item console.log(key, value) }
let obj1 = { name: "why" } let obj2 = { name: "kobe" } // 1.WeakMap的基本使用 const weakMap = new WeakMap() // weakMap.set(123, "aaa") weakMap.set(obj1, "aaa") weakMap.set(obj2, "bbb") obj1 = null obj2 = null