嘉兴网站建设方案外包,微信商城在哪里找,wordpress主题xstore,2024年3月新冠高峰文章目录 一、ES6中对象的增强1.字面量的增强2.解构Destructuring3.解构的应用场景 二、手写实现apply、call、bind方法1.apply、call方法2.bind方法 三、ECMA新描述概念1.新的ECMA代码执行描述2.词法环境#xff08;Lexical Environments#xff09;3.词法环境和变量环境4.环… 文章目录 一、ES6中对象的增强1.字面量的增强2.解构Destructuring3.解构的应用场景 二、手写实现apply、call、bind方法1.apply、call方法2.bind方法 三、ECMA新描述概念1.新的ECMA代码执行描述2.词法环境Lexical Environments3.词法环境和变量环境4.环境记录Environment Record 四、let、const的使用1.let、const基本使用2.let、const作用域提升3.let、const声明变量保存位置4.块级作用域4.1暂时性死区 5.var、let、const的选择 五、模板字符串1.字符串模板基本使用2.标签模板字符串的使用2.1标签模板字符串的应用 六、函数知识补充1.函数的默认参数2.函数的剩余参数4.箭头函数的补充 七、其它知识补充1.展开语法2.数值的表示 八、symbol1.symbol的基本使用2.Symbol作为属性名3.相同值的Symbol 一、ES6中对象的增强
1.字面量的增强
ES6中对 对象字面量 进行了增强称之为 Enhanced object literals增强对象字面量。
字面量的增强主要包括下面几部分 属性的简写Property Shorthand 方法的简写Method Shorthand 计算属性名Computed Property Names
/*1.属性的增强2.方法的增强3.计算属性名的写法
*/var name why
var age 18var key address cityvar obj {// 1.属性的增强name,age,// 2.方法的增强running: function() {console.log(this)},swimming() {console.log(this)},eating: () {console.log(this)},// 3.计算属性名[key]: 广州
}obj.running()
obj.swimming()
obj.eating()function foo() {var message Hello Worldvar info my name is whyreturn { message, info }
}var result foo()
console.log(result.message, result.info)2.解构Destructuring
ES6中新增了一个从数组或对象中方便获取数据的方法称之为解构Destructuring。
解构赋值 是一种特殊的语法它使我们可以将数组或对象拆包至一系列变量中。
我们可以划分为数组的解构和对象的解构。
数组的解构
基本解构过程顺序解构解构出数组…语法默认值
对象的解构
基本解构过程任意顺序重命名默认值
var names [abc, cba, undefined, nba, mba]// 1.数组的解构
// var name1 names[0]
// var name2 names[1]
// var name3 names[2]
// 1.1. 基本使用
// var [name1, name2, name3] names
// console.log(name1, name2, name3)// 1.2. 顺序问题: 严格的顺序
// var [name1, , name3] names
// console.log(name1, name3)// 1.3. 解构出数组
// var [name1, name2, ...newNames] names
// console.log(name1, name2, newNames)// 1.4. 解构的默认值
var [name1, name2, name3 default] names
console.log(name1, name2, name3)// 2.对象的解构
var obj { name: why, age: 18, height: 1.88 }
// var name obj.name
// var age obj.age
// var height obj.height
// 2.1. 基本使用
// var { name, age, height } obj
// console.log(name, age, height)// 2.2. 顺序问题: 对象的解构是没有顺序, 根据key解构
// var { height, name, age } obj
// console.log(name, age, height)// 2.3. 对变量进行重命名
// var { height: wHeight, name: wName, age: wAge } obj
// console.log(wName, wAge, wHeight)// 2.4. 默认值
var { height: wHeight, name: wName, age: wAge, address: wAddress 中国
} obj
console.log(wName, wAge, wHeight, wAddress)// 2.5. 对象的剩余内容
var {name,age,...newObj
} obj
console.log(newObj)3.解构的应用场景
解构目前在开发中使用是非常多的 比如在开发中拿到一个变量时自动对其进行解构使用 比如对函数的参数进行解构
// 应用: 在函数中(其他类似的地方)
function getPosition({ x, y }) {console.log(x, y)
}getPosition({ x: 10, y: 20 })
getPosition({ x: 25, y: 35 })二、手写实现apply、call、bind方法
1.apply、call方法
// new Function()
// foo.__proto__ Function.prototype
function foo(name, age) {console.log(this, name, age)
}// foo函数可以通过apply/call
// foo.apply(aaa, [why, 18])
// foo.call(bbb, kobe, 30)// 1.给函数对象添加方法: hyapply
Function.prototype.hyapply function(thisArg, otherArgs) {// this - 调用的函数对象// thisArg - 传入的第一个参数, 要绑定的this// console.log(this) // - 当前调用的函数对象// this.apply(thisArg)thisArg.fn this// 1.获取thisArg, 并且确保是一个对象类型thisArg (thisArg null || thisArg undefined)? window: Object(thisArg)// thisArg.fn thisObject.defineProperty(thisArg, fn, {enumerable: false,configurable: true,value: this})thisArg.fn(...otherArgs)delete thisArg.fn
}// foo.hyapply({ name: why }, [james, 25])
// foo.hyapply(123, [why, 18])
// foo.hyapply(null, [kobe, 30])// 2.给函数对象添加方法: hycall
Function.prototype.hycall function(thisArg, ...otherArgs) {// 1.获取thisArg, 并且确保是一个对象类型thisArg (thisArg null || thisArg undefined)? window: Object(thisArg)// thisArg.fn thisObject.defineProperty(thisArg, fn, {enumerable: false,configurable: true,value: this})thisArg.fn(...otherArgs)delete thisArg.fn
}foo.hycall({ name: why, fn: abc }, james, 25)
foo.hycall(123, why, 18)
foo.hycall(null, kobe, 30)抽取代码进行封装
// new Function()
// foo.__proto__ Function.prototype
function foo(name, age) {console.log(this, name, age)
}// foo函数可以通过apply/call
// foo.apply(aaa, [why, 18])
// foo.call(bbb, kobe, 30)// 1.封装思想
// 1.1.封装到独立的函数中
function execFn(thisArg, otherArgs, fn) {// 1.获取thisArg, 并且确保是一个对象类型thisArg (thisArg null || thisArg undefined)? window: Object(thisArg)// thisArg.fn thisObject.defineProperty(thisArg, fn, {enumerable: false,configurable: true,value: fn})// 执行代码thisArg.fn(...otherArgs)delete thisArg.fn
}// 1.2. 封装原型中
Function.prototype.hyexec function(thisArg, otherArgs) {// 1.获取thisArg, 并且确保是一个对象类型thisArg (thisArg null || thisArg undefined)? window: Object(thisArg)// thisArg.fn thisObject.defineProperty(thisArg, fn, {enumerable: false,configurable: true,value: this})thisArg.fn(...otherArgs)delete thisArg.fn
}// 1.给函数对象添加方法: hyapply
Function.prototype.hyapply function(thisArg, otherArgs) {this.hyexec(thisArg, otherArgs)
}
// 2.给函数对象添加方法: hycall
Function.prototype.hycall function(thisArg, ...otherArgs) {this.hyexec(thisArg, otherArgs)
}foo.hyapply({ name: why }, [james, 25])
foo.hyapply(123, [why, 18])
foo.hyapply(null, [kobe, 30])foo.hycall({ name: why }, james, 25)
foo.hycall(123, why, 18)
foo.hycall(null, kobe, 30)2.bind方法
// apply/call
function foo(name, age, height, address) {console.log(this, name, age, height, address)
}// Function.prototype
// var newFoo foo.bind({ name: why }, why, 18)
// newFoo(1.88)// 实现hybind函数
Function.prototype.hybind function(thisArg, ...otherArgs) {// console.log(this) // - foo函数对象thisArg thisArg null || thisArg undefined ? window: Object(thisArg)Object.defineProperty(thisArg, fn, {enumerable: false,configurable: true,writable: false,value: this})return (...newArgs) {// var allArgs otherArgs.concat(newArgs)var allArgs [...otherArgs, ...newArgs]thisArg.fn(...allArgs)}
}var newFoo foo.hybind(abc, kobe, 30)
newFoo(1.88, 广州市)
newFoo(1.88, 广州市)
newFoo(1.88, 广州市)
newFoo(1.88, 广州市)三、ECMA新描述概念
1.新的ECMA代码执行描述
在执行学习JavaScript代码执行过程中我们学习了很多ECMA文档的术语 执行上下文栈Execution Context Stack用于执行上下文的栈结构 执行上下文Execution Context代码在执行之前会先创建对应的执行上下文- 变量对象Variable Object上下文关联的VO对象用于记录函数和变量声明 全局对象Global Object全局执行上下文关联的VO对象 激活对象Activation Object函数执行上下文关联的VO对象 作用域链scope chain作用域链用于关联指向上下文的变量查找
在新的ECMA代码执行描述中ES5以及之上对于代码的执行流程描述改成了另外的一些词汇 基本思路是相同的只是对于一些词汇的描述发生了改变 执行上下文栈和执行上下文也是相同的
2.词法环境Lexical Environments
词法环境是一种规范类型用于在词法嵌套结构中定义关联的变量、函数等标识符 一个词法环境是由环境记录Environment Record和一个外部词法环境oute;r Lexical Environment组成 一个词法环境经常用于关联一个函数声明、代码块语句、try-catch语句当它们的代码被执行时词法环境被创建出来 也就是在ES5之后执行一个代码通常会关联对应的词法环境
那么执行上下文会关联哪些词法环境呢 3.词法环境和变量环境
LexicalEnvironment和VariableEnvironment
LexicalEnvironment用于存放let、const声明的标识符 VariableEnvironment用于存放var和function声明的标识符 4.环境记录Environment Record
在这个规范中有两种主要的环境记录值:声明式环境记录和对象环境记录。
声明式环境记录声明性环境记录用于定义ECMAScript语言语法元素的效果如函数声明、变量声明和直接将标识符绑定与ECMAScript语言值关联起来的Catch子句。
对象式环境记录对象环境记录用于定义ECMAScript元素的效果例如WithStatement它将标识符绑定与某些对象的属性关联起来。 四、let、const的使用
1.let、const基本使用
在ES5中我们声明变量都是使用的var关键字从ES6开始新增了两个关键字可以声明变量let、const
let关键字
从直观的角度来说let和var是没有太大的区别的都是用于声明一个变量
const关键字
const关键字是constant的单词的缩写表示常量、衡量的意思
它表示保存的数据一旦被赋值就不能被修改
但是如果赋值的是引用类型那么可以通过引用找到对应的对象修改对象的内容 注意另外let、const不允许重复声明变量 // ES6之前
var message1 Hello World
message1 Hello Coderwhy
message1 aaaaa
console.log(message1)// ES6开始
// 1.let
let message2 你好, 世界
message2 你好, why
message2 123
console.log(message2)// 2.const
// const message3 nihao, shijie
// message3 nihao, why// 赋值引用类型
const info {name: why,age: 18
}
// info {}
info.name kobe
console.log(info)let、const声明的变量不允许重复声明
// 1.var变量可以重复声明
// var message Hello World
// var message 你好, 世界// 2.let/const不允许变量的重复声明
// var address
let address 广州市
// let address 上海市
const info {}
// const info {}2.let、const作用域提升
let、const和var的另一个重要区别是作用域提升
我们知道var声明的变量是会进行作用域提升的但是如果我们使用let声明的变量在声明之前访问会报错
那么是不是意味着foo变量只有在代码执行阶段才会创建的呢
事实上并不是这样的我们可以看一下ECMA262对let和const的描述 这些变量会被创建在包含他们的词法环境被实例化时但是是不可以访问它们的直到词法绑定被求值 从上面我们可以看出在执行上下文的词法环境创建出来的时候变量事实上已经被创建了只是这个变量是不能被访问的。
那么变量已经有了但是不能被访问是不是一种作用域的提升呢
事实上维基百科并没有对作用域提升有严格的概念解释那么我们自己从字面量上理解
作用域提升在声明变量的作用域中如果这个变量可以在声明之前被访问那么我们可以称之为作用域提升
在这里它虽然被创建出来了但是不能被访问我认为不能称之为作用域提升
所以我的观点 let、const没有进行作用域提升但是会在解析阶段被创建出来。 3.let、const声明变量保存位置
我们知道在全局通过var来声明一个变量事实上会在window上添加一个属性
但是let、const是不会给window上添加任何属性的。
那么我们可能会想这个变量是保存在哪里呢
我们先回顾一下最新的ECMA标准中对执行上下文的描述 也就是说我们声明的变量和环境记录是被添加到变量环境中的
但是标准有没有规定这个对象是window对象或者其他对象呢
其实并没有那么JS引擎在解析的时候其实会有自己的实现
比如v8中其实是通过VariableMap的一个hashmap来实现它们的存储的。
那么window对象呢而window对象是早期的GOGlobal Object对象在最新的实现中其实是浏览器添加的全局对象并且一直保持了window和var之间值的相等性 4.块级作用域
在我们前面的学习中JavaScript只会形成两个作用域全局作用域和函数作用域。 ES5中放到一个代码中定义的变量外面是可以访问的
{var a 10;
}
console.log(a)在ES6中新增了块级作用域并且通过let、const、function、class声明的标识符是具备块级作用域的限制的
{var a hello;let info zhan;const num 10;class Person {}
}
console.log(a) // 可以访问
console.log(info) // 无法访问
console.log(Person) // 无法访问但是我们会发现函数拥有块级作用域但是外面依然是可以访问的
这是因为引擎会对函数的声明进行特殊的处理允许像var那样进行提升
4.1暂时性死区
暂时性死区指在作用域内执行开始位置到标识符定义这块区域
// 1.暂时性死区
// function foo() {
// console.log(bar, baz)// console.log(Hello World)
// console.log(你好 世界)
// 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() {console.log(message)const message 哈哈哈哈}foo()块级作用域的应用场景
bodydivbutton按钮1/buttonbutton按钮2/buttonbutton按钮3/buttonbutton按钮4/button/divscript
const btnArr document.querySelectorAll(button);
for (let i 0; i btnArr.length; i) {btnArr[i].onclick function () {console.log(按钮${i1});}
}/script5.var、let、const的选择
那么在开发中我们到底应该选择使用哪一种方式来定义我们的变量呢
对于var的使用 我们需要明白一个事实var所表现出来的特殊性比如作用域提升、window全局对象、没有块级作用域等都是一些历史遗留问题 其实是JavaScript在设计之初的一种语言缺陷 当然目前市场上也在利用这种缺陷出一系列的面试题来考察大家对JavaScript语言本身以及底层的理解 但是在实际工作中我们可以使用最新的规范来编写也就是不再使用var来定义变量了
对于let、const
对于let和const来说是目前开发中推荐使用的我们会有限推荐使用const这样可以保证数据的安全性不会被随意的篡改只有当我们明确知道一个变量后续会需要被重新赋值时这个时候再使用let这种在很多其他语言里面也都是一种约定俗成的规范尽量我们也遵守这种规范
五、模板字符串
1.字符串模板基本使用
在ES6之前如果我们想要将字符串和一些动态的变量标识符拼接到一起是非常麻烦和丑陋的ugly。
ES6允许我们使用字符串模板来嵌入JS的变量或者表达式来进行拼接
首先我们会使用 符号来编写字符串称之为模板字符串
其次在模板字符串中我们可以通过 ${expression} 来嵌入动态的内容
const age 10
const info 吃饭睡觉打豆豆的${age} 是我的年龄2.标签模板字符串的使用
模板字符串还有另外一种用法标签模板字符串Tagged Template Literals。
我们一起来看一个普通的JavaScript的函数
function foo(...args) {console.log(args)
}
foo(hello world)如果我们使用标签模板字符串并且在调用的时候插入其他的变量
const name hello
const age 18
fooabcd ${age} is ${name}模板字符串被拆分了第一个元素是数组是被模块字符串拆分的字符串组合后面的元素是一个个模块字符串传入的内容 2.1标签模板字符串的应用
React的styled-components库用法 六、函数知识补充
1.函数的默认参数
在ES6之前我们编写的函数参数是没有默认值的所以我们在编写函数时如果有下面的需求 传入了参数那么使用传入的参数 没有传入参数那么使用一个默认值 而在ES6中我们允许给函数一个默认值
function foo(xname, yage) {console.log(x, y)
}
foo()// name, age默认值也可以和解构一起来使用
function foo({name, age}) {console.log(name, age)
}
// 方式1
function foo({name, age} {name: zhangsan, age: 18}) {console.log(name, age)
}
// 方式2
function foo({name zhangsan, age 18} {}) {console.log(name, age)
}另外参数的默认值我们通常会将其放到最后在很多语言中如果不放到最后其实会报错的
但是JavaScript允许不将其放到最后但是意味着还是会按照顺序来匹配
另外默认值会改变函数的length的个数默认值以及后面的参数都不计算在length之内了。
// 注意: 默认参数是不会对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)2.函数的剩余参数
ES6中引用了rest parameter可以将不定数量的参数放入到一个数组中
如果最后一个参数是 ... 为前缀的那么它会将剩余的参数放到该参数中并且作为一个数组
function foo(m, n, ...args) {console.log(m, n)console.log(args)
}那么剩余参数和arguments有什么区别呢
剩余参数只包含那些没有对应形参的实参而 arguments 对象包含了传给函数的所有实参arguments对象不是一个真正的数组而rest参数是一个真正的数组可以进行数组的所有操作arguments是早期的ECMAScript中为了方便去获取所有的参数提供的一个数据结构而rest参数是ES6中提供并且希望以此来替代arguments的剩余参数必须放到最后一个位置否则会报错。
4.箭头函数的补充
在前面我们已经学习了箭头函数的用法这里进行一些补充
箭头函数是没有显式原型的所以不能作为构造函数使用new来创建对象
var foo () {}
console.log(foo.prototype)//undefined
var f1 new foo()// TypeError: foo is not a constructor// 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)//false
// 没有显式原型
console.log(bar.prototype)
var b new bar()七、其它知识补充
1.展开语法
展开语法(Spread syntax)
可以在函数调用/数组构造时将数组表达式或者string在语法层面展开还可以在构造字面量对象时, 将对象表达式按key-value的方式展开
展开语法的场景
在函数调用时使用在数组构造时使用在构建对象字面量时也可以使用展开运算符这个是在ES2018ES9中添加的新特性
注意展开运算符其实是一种浅拷贝
// 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/argumentsconst info {...obj,height: 1.88,address: 广州市
}
console.log(info)2.数值的表示
在ES6中规范了二进制和八进制的写法
const foo 0x100; // 十六进制
const bar 100; // 十进制
const ten 0o100; // 八进制
const bin 0b100; // 二进制另外在ES2021新增特性数字过长时可以使用_作为连接符
const num 100_000_000;八、symbol
1.symbol的基本使用
Symbol是什么呢Symbol是ES6中新增的一个基本数据类型翻译为符号。
那么为什么需要Symbol呢
在ES6之前对象的属性名都是字符串形式那么很容易造成属性名的冲突比如原来有一个对象我们希望在其中添加一个新的属性和值但是我们在不确定它原来内部有什么内容的情况下很容易造成冲突从而覆盖掉它内部的某个属性比如我们前面在讲apply、call、bind实现时我们有给其中添加一个fn属性那么如果它内部原来已经有了fn属性了呢比如开发中我们使用混入那么混入中出现了同名的属性必然有一个会被覆盖掉
Symbol就是为了解决上面的问题用来生成一个独一无二的值。
Symbol值是通过Symbol函数来生成的生成后可以作为属性名
也就是在ES6中对象的属性名可以使用字符串也可以使用Symbol值
Symbol即使多次创建值它们也是不同的Symbol函数执行后每次创建出来的值都是独一无二的
我们也可以在创建Symbol值的时候传入一个描述description这个是ES2019ES10新增的特性
2.Symbol作为属性名
我们通常会使用Symbol在对象中表示唯一的属性名
const s1 Symbol();// 方式1
const obj {}
obj[s1] zhangsan// 方式2
Object.defineProperty(obj, s1, {value: lisi
})// 方式3
const obj2 {[s1]: wangwu
}获取对象的symbol属性名
// 获取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.相同值的Symbol
前面我们讲Symbol的目的是为了创建一个独一无二的值那么如果我们现在就是想创建相同的Symbol应该怎么来做呢
我们可以使用Symbol.for方法来做到这一点
const key1 Symbol.for(abc);
const key2 Symbol.for(abc);
console.log(key1 key2);//true并且我们可以通过Symbol.keyFor方法来获取对应的key
Symbol.keyFor(key1)// abc