目录
- 一、浏览器工作原理和V8引擎
- 二、JavaScript的执行过程
- 三、JS的内存管理和闭包
- 四、关于javascript中的this
- 五、函数式编程
- 六、with-eval-strict
- 七、js面向对象(字面量)
- 八、js面向对象(构造函数)
- 九、js面向对象(三大特性)
- 十、ES6类(class)
- 十一、ES6知识点讲解
- 十二、ES6新数据类型(Symbol)
- 十三、ES6新数据结构
- 十四、ES7~ES12
- 1、ES7-Array Includes
- 2、ES7-指数(乘方)exponentiation运算符
- 3、ES8-Object values
- 4、ES8-Object entries
- 5、ES8-String Padding
- 6、ES8-Trailing commas
- 7、ES10-flat和flatMap
- 8、ES10-Object fromEntries
- 9、ES10-trimStart trimEnd
- 10、ES11-BigInt
- 11、ES11-Nullish Coalescing Operator
- 12、ES11-Optional Chaining
- 13、ES11-Global This
- 14、ES12-FinalizationRegistry
- 15、ES12-logical assignment operators
一、浏览器工作原理和V8引擎
1、浏览器内核和js引擎的关系
* WebCore:负责HTML解析、布局、渲染等等相关的工作
* JavaScriptCore:解析、执行JavaScript代码
2、V8引擎的原理
* JavaScript源代码
- 词法分析:解析源代码中每个单词的类型、值等信息
- 语法分析:根据单词的类型信息可做语法分析,生成抽象语法树
* AST抽象语法树(格式固定的树结构对象。babel原理:ts -> ast -> js)
* MachineCode优化的机器码(多次执行的字节码函数会被标记为hot函数,hot函数会优化为固定的一组机器指令,
无需每次执行都将字节码函数转成机器指令,从而提高执行效率。hot函数优化生成的机器指令,会由于类型等原因
造成机器指令执行不正确,机器指令则会反向生成字节码函数,再由字节码函数转成机器指令执行,类型确定是ts执
行效率高于js的原因。)
* bytecode字节码
二、JavaScript的执行过程
1、全局代码执行和作用域提升
* 执行上下文栈(ECStack)
全局执行上下文(GEC)
变量对象(VO) == 全局对象(GO)
函数执行上下文(FEC)
变量对象(VO) == 激活对象(AO)
作用域链 == AO + 父级作用域 + GO
* 声明提升:还未进行变量赋值和函数执行的过程。声明变量:默认是undefined。声明函数:会在堆内存开辟空间
以存放函数代码块,再将内存地址赋给声明的函数。
* 延迟解析:将全局作用域下执行的函数进行预解析,非全局作用域下执行的函数(嵌套函数),在函数被调用时才会
全量解析,提高网页的运行效率。
* ES5函数作用域:作用域在函数内修改。
* var a = b = 10 等价 var a = 10; b = 10(未用var声明的变量,会成为GO的属性)。
* var a, b = 10 等价 var a; var b = 10
三、JS的内存管理和闭包
1、常见的GC算法
* 引用计数
* 标记清除
2、闭包
/**
* 高阶函数:当function的参数或者返回值也为函数时,则称这个function为高阶函数
* 闭包:函数+可以访问的自由变量
* 基本数据类型所占内存空间:小于2**32的整数占4字节32位,小数占8字节64位
*/
function foo() {
var name = "foo"
// 内层函数(bar代码块)未使用到的自由变量,js引擎会做优化将其销毁
var age = 18
function bar() {
console.log(name)
}
return bar
}
// 内层函数(bar代码块)被fn引用着,GC不处理则不释放内层函数(bar代码块)所占的内存空间
var fn = foo()
fn()
// 释放内层函数(bar代码块)所占的内存空间
fn = null
// 释放外层函数(foo代码块)所占的内存空间
foo = null
四、关于javascript中的this
1、this在全局作用域下
* 浏览器:this指向window
* Node环境:this指向空对象({})。Node环境会将.js文件作为一个module,放入一个函数中,
并使用.call({})方式执行这个函数。
2、this的四种绑定规则
* 默认绑定(fun()):独立函数调用this指向window
* 隐式绑定(object.fun()):object对象会被js引擎绑定到fun函数中的this里面
* 显示绑定:
- apply:fun.apply(object,[itemX]);
- call:fun.call(object,itemX);
- bind:var funBind = fun.bind(object); funBind();
* new绑定:function Person(name) { this.name = name; }
this指向函数作为构造器new出来的对象
* 优先级:new绑定 > 显示绑定(bind > call/apply) > 隐式绑定 > 默认绑定
3、this的其他补充(非箭头函数)
* setTimeout(fun):函数中this指向window
* div.onclick=fun:函数中this指向div
div.addEventListener("click",fun):函数中this指向div。
* 数组forEach/map/filter/find:有参数2则函数中的this指向参数2,没有则this指向window
* 忽略显示绑定:apply/call/bind参数传null/undefined相当于独立函数调用this指向window
* 间接函数引用:(obj1.fun=obj2.fun)() 是独立函数调用this指向window
* (object.fun)() 等价 object.fun() 是隐式绑定this指向object
4、箭头函数
* ()=>({}):只有一行代码并且返回一个对象的简写方式
* 箭头函数的this获取:箭头函数不绑定this,this从上层作用域获取。call/apply/bind绑定无效
5、call函数的实现
// 参数1:绑定的对象;参数2:剩余参数方式接收参数,args为数组
Function.prototype.mycall = function (bind, ...args) {
// 当xyz.mycall()则this指向xyz
var fun = this
// bind传入基本数据类型则要转成包装类型对象,传入null/undefined则为window对象
var rebind = (bind !== null && bind !== undefined) ? Object(bind) : window
rebind.fun = fun
// 展开运算符方式传入参数
var res = rebind.fun(...args)
delete rebind.fun
return res
}
function xyz(a, b) {
console.log(this)
return a + b
}
console.log(xyz.mycall({}, 1, 2))
6、类数组对象arguments
function fun(...args) {
// 知识点一、类数组对象:有length属性,可通过数组下标获取元素
console.log(arguments.length)
console.log(arguments[0])
// 指向函数体本身,严格模式("use strict";)下会报错
// console.log(arguments.callee)
// 知识点二、类数组对象转数组
// 方式1:
var arr1 = []
for (var i = 0; i < arguments.length; i++) {
arr1.push(arguments[i])
}
console.log(arr1)
// 方式2:
var arr2 = Array.prototype.slice.call(arguments)
console.log(arr2)
// 方式3:
var arr3 = [].slice.call(arguments)
console.log(arr3)
// 方式4:
var arr4 = Array.from(arguments)
console.log(arr4)
// 方式5:
var arr5 = [...arguments]
console.log(arr5)
// 知识点三、箭头函数不绑定arguments,从上层作用域获取,全局作用域没有arguments
var xyz = () => {
console.log(arguments)
}
xyz()
}
fun(1, 2, 3)
五、函数式编程
1、纯函数
* 相同的输入一定产生相同的输出
* 在执行的过程中不会产生任何的副作用
2、柯里化
* 柯里化的好处:对上层函数逻辑的复用
* 简化写法:var fun = x => y => z => x + y + z
3、通用柯里化函数的实现
function autoCurrying(fun) {
return function curried(...args1) {
// 函数的length属性表示可传参数的长度
if (args1.length >= fun.length) {
return fun.apply(this, args1)
} else {
return function (...args2) {
return curried.apply(this, args1.concat(args2))
}
}
}
}
function fun(time, level, message) {
console.log(`[${time.getHours()}:${time.getMinutes()}][${level}][${message}]`)
}
var curryingFun = autoCurrying(fun)
curryingFun(new Date())("DEBUG")("这是一段中文")
curryingFun(new Date(), "DEBUG")("这是一段中文")
curryingFun(new Date(), "DEBUG", "这是一段中文")
4、组合函数
* 组合函数:依次执行的多个函数组合在一起
5、通用组合函数的实现
function composeFun(...funs) {
var length = funs.length;
for (var i = 0; i < length; i++) {
if (typeof funs[i] !== "function") {
throw new TypeError("Expected arguments are functions")
}
}
return function (...args) {
var index = 0
var result = length ? funs[index].apply(this, args) : args
while (++index < length) {
result = funs[index].call(this, result)
}
return result
}
}
function fun1(a, b) {
return a + b
}
function fun2(c) {
return c * 2
}
var fun = composeFun(fun1, fun2)
console.log(fun(1, 2))
六、with-eval-strict
1、with
var obj = {message: "obj"}
function fun() {
var message = "fun"
/**
* with语句:可以形成自己的作用域
* 作用域链:传入的对象 -> 父级作用域 -> GO
* 严格模式下会报错,不推荐使用
*/
with (obj) {
console.log(message)
}
}
fun()
2、eval
* eval可以将传入的字符串当做javascript代码来运行
* 不推荐使用:代码可读性差;字符串易被篡改,有被攻击的风险;js引擎无法优化
3、严格模式
* 开启严格模式:
- 作用整个文件:文件第一行加"use strict";
- 作用某个函数:函数第一行加"use strict";
* 严格模式常见的限制
- 禁止意外创建全局变量
- 不允许函数有相同的参数名称
- 静默错误
- 不允许使用原先的八进制格式 0123
- with语句不允许使用
- eval函数不会向上引用变量了
- 在严格模式下,自执行函数(默认绑定)会指向undefined
- setTimeout(fun,delay)的this,fun.apply(this = window)
七、js面向对象(字面量)
1、创建对象的方式
* 通过new Object()创建:var obj = new Object();
* 字面量形式:var obj = {}
2、对象属性的操作
var obj = {
name: "黄婷婷",
age: 18
}
// 1、获取属性的值
console.log(obj.name)
// 2、设置属性的值
obj.name = "孟美岐"
// 3、删除属性
// delete obj.age
console.log(obj)
// 4、遍历属性
for (const objKey in obj) {
console.log(objKey)
}
Object.keys(obj).forEach(objKey => {
console.log(objKey)
})
3、defineProperty方法(数据属性描述符)
var obj = {
name: "黄婷婷"
}
/**
* 未使用属性描述符定义的属性("name"属性),也具备对应的特性:
* value: 赋值的value
* writable: true
* configurable: true
* enumerable: true
*/
Object.defineProperty(obj, "address", {
// 默认值undefined
value: "无锡市",
// 默认值false。该特性表示"address"属性不可赋值(写入值)
writable: false,
// 默认值false。该特性表示"address"属性不可删除(严格模式下会报错),也不可重新定义属性描述符
configurable: false,
// 默认值false。该特性表示"address"属性不可枚举
enumerable: false
})
obj.address = "上海市"
delete obj.address
console.log(Object.keys(obj))
console.log(obj)
4、defineProperty方法(存取属性描述符)
var obj = {
// 下划线开头的属性常用来表示私有的属性
_address: "",
_age: 18,
// configurable默认true,enumerable默认true
get age() {
return this._age
},
set age(value) {
this._age = value
}
}
// get/set不可与数据属性描述符的value/writable同时使用
/**
* 作用一、隐藏某一个私有属性,不希望直接被外界使用和赋值
* 作用二、如果我们希望截获某一个属性它访问和设置值的过程时,也会使用存取属性描述符
*/
Object.defineProperty(obj, "address", {
configurable: true,
enumerable: true,
get: function () {
return this._address
},
set: function (value) {
this._address = value
}
})
console.log(obj)
5、定义多个属性描述符
var obj = {
_age: 18
}
Object.defineProperties(obj, {
name: {
value: "黄婷婷",
writable: true,
configurable: true,
enumerable: true
},
age: {
configurable: true,
enumerable: true,
get: function () {
return this._age
},
set: function (value) {
this._age = value
}
}
})
6、补充说明Object的方法
var obj = {
name: "黄婷婷",
age: 18
}
// 1、获取对象某一个属性的属性描述符
console.log(Object.getOwnPropertyDescriptor(obj, "name"))
// 2、获取对象所有属性的属性描述符
console.log(Object.getOwnPropertyDescriptors(obj))
// 3、禁止对象添加新的属性
Object.preventExtensions(obj)
obj.address = "无锡市"
console.log(obj)
// 4、禁止对象配置/删除里面的属性
Object.seal(obj)
delete obj.name
console.log(obj)
// 5、让属性不可修改(writable:false)
Object.freeze(obj)
obj.name = "孟美岐"
console.log(obj)
八、js面向对象(构造函数)
1、创建对象方案(工厂模式)
// 缺点:获取不到对象最真实的类型
function createPerson(name, age) {
var person = {}
person.name = name
person.age = age
return person
}
var person = createPerson("黄婷婷", 18)
2、认识构造函数
function Person() {
}
// 通过new关键字去调用一个函数,那么这个函数就是一个构造函数了
var person = new Person(); // 无需传参时小括号可省略
3、创建对象方案(构造函数)
// 规范:构造函数的首字母一般是大写
function Person(name, age) {
this.name = name
this.age = age
}
/**
* 如果一个函数被使用new操作符调用了,那么它会执行如下操作:
* - 1、在内存中创建一个新的对象(空对象)
* var 空对象 = {}
* - 2、构造函数内部的this,会指向创建出来的新对象
* this = 空对象
* - 3、这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性
* this.__proto__ = Person.prototype
* - 4、执行函数的内部代码(函数体代码)
* Person()
* - 5、如果构造函数没有返回非空对象,则返回创建出来的新对象
*/
var person = new Person("黄婷婷", 18);
// 获取对象类型(构造函数名)
console.log(person.__proto__.constructor.name)
4、构造函数缺点
function Person() {
this.eating = function () {
}
}
// 缺点:每次new对象eating方法都会创建一个函数,比较浪费内存空间
var person = new Person();
5、对象的原型(隐式原型)的理解
var obj = {}
/**
* 每个对象中都有一个[[prototype]],这个[[prototype]]可以称之为对象的原型(隐式原型)
* 1、获取原型对象:
* - obj.__proto__
* - Object.getPrototypeOf(obj)
* 2、原型的作用:当我们从一个对象中获取某一个属性时,它会触发[[get]]操作。1、在当前对象中去查
* 找对应的属性,如果找到就直接使用;2、如果没有找到,那么会沿着它的原型去查找[[prototype]]
*/
console.log(obj)
6、函数的原型(显示原型)的理解
/**
* 函数也是一个对象(new Function())
* 函数作为对象来说,它也是有[[prototype]]隐式原型的
* console.log(Person.__proto__)
*/
function Person() {
}
var p = new Person()
// 函数它因为是一个函数,所以它还会多出来一个显示原型属性:prototype
console.log(p.__proto__ === Person.prototype)
7、构造函数、原型对象、实例对象
原型对象.constructor -> 构造函数
构造函数.prototype -> 原型对象
实例对象.__proto__ -> 原型对象
new 构造函数() -> 实例对象
8、自定义构造函数的原型对象
function Person() {
}
// 1、Person.prototype重新赋值
Person.prototype = {
name: "黄婷婷"
}
// 2、Person.prototype的constructor属性指向Person构造函数(不可枚举)
Object.defineProperty(Person.prototype, "constructor", {
value: Person,
writable: true,
configurable: true,
enumerable: false
})
console.log(Person.prototype)
9、创建对象方案(构造函数+原型对象)
function Person(name) {
// 1、属性定义在构造函数中
this.name = name
}
// 2、方法定义在原型对象上
Person.prototype.sleep = function () {
console.log(this.name + " 睡觉")
}
var person = new Person("孟美岐");
person.sleep()
九、js面向对象(三大特性)
1、原型链直接继承的弊端
function Person(name, friends) {
this.name = name
this.friends = friends
}
var person = new Person("林志玲", []);
function Student(age) {
this.age = age
}
Student.prototype = person
// 弊端三:在前面实现类的过程中都没有传递参数
var student = new Student(18);
// 弊端一:打印student对象,继承的属性是看不到的
console.log(student)
// 弊端二:创建两个Student类型对象
// 直接修改对象上的属性,是给本对象添加了一个新属性
student.name = "佟丽娅"
// 获取引用,修改引用中的值,会相互影响
student.friends.push("鞠婧祎")
2、借用构造函数继承的弊端
function Person(name, friends) {
this.name = name
this.friends = friends
}
var person = new Person("", []);
function Student(age, name, friends) {
Person.call(this, name, friends)
this.age = age
}
Student.prototype = person
var student = new Student(18, "林志玲", []);
// 弊端一、Person函数至少被调用了两次
// 弊端二、student的原型对象上会多出一些属性,但是这些属性是没有存在的必要
3、父类原型对象赋值给子类原型对象继承的弊端
function Person(name, friends) {
this.name = name
this.friends = friends
}
function Student(age, name, friends) {
Person.call(this, name, friends)
this.age = age
}
// 弊端:操作子类原型对象,会影响父类原型对象
Student.prototype = Person.prototype
4、原型式继承(对象继承对象)
var obj = {
name: "孟美岐"
}
// 方式一
// var info = createObject1(obj);
function createObject1(o) {
var newObj = {}
// 相当于newObj.__proto__ = o(可以这么理解,不能这么做)
Object.setPrototypeOf(newObj, o)
return newObj
}
// 方式二
// var info = createObject2(obj);
function createObject2(o) {
function Fn() {
}
Fn.prototype = o
var newObj = new Fn()
return newObj
}
// 方式三
var info = Object.create(obj);
5、寄生式继承
var personObj = {
running: function () {
console.log("running")
}
}
function createStudent(name) {
var stu = Object.create(personObj)
stu.name = name
stu.studying = function () {
console.log("studying~")
}
return stu
}
var student = createStudent("黄婷婷");
6、寄生组合式继承
// 相当于Object.create()
function createObject(o) {
function Fn() {
}
Fn.prototype = o
return new Fn()
}
// 2、用于继承方法(配合原型三角图理解,不用考虑父类实例)
function inheritPrototype(SubType, SuperType) {
SubType.prototype = createObject(SuperType.prototype)
Object.defineProperty(SubType.prototype, "constructor", {
value: SubType,
writable: true,
configurable: true,
enumerable: false
})
}
function Person() {
}
function Student() {
// 1、用于继承属性
Person.call(this)
}
inheritPrototype(Student, Person)
7、补充说明对象的方法
var obj = {
name: "孟美岐",
age: 18
}
var info = Object.create(obj, {
address: {
value: "北京市",
enumerable: true
}
})
// 1、hasOwnProperty:对象是否有某一个属于自己的属性(不是在原型上的属性)
console.log(info.hasOwnProperty("address"))
console.log(info.hasOwnProperty("name"))
// 2、in/for in 操作符:判断某个属性是否在某个对象或者对象的原型上
console.log("address" in info)
console.log("name" in info)
for (var key in info) {
console.log(key)
}
// 3、instanceof:用于检测构造函数的prototype,是否出现在某个实例对象的原型链上
console.log(obj instanceof Object)
// 4、isPrototypeOf:用于检测某个对象,是否出现在某个实例对象的原型链上
console.log(obj.isPrototypeOf(info))
8、Function、Object
* function fun() {} 等价 var fun = new Function("")
* 所有(不考虑继承)构造函数的__proto__都指向Function的原型对象
Function.__proto__ -> Function.prototype
Object.__proto__ -> Function.prototype
* 除了Object原型对象,所有(不考虑继承)原型对象的__proto__都指向Object的原型对象
Function.prototype.__proto__ -> Object.prototype
Object.prototype.__proto__ -> null
* 所有构造函数的原型对象的constructor属性,都指向构造函数本身
Function.prototype.constructor -> Function
十、ES6类(class)
1、类与构造函数
/**
* 类是构造函数的语法糖,与构造函数的本质没有区别
*/
// 类的声明
class Person {
}
// 类的表达式
var Animal = class {
}
console.log(typeof Animal)// function
2、类的构造器
class Person {
// 类的构造器
// 注意:一个类只能有一个构造器
// 1、在内存中创建一个对象 moni = {}
// 2、将类的原型prototype赋值给创建出来的对象 moni.__proto__ = Person.prototype
// 3、将对象赋值给函数的this:new绑定 this = moni
// 4、执行函数体中的代码
// 5、自动返回创建出来的对象
constructor(name, age) {
this.name = name
this.age = age
}
}
3、类中的方法
class Person {
// 1、类的构造器方法
constructor(name) {
this.name = name
this._address = "无锡市"
}
// 2、类的实例方法
// Person.prototype.sleep
sleep() {
console.log(this.name + ":睡觉")
}
// 3、类的访问器方法
// 存取属性描述符
get address() {
return this._address
}
set address(address) {
this._address = address
}
// 4、类的静态方法(类方法)
// Person.study
static study() {
}
}
4、类的继承
class Person {
constructor(name) {
this.name = name
}
study() {
}
static eat() {
}
static sleep() {
}
}
// Student称之为子类(派生类)
class Student extends Person {
/**
* * JS引擎在解析子类的时候要求,如果我们有实现继承,那么子类的构造方法中,
* 在使用this之前必须调用super()
* * super的使用位置:构造器方法、实例方法、静态方法
*/
constructor(name, age) {
super(name)// Person.call(this)
this.age = age
}
// 重写实例方法:子类可重写父类的实例方法
study() {
super.study()// Person.prototype.study.call(this)
}
// 重写静态方法:子类可重写父类的静态方法
static eat() {
super.eat()// Person.eat()
}
}
// 静态方法继承:Student.__proto__.sleep = Person.sleep
Student.sleep()
5、ES6转ES5
* Babel中文网:https://www.babeljs.cn/
* /*#__PURE__*/:纯函数标记。webpack打包压缩(tree-shaking)的时候,
如果发现 /*#__PURE__*/ 标记的函数没有调用,则会将函数直接删除。
6、继承内置类
class MyArray extends Array {
firstItem() {
return this[0]
}
}
var arr = new MyArray("黄婷婷", "孟美岐", "周洁琼");
console.log(arr.firstItem())
7、类的混入mixin
class Person {
}
function mixinRunner(BaseClass) {
return class extends BaseClass {
running() {
console.log("running~")
}
}
}
function mixinEater(BaseClass) {
return class extends BaseClass {
eating() {
console.log("eating~")
}
}
}
class NewPerson extends mixinEater(mixinRunner(Person)) {
}
var np = new NewPerson();
np.eating()
np.running()
8、JavaScript中的多态
/**
* * 多态:当对不同的数据类型执行同一个操作时,如果表现出来的行为(形态)不一样,那么就是多态的体现
* * 传统的面向对象多态是有三个前提:
* - 必须有继承(是多态的前提)
* - 必须有重写(子类重写父类的方法)
* - 必须有父类引用指向子类对象
*/
function sum(a, b) {
return a + b
}
// 同一操作,数据类型不同(Number、String),行为不同(数字相加、字符串拼接),所以也是多态的体现
sum(1, 2)
sum("z", "y")
十一、ES6知识点讲解
1、字面量增强的写法
var attr = "name"
var age = 18
var obj1 = {
age: age,
sleep: function () {
}
}
obj[attr] = "黄婷婷"
var obj2 = {
// 1、property shorthand(属性的简写)
age,
// 2、method shorthand(方法的简写)
sleep() {
},
// 3、computed property name(计算属性名)
[attr]: "黄婷婷"
}
2、解构Destructuring
// 一、数组的解构
var names = ["黄婷婷", "孟美岐", "姜贞羽"]
// 1、对数组的解构
// var item1 = names[0], item2 = names[1], item3 = names[2];
var [item1, item2, item3] = names
console.log(item1, item2, item3)
// 2、解构后面的元素
var [, , itemz] = names
console.log(itemz)
// 3、解构出一个元素,后面的元素放到一个新数组中
var [itemx, ...newNames] = names
console.log(itemx, newNames)
// 4、解构的默认值
var [itema, itemb, itemc, itemd = "佟丽娅"] = names
console.log(itemd)
// 二、对象的解构
var obj = {name: "黄婷婷", age: 18, gender: "女"}
// 1、基本
var {gender, name} = obj
console.log(name, gender)
// 2、解构默认值
var {address: newAddress = "无锡市"} = obj
console.log(newAddress)
3、let/const基本使用
* const声明一个只读变量并非真正的常量,对象的属性或数组的长度是可变的
* let/const不能重复声明
* let/const不存在变量提升。块级作用域中声明变量之前变量无法访问这种现象称之为暂时性死区
* let/const声明的全局作用域变量不属于window对象
* 块级作用域(代码块、if、switch、for)只对let/const/function(不兼容)/class声明的类型有效。
for (let i = 0; i < 2; i++) {} 的执行过程:{let i = 0} {let i = 1}
4、模板字符串
function myTag() {
console.log(arguments)
}
// 标签模板字符串,函数第一个参数是字符串值的数组,其余的参数与表达式相关
myTag`黄${'hello'}婷${'world'}婷`
5、默认参数值
/**
* 1、有默认值的形参最好放到最后。默认值会改变函数的length的个数,
* 默认值以及后面的参数都不计算在length之内了
* 2、面试题注意点:当函数的参数有默认值时,会形成一个新的作用域,
* 这个作用域用于保存参数的值(参数作用域是函数作用域的上一级)
*/
function multiply(a, b = 1) {
}
6、剩余参数
/**
* 1、剩余参数可将不定数量的参数放入到一个数组中
* 2、剩余参数只包含那些没有对应形参的实参,而arguments对象包含了传给函数的所有实参
* 3、剩余参数必须放到最后一个位置,否则会报错
*/
function foo(a, ...b) {
console.log(arguments)
}
7、箭头函数
* 箭头函数是没有显示原型的,所以不能作为构造函数,使用new来创建对象
* 箭头函数中不绑定this、arguments
8、展开语法的使用
* 1、函数调用时,foo(...arr)
* 2、构造函数时,[...arr]
* 3、构建对象字面量时,{...obj}
* 补充:展开运算符其实进行的是一个浅拷贝
9、表示数值的方式
const num1 = 100// 十进制
// b -> binary
const num2 = 0b100// 二进制
// o -> octonary
const num3 = 0o100// 八进制
// x -> hexadecimal
const num4 = 0x100// 十六进制
// 大的数值的连接符(ES2021 ES12)
const num = 10_000_000
十二、ES6新数据类型(Symbol)
// 一种新的原始数据类型,表示独一无二的值,最大的用法是用来定义对象的唯一属性名
// 1、基本使用
let aaa = Symbol('黄婷婷')
let bbb = Symbol('黄婷婷')
console.log(Symbol())// Symbol()
console.log(aaa.description)// 黄婷婷
console.log(typeof aaa)// symbol
console.log(aaa === bbb)// false
// 2、Symbol.for(key)/Symbol.keyFor(symbol)
let ccc = Symbol.for('孟美岐')
let ddd = Symbol.for('孟美岐')
console.log(ccc === ddd)// true
console.log(Symbol.keyFor(ddd))// 孟美岐
// 3、设置对象的属性为Symbol类型
// 不能通过obj.symbol语法获取值
let obj = {
[aaa]: '张婧仪'
}
obj[bbb] = "鞠婧祎"
Object.defineProperty(obj, ccc, {
value: "姜贞羽",
writable: true,
configurable: true,
enumerable: true,
})
// 4、获取对象中属性为Symbol类型的集合
console.log(Object.keys(obj))// []
console.log(Object.getOwnPropertyNames(obj))// []
// [Symbol(黄婷婷), Symbol(黄婷婷), Symbol(孟美岐)]
console.log(Object.getOwnPropertySymbols(obj))
十三、ES6新数据结构
1、Set
// 1、创建Set
const set = new Set()
// 2、数组转Set会去重
const arr = ["黄婷婷", "姜贞羽", "孟美岐"]
const set1 = new Set(arr)
// 3、Set转数组
const arr1 = Array.from(set1);
const arr2 = [...set1];
// 4、Set的属性
console.log(set1.size)
// 5、Set的方法
set1.add("张婧仪")
set1.delete("姜贞羽")
set1.has("孟美岐")
// set1.clear()
// 6、遍历Set
set1.forEach(item => {
console.log(item)
})
for (const item of set1) {
console.log(item)
}
2、WeakSet
/**
* 1、强引用与弱引用的概念
* - 强引用(Set):GC不会回收
* - 弱引用(WeakSet):GC会回收
*/
const weakSet = new WeakSet();
// 2、只能存放对象类型
weakSet.add({name: "黄婷婷"})
// 3、没有.clear()方法
// 4、不能遍历
// 5、应用场景
const pwset = new WeakSet();
class Person {
constructor() {
pwset.add(this)
}
running() {
if (!pwset.has(this)) throw new Error("不能通过其他对象调用running方法")
console.log("running", this)
}
}
3、Map
// 1、JavaScript中对象的key只能是String/Symbol类型
const ojb = {}
// 2、Map允许对象类型作为key
const map1 = new Map();
const map = new Map([["name", "黄婷婷"], ["age", 18], ["gender", "女"]]);
// 3、Map方法
console.log(map.size)
map.set("address", "无锡市")
console.log(map.get("name"))
console.log(map.has("address"))
console.log(map.delete("address"))
// map.clear()
// 4、遍历
map.forEach((value, key) => {
})
for (const entry of map) {
}
for (const [key, value] of map) {
}
4、WeakMap
const obj = {name: "黄婷婷"}
// 1、WeakMap的key只能是对象类型
// 2、key是弱引用
const weakMap = new WeakMap();
// 3、常见方法
weakMap.set(obj, "中国人")
console.log(weakMap.get(obj))
console.log(weakMap.has(obj))
weakMap.delete(obj)
// 4、不能遍历
5、WeakMap应用场景(vue3响应式原理)
const obj1 = {
name: "why",
age: 18
}
function obj1NameFn1() {
console.log("obj1NameFn1被执行")
}
function obj1NameFn2() {
console.log("obj1NameFn2被执行")
}
function obj1AgeFn1() {
console.log("obj1AgeFn1")
}
function obj1AgeFn2() {
console.log("obj1AgeFn2")
}
const obj2 = {
name: "kobe",
height: 1.88,
address: "广州市"
}
function obj2NameFn1() {
console.log("obj2NameFn1被执行")
}
function obj2NameFn2() {
console.log("obj2NameFn2被执行")
}
// 1、创建WeakMap
const weakMap = new WeakMap();
// 2、收集依赖结构
// 2.1、对obj1收集的数据结构
const obj1Map = new Map();
obj1Map.set("name", [obj1NameFn1, obj1NameFn2])
obj1Map.set("age", [obj1AgeFn1, obj1AgeFn2])
weakMap.set(obj1, obj1Map)
// 2.2、对obj2收集的数据结构
const obj2Map = new Map();
obj2Map.set("name", [obj2NameFn1, obj2NameFn2])
weakMap.set(obj2, obj2Map)
// 3、如果obj1.name发生了改变
// Proxy/Object.defineProperty
obj1.name = "james"
const targetMap = weakMap.get(obj1);
const fns = targetMap.get("name");
fns.forEach(item => item())
十四、ES7~ES12
1、ES7-Array Includes
const arr = ["黄婷婷", "孟美岐", "张婧仪", NaN]
/**
* searchElement:需要查找的元素
* fromIndex?:从该索引处开始查找
*/
// arr.includes(searchElement, fromIndex)
console.log(arr.includes(NaN, 0))// true
2、ES7-指数(乘方)exponentiation运算符
// Math.pow(3,3)
const result = 3 ** 3
console.log(result)// 27
3、ES8-Object values
const obj = {
name: "黄婷婷",
age: 18,
gender: "女"
}
console.log(Object.values(obj))
4、ES8-Object entries
const obj = {
name: "黄婷婷",
age: 18,
gender: "女"
}
const arr = ["黄婷婷", "孟美岐", "周洁琼"]
const str = "张婧仪"
// [["name", "黄婷婷"], ["age", 18], ["gender", "女"]]
console.log(Object.entries(obj))
// [["0", "黄婷婷"], ["1", "孟美岐"], ["2", "周洁琼"]]
console.log(Object.entries(arr))
// [["0", "张"], ["1", "婧"], ["2", "仪"]]
console.log(Object.entries(str))
5、ES8-String Padding
const str = "张婧仪"
const msg = str.padStart(6, "*").padEnd(9, "+");
console.log(msg)// ***张婧仪+++
6、ES8-Trailing commas
// 允许最后一个参数加逗号
function fun(a, b,) {
}
fun(1, 2,)
7、ES10-flat和flatMap
const arr = [1, [2], [[3]]]
// 1、flat:递归遍历数组,参数不传默认值是1
console.log(arr.flat())// [1, 2, [3]]
console.log(arr.flat(2))// [1, 2, 3]
// 2、flatMap:相当于先map遍历,再flat(1)
const arr1 = ["黄婷婷,张婧仪", "孟美岐,周洁琼", "姜贞羽,吴雨诗"]
const arr2 = arr1.flatMap(item => {
return item.split(",")
})
// ['黄婷婷', '张婧仪', '孟美岐', '周洁琼', '姜贞羽', '吴雨诗']
console.log(arr2)
8、ES10-Object fromEntries
const obj = {
name: "黄婷婷",
age: 18,
gender: "女"
}
const entries = Object.entries(obj);
// 1、键值对列表转对象
const fromEntries = Object.fromEntries(entries);
// {name: '', age: 18, gender: '女'}
console.log(fromEntries)
// 2、应用场景
const query = "name=孟美岐&age=19&gender=女"
const urlSearchParams = new URLSearchParams(query);
const oQuery = Object.fromEntries(urlSearchParams);
// {name: '孟美岐', age: '19', gender: '女'}
console.log(oQuery)
9、ES10-trimStart trimEnd
const str = " 黄婷婷 "
console.log(str.trimStart().trimEnd())// 黄婷婷
10、ES11-BigInt
// 1、ES11之前最大数
const maxsafeinteger = Number.MAX_SAFE_INTEGER;
console.log(maxsafeinteger)// 9007199254740991
// 2、ES11表示大数
const bigint = 9007199254740991n;
// 3、Number和BigInt类型不同不能相加
const bigint1 = bigint + 1024n
console.log(bigint1)// 9007199254742015n
// 4、BigInt转Number,可能会损失精度
const number = Number(9007199254742015n);
console.log(number)// 9007199254742016
// 5、Number转BigInt
const bigint2 = BigInt(number);
11、ES11-Nullish Coalescing Operator
// 空值合并运算 ??
const foo = undefined
const bar2 = foo ?? "default value"
// 相当于
let bar
if (foo === undefined || foo === null) {
bar = "default value"
} else {
bar = foo
}
12、ES11-Optional Chaining
// 可选链
const obj = {}
console.log(obj.friend?.name)// undefined
13、ES11-Global This
/**
* 全局对象
* - 浏览器:globalThis === window
* - node:globalThis === global
*/
console.log(globalThis)
14、ES12-FinalizationRegistry
// 1、FinalizationRegistry对象可以让你在对象被垃圾回收时请求一个回调
const finalRegistry = new FinalizationRegistry(value => {
console.log(`注册在finalRegistry的对象,${value}被销毁了`)
})
let obj = {name: "黄婷婷"}
// 2、WeakRef对象允许您保留对另一个对象的弱引用,而不会阻止被弱引用对象被GC回收
let info = new WeakRef(obj)
finalRegistry.register(obj, "obj")
obj = null
setTimeout(() => {
// 原对象未被销毁则返回原对象,已销毁则返回undefined
console.log(info.deref()?.name)
}, 10000)
15、ES12-logical assignment operators
// 1、||=逻辑或赋值运算
let msg = false
msg = msg || "default value"
// 相当于
msg ||= "default value"
console.log(msg)// default value
// 2、&&=逻辑与赋值运算
let msg1 = true
msg1 = msg1 && "default value"
// 相当于
msg1 &&= "default value"
console.log(msg1)// default value
// 3、??=逻辑空赋值运算
let msg2 = ""
msg2 = msg2 ?? "default value"
// 相当于
msg2 ??= "default value"
console.log(msg2)// 空字符串