Javascript Advanced
JS advanced
this
函数调用时候,js会默认给this绑定一个值
this的绑定和定义位置(编写位置)没有关系
this的绑定和调用方式跟调用的位置有关系
this是再运行时被绑定
绑定规则
-
默认绑定
function setup() { console.log("setup", this) } // 直接调用 setup() // window // 对象调用 const obj = { name: 'baozi' } obj.setup = setup obj.setup() // obj const object = { name: 'baozi', setup: function () { console.log('object',this) } } object.setup() // object const launch = object.setup launch() // window // "use strict" // 严格模式下,独立调用函数中的this指向undefined // 高阶函数 function test(fn) { fn() // 独立调用 } test(object.setup) // window
-
隐式绑定
function setup() { console.log('setup', this) } const obj = { launch: setup } // 隐式绑定 obj.launch() // obj
-
new绑定
// new 创建一个新对象 步骤 // step1 将this指向空对象 // step2 执行函数体中的代码 // step3 没有显示返回非空对象时,默认返回这个对象 function setup() { this.name = 'baozi' console.log('setup',this) } new setup() // 空对象
-
显示绑定
function setup() { console.log('setup', this) } const obj = { name: 'baozi' } // 显示绑定 setup.apply(obj)// obj setup.call(obj)// obj
func.apply(thisArg,[argArray])
func.call(thisArg,arg1,arg2)
```js
function setup(height, age) {
console.log('setup', this)
console.log({ height, age})
}
const obj = {
name: 'baozi'
}
// 显示绑定
// param1 绑定参数
// param2 传入额外的实参,以数组的形式
setup.apply(obj,[188,20])
// param1 绑定参数
// param2 参数列表
setup.call(obj,188,20)
func.apply(thisArg,[argArray])
func.call(thisArg,arg1,arg2)
```js
function setup(height, age) {
console.log('setup', this)
console.log({ height, age})
}
const obj = {
name: 'baozi'
}
// 显示绑定
// param1 绑定参数
// param2 传入额外的实参,以数组的形式
setup.apply(obj,[188,20])
// param1 绑定参数
// param2 参数列表
setup.call(obj,188,20)
func.apply(thisArg,[argArray])
func.call(thisArg,arg1,arg2)
```js
function setup(height, age) {
console.log('setup', this)
console.log({ height, age})
}
const obj = {
name: 'baozi'
}
// 显示绑定
// param1 绑定参数
// param2 传入额外的实参,以数组的形式
setup.apply(obj,[188,20])
// param1 绑定参数
// param2 参数列表
setup.call(obj,188,20)
func.apply(thisArg,[argArray])
func.call(thisArg,arg1,arg2)
```js
function setup(height, age) {
console.log('setup', this)
console.log({ height, age})
}
const obj = {
name: 'baozi'
}
// 显示绑定
// param1 绑定参数
// param2 传入额外的实参,以数组的形式
setup.apply(obj,[188,20])
// param1 绑定参数
// param2 参数列表
setup.call(obj,188,20)
bind
func.bind(thisArg,arg1,arg2,...)
function setup(height,age) {
console.log('setup', this)
console.log({height,age})
}
const obj = {
name: 'baozi'
}
const launch = setup.bind(obj,188,20)
// 每次调用setup时,总是绑定到obj
launch()
-
内置函数调用绑定 根据经验
function setup() { console.log('setup', this) } setTimeout(setup,1000) // window , 显式绑定window
const chars = ['a','b','c']
// foreach 第二个参数可以显示绑定 this
chars.forEach(function(item) {
console.log(this)
},chars) // chars 显式指定
优先级
默认绑定优先级最低
显示绑定优先级高于隐式绑定
function setup() {
console.log('setup', this)
}
const starter = {
setup
}
// 比较优先级
starter.setup.apply('a')// Sting a
bind高于隐式绑定
function setup() {
console.log('setup', this)
}
const launch = setup.bind(1)
const starter = {
launch
}
starter.launch()// 1
new 绑定高于隐式绑定
const starter = {
launch: function setup() {
console.log('setup', this)
}
}
new starter.launch()// {}
new 不可以和 apply/call 一起使用
new 和bind new的优先级高于bind
function setup() {
console.log('setup', this)
}
const launch = setup.bind(2)
new launch() // {}
忽略绑定
function setup() {
console.log('setup', this)
}
setup.apply(null) // object
setup.call(undefined) // undefined
间接函数引用
const starter = {
name: 'starter',
setup: function setup() {
console.log('setup', this)
}
}
const starter2 = {
name: 'starter2'
}
starter2.setup = starter.setup
starter2.setup() // starter2
匿名函数
不会绑定this,arguments属性
不能作为构造函数使用(不能和new使用)
只会通过上层作用域寻找,代码块才有作用域,对象没有作用域
浏览器原理
渲染原理
网页解析过程
域名解析->静态资源->index.html->根据link和script请求下载css和js文件
浏览器内核WebKit
解析流程:LoadHtml->PraseHTML->LoadCss/PraseCSS->Attach->Create Dom Tree->Display
详细流程
defer和async
defer 下载的时候不会阻塞,浏览器继续解析html,Dom tree
执行代码,触发DOMContentLoader继续渲染。在defer代码中可以操作DOM
defer可以提高页面性能,推荐放到head中
async 不会阻塞,但是不能保证执行顺序。下载完成立即执行。
JS执行原理
JS执行过程
执行上下文(Execution Contexts)
js引擎在执行代码之前,会在堆内存中创建一个全局对象:Global Object(GO)
-
该对象所有的作用域都可以访问
-
包含Date、Array、String、Number、setTimeout等
-
包含一个window属性指向自己
js 内部有执行上下文栈(Execution Context Stack),用于执行代码的调用栈
VO对象(Variable Object)
每个执行上下文会关联一个VO对象,变量和函数声明会被添加到这个VO对象中。全局上下文中的VO就是GO
VO中函数先被声明。
函数代码执行流程
执行另一段代码,会形成新的执行上下文,关联一个VO对象,函数关联的叫AO
AO对象会使用arguments初始化(有值)初始值为传入的参数
作用域和作用域链
全局代码查找变量
var message = 'global' // top[scope]
function setup() { // [scope]
var message = 'setup'
function lanuch() { // [scope]
var message = 'lanuch'
console.log(message) // 先查找自己的作用域/找不到向上查找
}
return lanuch
}
var lanuch = setup()
lanuch()
当进入到一个执行上下文时,执行上下文也会关联一个作用域链
-
作用域链是一个对象列表,用于变量标识符求值
-
当进入一个执行上下文时,这个作用域链被创建,并且根据代码类型,添加一系列对象
-
全局代码的作用域链就是GO
-
函数被解析时已经确定。即在定义的时候已经确定作用域链。
-
当执行到函数的执行上下文,将作用域链中的值保存到自己的VO
作用域链的设计为了闭包。
内存管理和闭包
内存管理
代码执行过程中需要分配内存,某些语言需要手动管理内存,某些会自动管理内存。
JS的内存管理是自动的。JS引擎会处理好
Js在定义数据时分配内存
原始数据类型内存分配会在执行时直接在栈空间分配-
复杂数据类型内存分配会在堆中开辟空间,并这块空间的地址返回
垃圾回收GC
引用计数算法
对象中retainCount记录这块地址被引用的个数。当引用为0时释放对象。
弊端: 可能会产生循环引用
标记清除算法
核心思想是可达性
设置一个根对象(Root Object),垃圾回收器定期从根开始,找到从根开始有引用的对象,没用引用到的对象,就认为是不可用的对象
可以解决循环引用的问题
标记整理
回收期间会将保留的对象搬运到连续的内存空间,避免内存碎片化
分代收集
对象被分为两组:新的和旧的
长期存活的对象会变老旧,被检查的频次会减少
增量收集
将垃圾回收工作分为多个部分处理,逐一处理
闲时收集
在cpu空闲时,回收
闭包(Closure)
- 计算机科学中的闭包
支持头等函数的编程语言中,实现词法绑定的一种技术
闭包是一个结构体,它存储了一个函数和一个关联的环境
闭包实现:作用域链
- JS中的闭包
js定义的函数就是一个闭包(广义)
访问外层作用域的变量(狭义)
闭包的访问过程
function createAdder(count) {
function adder(num) { // 上层作用域找count
return count + num
}
return adder
}
var adder5 = createAdder(5)
adder5(10)
var adder8 = createAdder(8)
adder8(10)
闭包的内存泄露
function createAdder(count) {
function adder(num) { // 上层作用域找count
return count + num
}
return adder
}
var adder5 = createAdder(5)
adder5(10)
var adder8 = createAdder(8)
adder8(20)
// 内存泄漏:如果后面不再使用adder8,GC保留,造成内存泄漏
adder8 = null // 手动释放内存,下次GC就会回收
浏览器的优化
function setup() {
var name = 'setup'
var options = 'option'
function lanuch() {
console.log(name)
}
return lanuch
}
var fn = setup()
fn() // 闭包中没有用到的属性会被浏览器优化
函数增强
函数属性和arguments
function setup() {
}
setup.applay() // applay 就是属性
function setup() {
}
var lanuch = function() {
}
// 自定义属性
setup.title = 'setup'
// 默认函数对象就有自己的属性
// 1.name
console.log(setup.name)
// 2.length 获取参数本来接收的参数个数,剩余参数不会算入length中
setup(1,2,3)
arguments
function setup(_1,_2) {
// 类似数组(array-like)对象,是一个可迭代对象
// 不是数组类型,是对象类型
// 有特性,lenthg/index
// 没有数组中的一些方法 filter/map
console.log(arguments)
console.log(arguments[0])
for (const argu of arguments) {
console.log(argu)
}
// 获取参数中全部偶数,arguments没有filter方法,它不是一个数组
}
setup(1,2,3,4)
arguments 转 Array
-
遍历arguments,添加到新数组中
-
ES6中,Array.from(arguments), Array.from参数必须是一个可迭代对象
-
展开运算符[...arguments]
-
调用slice方法
[].slice.apply(arguments)
箭头函数不绑定arguments
函数的剩余参数rest
ES6中引入了rest parameter, 剩余参数需要写到最后。作为arguments的替代品
function setup(arg1, arg2, ...rest) {
console.log({arg1,arg2,rest})
// { arg1: 10, arg2: 20, rest: [ 30, 40 ] }
}
setup(10,20,30,40)
纯函数(Pure function)
相同的输入相同的输出
输入输出值以外的其他隐藏信息或状态无关
确定的输入,一定产生确定的输出
函数执行过程不能产生副作用
function setup(obj){
console.log(obj)
obj.name = 'obj' // side effect
}
var obj = {
age: 18
}
setup(obj)
数组slice和splice
var names = ['a','b','c','d','e']
names.slice(0,2) // 不修改原来数组 本质是调用this
names.splice(0,2) // 修改原数组
console.log(names)
柯里化Currying
把接收多个参数的函数,变成接收一个单一参数的函数,并且接受余下的参数,返回结果的新函数的技术
function setup(x, y, z) {
console.log( {x,y,z})
}
setup(10,10,10)
// 柯里化
// 柯里化函数
function enhanceSetup(x) {
return function(y) {
return function(z) {
console.log( {x,y,z})
}
}
}
enhanceSetup(20)(20)(20)
另一种写法:箭头函数写法
// 优化
var enhanceSetup2 = x=> y=> z=> console.log({x,y,z})
enhanceSetup2(30)(30)(30)
自动柯里化
function setup(x,y,z) {
console.log(x,y,z)
}
function sum(x,y) {
return x+y
}
// 自动柯里化
function transformCurring(fn) {
function curryFn(...rest) {
// 继续返回新的函数
// 执行fn函数
if (rest.length >= fn.length) {
// 参数足够,执行fn
return fn(...rest)
}else { // 执行第一类
return function(...newRest) {
// 保存原来的参数
return curryFn(...rest.concat(newRest))
}
}
}
return curryFn
}
var enhanceSetup = transformCurring(setup)
enhanceSetup(1)(1)(1)
组合函数Compose
var num = 100
function double(num) {
return num * 2
}
function pow(num) {
return num ** 2
}
// 将两个函数组合再一起生成一个新函数
function composeFn(num) {
return pow(double(2))
}
// 通用组合函数
function transformCompose(...fns){
// 边界判断
if(fns.length <= 0) return
for (const fn of fns) {
if(typeof fn !== 'function') {
throw new Error(`param ${fn} is not a function`)
}
}
function enhanceCompose(...args) {
var res = fns[0].apply(this,args)
for (var i = 1; i < fns.length; i++) {
var fn = fns[i]
res = fn.apply(this,[res])
}
return res
}
return enhanceCompose
}
var newFn = transformCompose(pow,double)
console.log(newFn(2))
with
扩展语句的作用域链
var obj = {
namea: 'obj'
}
with(obj) {
console.log(namea)
}
eval函数
可以将传入的字符串作为函数代码执行,将最后一行代码作为返回值
对象增强
对象属性操作的控制,使用属性描述符
Object.defineProperty
这个方法会直接在对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
Object.defineProperty( obj, prop ,description)
obj 定义属性的对象,prop修改的属性名或Symbol,description修改的属性描述符
属性描述符分类
configurable: 是否可以delete删除属性,或者修改它的特性,或者是否可以将它修改为存取属性描述符
enumerable:表示该属性是否可以通过for-in或者Object.keys返回该属性
writeable:表示是否可以修改属性的值 ,只读属性
value: 返回的value
存取属性描述符
set:
get:
拦截set和get
多个属性描述符
Object.defineProperties(obj,{})
原型和原型链
对象和函数的原型
对象的原型
对象中存在一个[[Prototype]]属性称为对象的原型,这个特殊的对象可以指向另外一个对象
浏览器会添加一个属性__proto__
可以获取到原型,浏览器可能未实现
也可以通过标准方法Object.getPrototypeOf(obj)获得
当通过[[get]]方式获取一个属性对应的value时,它优先在自己的对象中查找,查找到直接返回,没有找到则会在原型对象中查找
var obj = {
name: 'baozi',
title: 'hi'
}
console.log(obj.message)// undefined
obj.__proto__.message = 'hi coder'
console.log(obj.message)// 'hi coder'
函数对象的原型
function setup() {
}
// 函数看做普通对象时,也有__proto__(隐式原型)
console.log(setup.__proto__)
// 函数看做一个函数时,具备prototype,对象没有这个属性(显式原型)
// 作用:构建对象时,给对象设置隐式原型
console.log(setup.prototype)
new操作符
-
创建空对象
var obj = {}
-
将空对象赋值给this
this = obj
-
将函数的显式原型赋值给这个对象作为它的隐式原型
obj.__proto__ = Person.prototype
-
执行函数体中的代码
-
将对象默认返回
当多个实例化对象有共同的方法时,可以把该方法放到构造函数对象的显示原型上,所有实例化的对象都会共享该方法
function Student(name,number){
this.name = name
this.number = number
// 实例化会产生很多个函数对象
// this.happying = function() {
// console.log(this.name + 'happying')
// }
}
Student.prototype.running = function(){
console.log(this.name + ' running')
}
// stu1的隐式原型就是Student.prototype
var stu1 = new Student('lili',1)
// 查找,现在自己身上查找,没找到,到原型上查找
stu1.running() // this 隐式绑定
var stu2 = new Student('ming',2)
显式原型上的属性
函数的显示原型对象上有一个constructor属性指向函数
function Person() {
}
var PersonPrototype = Person.prototype
console.log(PersonPrototype.constructor === Person ) //true
重写原型对象
当需要在原型上添加过多的对象时,往往会重写原型对象
function Person() {
}
console.log(Person.prototype)
Person.prototype.message = 'message'
Person.prototype.info = 'info'
Person.prototype.running = function(){
}
// 直接赋值一个新的原型对象
// 但是没有Constructor,可以手动添加
Person.prototype = {
message: 'new message',
info: 'new info',
running: function(){
console.log('new running')
},
constructor: Person
}
直接重写后会把Constructor 的enumable变成true,用Objec.defineProperty()修改更好
继承
var info = {} // 对象字面量的本质
// 等价于
var info = new Object()
console.log(info.__proto__ === Object.prototype) // true
原型链
从一个对象上获取属性,如果在当前对象中没有获取到就会去他的原型上面获取
var obj = {
name: 'obj',
title: 'title'
}
// 查找顺序
// obj上面寻找
// obj.__proto__上面寻找
// obj.__proto__.__proto__ ->null 上面寻找 undefined
console.log(obj.message)
Object对象的隐式原型为null,是所有类的父类
原型链实现继承
不可以把父类的原型赋值给子类的原型
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.running = function () {
console.log('Running')
}
Person.prototype.eating = function () {
console.log('eating')
}
function Student(name,age,score) {
// 1.属性继承
this.name = name;
this.age = age;
this.score = score;
}
// 创建一个父类的实例对象,用这个实例对象作为子类的原型
var person = new Person('person',99)
Student.prototype = person
// 2.方法继承
// Student.prototype.running = function () {
// console.log('Running')
// }
// Student.prototype.eating = function () {
// console.log('eating')
// }
Student.prototype.studying = function () {
console.log('studying')
}
var stu1 = new Student('baozi',18,100)
stu1.running()
借用构造函数继承
function Student(name,age,score) {
// 借用构造函数
Person.call(this,name,age)
this.score = score;
}
以上是组合继承的实现。
组合继承总是会调用两次父类构造函数,子类有两份父类属性
原型式继承函数(寄生组合式)
function Person(name,age) {
}
Person.prototype.running = function () {
console.log('Running')
}
function Student(name,age,score) {
}
function createObject(obj) { // 接收父类显式原型
var _obj = {}
_obj.__proto__ = obj
return _obj
}
Student.prototype = createObject(Person.prototype)
var stu1 = new Student('baozi',18)
stu1.running()
方案二
Student.prototype = Object.create(Person.prototype)
function inherit(subClass,superType) {// 最终方案
subType.prototype = Object.create(superType)
}
function inherit(subClass,superType) { // 完善
subType.prototype = Object.create(superType)
// SubClass 是 子类函数
Object.defineProperty(subClass,"constructor",{
enumerable: false,
configurable: true,
writable: true,
value: subClass
})
}
ES6继承
Class类定义
添加到对象原型上的方法称为实例方法,添加到对象上的方法称为类方法
类使用
class Person {
constructor(name,age) {
console.log({name,age})
}
}
const p1 = new Person('baozi',1)
const p2 = new Person()
// 表达式
const Student = class {
}
const stu1 = new Student()
class类和function类的区别
function可以作为普通函数调用。
class不可以作为普通函数调用
class的访问器方法(access)
对象访问器方法
// 直接在对象中定义访问器
const obj = {
_name: 'baozi',
set name(value) { // setter
this._name = value
},
get name() {// getter
return this._name
}
}
类对象访问器方法
class Person {
constructor(name,age) {
this._name = name
this._age = age
}
set name(value) {
this._name = value
}
get name() {
return this._name
}
}
const p1 = new Person('baozi',18)
console.log(p1.name)
类的静态方法
加上static关键字
继承内置类
对内置类进行扩展。也可以直接对Array进行扩展,在Array的显式原型上扩展
类的mixins
Js只支持单继承
class Animal {
running(){}
}
function minxinAnimal(BaseClass) {
return class extends BaseClass {
running(){}
}
}
class newClass extends mininAnimal(Animal) {
}
Babel ES6转化为ES5
对象增强
计算属性名
[<变量>]: value
数组增强
数组解构
const names = [1,2,3,4,5]
const [name1,name2,name3] = names
手写apply/call/bind
function setup() {
console.log(this,'setup')
}
// setup 作为一个对象获取apply方法
// setup 的apply函数 来自于Function.prototype
setup.apply(1)
apply
function setup(aname) {
console.log(this,aname)
}
// setup 作为一个对象获取apply方法
// setup 的apply函数 来自于Function.prototype
// setup.apply(1,[1])
Function.prototype.myApply = function(_this,param = []) {
// 1.this.apply(_this)
// 2.隐式绑定实现
// _this.fn = this
// _this.fn()
// delete _this.fn
// 3.优化
// 边界 case 基本数据类型
_this = (_this === undefined || _this === null)? window : Object(_this)
Object.defineProperty(_this,'fn',{
configurable: true,
writable: false,
enumerable: false,
value: this
})
_this.fn(param)
delete _this.fn
}
setup.myApply({aname: 'baozi'},['myApply'])
bind
function setup(aname) {
console.log(this,aname)
}
// bind return 一个新函数
Function.prototype.myBind = function myBind(_this,...rest) {
// this setup 隐式调用
Object.defineProperty(_this,'fn',{
configurable: true,
enumerable: false,
writable: false,
value: this
})
return (...param) => {
_this.fn(...rest,...param)
}
}
const newFn = setup.myBind({aname: 'myBind'})
newFn('baozi')
ES6-ES13新特性
作用域提升
var声明的变量会有作用域提升,可以提前访问
let/const创建的变量并不是执行到这一行才被创建出来,当他们的词法环境被创建对的时候就已经创建,但是不能被提前访问。
暂时性死区(TDZ)
块作用域的顶部到变量被声明之前的区域为暂时性死区。
暂时性死区和定义的位置无关,和执行的顺序有关
function setup() {
console.log(message)
}
let message = 'hi'
setup()
let message = 'hi'
function setup() {
console.log(message)
let message = 'hello'
}
setup() // error 形成暂时性死区
let/const 定义的变量 不添加到window
会把定义的变量放到环境记录上,这个环境记录不等于window
let/const块级作用域
ES6之前只有全局和函数有自己的作用域
ES6开始let、const、function、class声明的变量有块级作用域
function浏览器做了特殊处理/访问过可以访问。
展开语法是浅拷贝
可以展开字符串,ES9可以展开对象对象要有可迭代属性
浅拷贝
深拷贝
JSON拷贝
缺点:不会拷贝函数
const obj = {
name: 'obj',
age: 18,
setup: {
name: 'setup',
}
}
const copiedObj = JSON.parse(JSON.stringify(obj))
手写
数值表示
// ES2021 数字过长可以用下划线分割
const money = 100_00_00_00000_00000
Symbol
是ES6中新增的基本数据类型、用于生成一个独一无二的值
对象的属性的key是string,可能会产生冲突。
当操作对象时,出现相同的属性,必然有一个会被覆盖
const s = Symbol('name')
const obj = {
[s]: 'object'
}
console.log(obj)
ES10中也可以在创建Symbol时传入一个description
Object.Keys()获取不到symbol的key
可以通过Object.getOwnPropertySymbols(obj)获取
通过调用Symbol生成的Symbol都是独一无二的
Symbol.for(Symbol.description)可以生成相同的Symbol
keyFor 可以获取Symbol的值
Set/Map数据结构
Set集合不能重复
Set是集合,元素不能重复。可以给数组去重
参数是可迭代对象
Set 属性 size 元素个数
-
add
-
delete
-
has 是否有这个元素
-
clear 清空set
-
forEach
const set = new Set()
// 添加元素
set.add(1)
WeakSet
Weak Reference 弱引用
可以获取到,但是不能保证GC会不会回收
只能放对象类型,不能放基本类型
对对象的引用都是弱引用
GC会对弱引用回收
WeakSet中的对象是没办法获取的
Map
当使用对象作为对象的key时,key都相同会覆盖。
WeakMap
key只能是对象,并且对key是弱引用会被GC回收
Object.entries
把对象key,value转化为一个数组
String Padding
padStart 前面尾数不足补齐
padEnd 后面尾数不足补齐
应用场景:对敏感数据格式化
flatMap
先进行map操作,再做flat操作
flatmap的深度是1
Object.fromEntiries
URLSearchParams
const searchString = "?name=why&age=18"
const param = new URLSearchParams(searchString)
console.log(param)// URLSearchParams { 'name' => 'why', 'age' => '18' }
param 存在get方法可以拿到值
param是可以遍历的。param有entries
把param转为对象
const paramObj = Object.fromEntries(param.entries())
TrimStart/TrimEnd
去除首部/尾部空格
FinalizationRegistry
提供一个方法,当在注册表的对象被回收时,请求在某个时间点上调用一个清理回调。
WeakRef
const setup = {
name: 'setup',
number: 1
}
let obj = new WeakRef(setup)
// deref 解析原来的对象,原来对象存在可以解析出来
// 原来对象被回收则不能解析出来
console.log(obj.deref())
词法环境
执行上下文有两个词法环境
LexicalEnvironment 处理let and const 声明的表示符
VariableEnvironment 处理var和function声明的标识符
Proxy-Reflect
Objec.defineProperty()做监听对象所有的属性时,不能监听整个对象,只能监听一个属性,不能监听新增/删除属性
Proxy创建一个代理对象,之后对该对象的操作,通过代理对象来完成。
const p = new Proxy(target,handler)
之后的操作直接操作代理对象
在handler捕获器中
- set函数有4个参数
target: 目标对象(监听的对象)
property: 将被设置的的属性key
value: 新属性值
receiver:调用的代理对象
-
get函数有三个参数
target: 目标对象
property: 被获取的属性key
receiver: 调用的代理对象
-
deleteProperty // 监听对象delete
target
key
- has // 监听in操作,判断属性是否在对象中
const obj = {
name: 'obj'
}
const objProxy = new Proxy(obj,{
set(traget,key,value) {
traget[key] = value
}
,
get(traget,key) {
return traget[key]
}
})
objProxy.name = 'setup'
console.log(objProxy.name)
监听函数对象的操作
function setup(...rest) {
console.log(this,rest)
}
const setupProxy = new Proxy(setup, {
apply: function(traget,_this,rest) {
traget.apply(_this,rest)
},
construct: function(traget,rest) { //监听new操作
return new traget(...rest)
}
})
setupProxy.apply({name:'setup'},[1,2])
console.log(new setupProxy(1,2,3))
Reflect的作用
reflect是一个对象,可以代替对象本身操作
它提供了很多操作JS对象的方法,类似于Object中操作对象的方法
对对象本身的操作,向Object对象上添加太多类方法,不是非常合适
把各种方法放到Reflect上
由Proxy和Reflect共同完成代理
代理对象的目的是不直接操作原对象
const obj = {
name: 'obj'
}
const objProxy = new Proxy(obj,{
set(traget,key,value,receiver) {
// traget[key] = value
// 通过代理进行操作, 不操作原对象
// 好处:以得知操作成功还是失败
const status = Reflect.set(traget,key,value)
if(!status) {
throw new Error(`set ${key} error`)
}
}
,
get(traget,key,receiver) {
return traget[key]
}
})
objProxy.name = 'setup'
console.log(objProxy.name)
Reflect可以设置Receiver(Proxy)
在拦截器拦截obj的时,需要加上Receiver可以让obj完全被监听到
Reflect.set(target,key,value,receiver)
Reflect Construct
function setup(name) {
this.name = name
}
function lanuch(name) {
setup.call(this,name) // 借用构造函数
}
const lan = Reflect.construct(setup,['a'],lanuch)
console.log(lan)
Promise
resolve(promise) 当前的promise的状态会由传入的Promise决定
resolve(thenable) 由传入的决定。
then的返回值
then方法的返回值也是一个新的Promise,链式中的then在等待新的Promise 状态之后执行
第二个Promise是第一个的返回值
then 方法 中return一个Promise等待这个Promise 决议,实现thenable方法,执行then方法。
catch方法的返回值
catch也会返回一个Promise对象
catch会被最近的promise reject执行
在then中抛出异常会被reject
finally方法
无论Promise的状态是rejected还是fulfilled都会执行。
Promise 类方法
Promise.resolve('hi')
当已经有了现有的内容,可以直接转为一个Promise
all方法
将多个Promise包裹在一起,形成一个新的Promise
新的Promise状态由包裹的所有Promise共同决定
当所有的Promise状态变成fulfilled状态时,新的Promise状态返回值组成一个数组
当有一个Promise状态为Rejected,新的Promise的状态为Rejected,并且将第一个rejected的返回值作为参数
const p1 = new Promise((resolve, reject) =>{
setTimeout(()=>{
resolve('p1 resolved successfully')
},3000)
})
const p2 = new Promise((resolve, reject) =>{
setTimeout(()=>{
resolve('p2 resolved successfully')
},2000)
})
const p3 = new Promise((resolve, reject) =>{
[
'p1 resolved successfully',
'p2 resolved successfully',
'p3 resolved successfully'
]
allSettled方法
all方法有缺陷,当有一个Promise变成rejected状态时,新的promise就会立即变成对应的rejected状态,这样获取不到Pedding的promise
allSettled会等到所有的Promise都有结果。结果是一个数组包含所有Promise的状态和值
race方法
如果有一个Promise有了结果,就决定最终的Promise状态
any方法
any方法会等到一个fulfilled状态,才会决定新Promise状态
Iterator-Generator
迭代器帮助我们对某个数据结构进行遍历的对象
Js 迭代器协议必须有一个next方法
next方法要求
-
一个无参数或者一个参数的函数,返回一个应当拥有一下两个属性的对象
-
done(Boolean)
-
value 具体值or undefined
// 给数组创建一个迭代器
const names = ['nickname', 'aaa','bbb','ccc']
let index = 0
const nameIterator = {
next() {
if(index<names.length) {
return {done: false, value: names[index++]}
}
return {
done: true
}
}
}
console.log(nameIterator.next())
console.log(nameIterator.next())
创建数组通用迭代器
const names = ['a', 'b', 'c', 'd', 'e', 'f']
const nums = [1,2,3,4,5,6,7,8,9,10]
function createArrayIterator(arr) {
let index = 0
return {
next(){
if(index< arr.length) {
return {
done: false,
value: arr[index++]
}
}
return {done: true, value: undefined}
}
}
}
const namesIterator = createArrayIterator(names)
const numsIterator = createArrayIterator(nums)
console.log(numsIterator.next())
可迭代对象
实现一个[Symbol.iterator] 函数,这个函数要返回一个迭代器用于迭代当前的对象
const obj = {
names: ['foo', 'bar'],
[Symbol.iterator](){
let index = 0
const nameIterator = {
next() {
if(index<obj.names.length) {
return {done: false, value: obj.names[index++]}
}
return {
done: true
}
}
}
return nameIterator
}
}
// 可迭代对象可以使用 for of操作
for (const o of obj) {
console.log(o)
}
迭代对象entries
const obj = {
name: 'obj',
desc: 'a Object',
[Symbol.iterator]() {
let index = 0
const entries = Object.entries(this)
const iterator = {
next(){
if(index < entries.length) {
return {done: false,value: entries[index++]};
}
return {done: true}
}
}
return iterator
}
}
for (const o of obj) {
console.log(o)
}
原生可迭代对象
String、Array、Set
应用场景
for of 、展开运算符、yield*、解构赋值
创建一些对象时:new Map([Iterable])、WeakMap、Set、WeakSet
一些方法调用时
Promsie.all(iterable)、Promise.race(iterable)、Array.form(iterable)
类的迭代
class Person {
constructor(name,age) {
this.name = name
this.age = age
}
// 实例方法
[Symbol.iterator]() {
let index = 0
const keys = Object.keys(this)
const iterator = {
next:() => {
if(index < keys.length) {
return {done: false, value: keys[index++]}
}
return {done: true}
}
}
return iterator
}
}
const p1 = new Person('baozi',18)
for (const key of p1) {
console.log(key)
}
生成器
生成器是ES6新增的一种函数控制、使用方案,它可以更加灵活的控制函数什么时候继续执行、暂停执行
生成器函数是一个函数、和普通的函数有一些区别
-
生成器函数需要在function的后面加一个符号 *
-
生成器函数可以通过yield关键字来控制函数的执行流程
-
生成器函数返回一个Gnerator(生成器)
生成器实际是一种特殊的迭代器
function* setup() {
console.log(1)
console.log('s')
yield
console.log(2)
yield
console.log(3)
}
// 代码执行被yield控制
// 返回一个生成器对象
// 当遇到yield中断执行,需要再次调用next继续执行代码
const generator = setup()
generator.next()
yield 可以返回结果
generator.next ==> {done: , value}
yield 'aaa'
==> {doen, value: aaa}
如果代码中间位置直接return
{done: true,value: }
函数调用参数
在yield前面接收参数
generator.next('baozi')
const name = yield "aaa"
第一次调用传入参在调用生成器函数时传入
generator.return 可以提前中断
generator.throw 可以向函数抛出一个异常
生成器替换迭代器
const names = ['ads','asd']
const nums = [1,2,3,4,5,6,7,8]
function* createArrayIterator(arr) {
for(let i = 0; i < arr.length; i++) {
yield arr[i]
}
}
const nameIterator = createArrayIterator(names)
for (const value of nameIterator) {
console.log(value)
}
const numsIterator = createArrayIterator(nums)
生成器生成一定范围的值
function* createRangeGenerator(start, end) {
for(let i = start; i < end; i++) {
yield i
}
}
const generator = createRangeGenerator(3,9)
生成器yield语法糖
yield* + 可迭代对象
function* createArrayIterator(arr) {
yield* arr
}
class Person {
constructor(name,age) {
this.name = name
this.age = age
}
// 实例方法
*[Symbol.iterator]() {
yield* Object.keys(this)
yield* Object.values(this)
yield* Object.entries(this)
}
}
异步
function* getData(){
const res1 = yield requestData('1')
const res2 = yield requestData('2'+ res1)
const res3 = yield requestData('3' + res2)
}
const generator = getData()
generator.next().value.then(res1=>{
generator.next(res1).value.then(res2=>{
generator.next(res2).value.then(res3=>{
generator.next(res3)
})
})
})
async await
async function getData(){
const res1 = await requestData('1')
const res2 = await requestData('2'+ res1)
const res3 = await requestData('3' + res2)
}
const generator = getData()
自动化执行generator next函数
async function getData(){
const res1 = await requestData('1')
const res2 = await requestData('2'+ res1)
const res3 = await requestData('3' + res2)
}
const generator = getData()
function autoGenFn(genFn) {
const generator = genFn()
function exec(res) {
const {done,value} = generator.next(res)
if(done) return
value.then(res => {
exec(res)
})
}
exec()
}
async 函数用于声明一个异步函数、异步函数返回一个Promise
异步函数中await关键字使用,await 后为一个Promise,当Promise的状态为fufilled状态才会继续执行
事件循环
Js是单线程的。浏览器又一个专门的线程来处理JavaScript代码
微任务和宏任务
Promise 中then的回调也会加入到队列中
宏任务队列
ajax、setTimeout、setInterval、Dom监听、UI rendering
微任务队列
Promise then的回调、Mutation Observer API、
执行新的宏任务之前、会先把微任务执行完。