大于 默认的this大于 隐式绑定的this大于 隐式绑定的this大于 bind绑定的优先级大于 apply、call绑定的this备注:
function foo() {console.log(this)
}
// 显示绑定的优先级高于隐式绑定
// 1.1 apply,call 高于默认绑定
let obj = {foo:foo}
foo.call(obj) // {foo: foo} // bind绑定高于默认绑定
let bar = foo.bind('aaa')
let obj = {name:'zd', baz:bar}
obj.baz() // aaa// new绑定的优先级高于隐式绑定let obj = {name: "zs",baz: foo,};obj.baz(); // {name: 'zs', baz: ƒ}new obj.baz(); // {}// new绑定的优先级高于隐式绑定
// bind 会返回一个新的函数let bindFn = foo.bind("aaa");bindFn(); // aaanew bindFn(); // {}// bind绑定的this 高于 apply、call绑定的thislet bindFn = foo.bind("aaa");bindFn(); // aaabindFn.call("bbb"); // aaabindFn.apply("bbb"); // aaa
function foo() {console.log(this);
}foo.call(null); // window对象
foo.call(undefined); // window对象
foo.call("q"); // 包装类 String{q} 就是字符串q//
let obj1 = {name: "obbj1",foo: function () {console.log(this);},
};let obj2 = {};
console.log((obj2.foo = obj1.foo)); // foo函数
// 因为赋值过程的返回值是当前函数,直接调用就是全局调用,this指向window
(obj2.foo = obj1.foo)(); // this指向window
箭头函数不会绑定this和arguments和super
箭头函数没有显式原型
箭头函数不能用作构造函数(因为没有显示原型)
如果函数体只有一行代码,默认值返回的是一个对象,必须用括号将对象包裹
// 如果不用括号包裹 () => {} 这样js引擎解析不出来
[1,2,3].map(item => ({a:1,b:2}))
箭头函数中没有this绑定
// 那为什么再箭头函数中可以使用this
// 因为再当前作用域中没有会向上一级作用域找
let foo = () => {console.log(this)
}
// 正常情况下,bind绑定this -> aaa
// 但是fn的this是window,并没有替换成aaa
// 由此可见 箭头函数中没有this
let fn = foo.bind('aaa')
fn() // window对象 arguments转换成数组
foo(1,2,3,4)
function foo() {let arr;//方法0 遍历// 方法一arr = Array.from(arguments)// 方法二arr = [...arguments]// 方法三// slice返回的是截取后的数组,没有参数,截取全部arr = [].slice.apply(arguments)
}
函数式编程中有一个非常重要的概念叫纯函数,JavaScript符合函数式编程的范式
纯函数的维基百科定义
纯函数相同的输入值时,产生相同的输出输入和输出值意外的其他隐藏信息或状态无关,也和由I/O设备产生的外部输出无关不能有语义上可观察的函数副作用,诸如,‘触发事件’,使输出设备输出,或更改输出值以外的内容等// 调用这个函数,比如输入10,20,结果是30
// 那么在任何地方foo(10,20) 值都是30
function foo(num1,num2) {return num1 + num2
}
foo(10,20) //30
foo(10,20) //30// 以下的情况,如果num发生了变化,那么输出的结果也会变化,不符合纯函数的定义,所以就不是纯函数
var num = 100
function foo(num1, num2) {return num1 + num2 + num
}foo(10,20) //130
num = 200
foo(10,20) // 230
总结纯函数
确定的输入,一定会产生相同的输出函数在执行过程中,不能产生副作用执行一个函数时,除了返回函数值外,还对调用函数产生了附加影响,比如修改了全局变量,修改参数挥着改变外部的存储维基百科解释:
自己的理解:
传递给函数一部分参数来调用它,让他返回一个函数去处理剩余的参数,这个过程就称之为柯里化function foo(x,y,x) {return ...
}
foo(10,20,30)// 将上面的函数转化成下面的函数的过程就是柯里化function foo(x) {return function(y) {return function(z) {return ...}}
}
foo(10)(20)(30)
let foo = x => y => z => console.log(x,y,z)
foo(1)(2)(3) // 1,2,3
function autoCurrying(fn) {return function currying(...arg) {if (arg.length >= fn.length) {return fn(...arg);} else {return function (...newArr) {console.log(arg);return currying(...arg.concat(newArr));};}};}function foo(x, y, z) {// console.log(x + y + z);console.log(x + y + z);}let autoFoo = autoCurrying(foo);autoFoo("aa")("bb")("cc"); // aabbcc
函数的使用技巧、模式// 有以下场景,首先对一个数取平方,再乘以2,我们需要用到两个函数才能完成,显得有些麻烦
function dobule(num) {return num * 2
}
function pow(num) {return num ** 2
}console.log(dobule(pow(2))) // 16// 将其转换成组合函数
function composeFn(num) {return dobule(pow(num))
}
console.log(composeFn(2)) // 16
function compose(...fns) {// 1. 边界判断let len = fns.length;if (!len) return;for (let i = 0; i < len; i++) {let fn = fns[i];if (typeof fn !== "function")throw new Error(`index positon ${i} must be function `);}//return function (...args) {// 因为做了边界判断,所以第一项一定存在// 如果直接遍历不取第一项的值,那么每次遍历传的值都是一样的,取了第一次的结果存起来,下次的参数就是这个结果let result = fns[0].apply(this, args);for (let i = 1; i < len; i++) {let fn = fns[i];// 参数加[]是因为apply调用的,参数必须是数组,使用apply可以指定this 更加灵活result = fn.apply(this, [result]);}return result;};
}function foo(num) {return num * 2;
}function fn(num) {return num ** 2;
}function fn1(num) {return num ** 2;
}let newFn = compose(foo, fn, fn1);
console.log(newFn(2)); // 256

介绍:
第一次确定节点的大小和位置称为布局(layout)回流引起回流的情况
介绍:
引起重绘的情况
回流一定会引起重绘,所以回流是一件很消耗性能的事情
合成层(compositeLayers),并且新的图层可以利用 GPU来加速绘制 常见的可以从形成新的合成层的属性:
分成确实可以提高性能,但是他以内存管理为代价,因此不应该最为web性能优化策略的一部分过度使用
上述的原因
保持正确的顺序执行不能保证顺序,他是独立下载,独立运行,不会等待其他脚本消除一些原有的静默错误让js引擎再执行代码时可以进行更多的优化(不需要对一些特殊的语法进行处理)禁用了再ECMAScript未来版本中可能定义的一些语法开启严格模式后
数据属性
存储属性
使用存储属性对对象的属性进行操作
// 1. 使用Object.defineProperty给obj添加一个name的属性
// 2. 外界访问name 其实就是访问_name的值,修改也是_name的值
// 因为return this.name
let obj = {_name: 'zs'}Object.defineProperties(obj, {_name: {enumerable: false,configurable: false},name: {get() {// 不能直接返回name 不然会造成死循环return this._name},set(val) {this._name = val},enumerable: true,configurable: false}}) 注意:对象中是不能用对象.prototype访问的,对象的原型称为隐式原型
触发[[Get]]的操作首先会检查该对象中是否有对应的属性,有的话就使用,没有就访问[[property]]中的获取对象原型的方式
__proto__属性可以获取到(但是这是早期浏览器自己添加的,存在一定的兼容性)Object.getPrototypeOf方法获取函数中的原型叫做显示原型
.prototype来获取原型Object.getPrototypeOf方法获取__proto__属性可以获取到(但是这是早期浏览器自己添加的,存在一定的兼容性)使用new操作符的时候,将函数的显示原型赋值给对象的隐式原型,这样对象就可以使用了
寄生组合式继承(思想)
// 函数作用: 创建一个空对象,对象的隐式原型指向ofunction createObject(o) {function F() {}F.prototype = o;return new F();}// 继承的函数function inherit(subType, superType) {// 如果适配早期的浏览器,那么就不能使用Object.crate()// subTyp.prototype = Object.create(superType.prototype);subType.prototype = createObject(superType.prototype);Object.defineProperty(subType.prototype, "constructor", {enumerable: false,configurable: true,writable: true,value: subType,});}// 实现继承function Person(name, age) {this.name = name;this.age = age;}Person.prototype.say = () => console.log("say Person");function Student(name, age, score) {Person.call(this.name, age);this.score = score;}inherit(Student, Person);let s1 = new Student("jack", 19, 98);console.log(s1);
自己的属性(不是在原型上的属性)某个属性是否在对象或者对象的原型上检测构造函数的property,是否出现在某个实例对象的原型链上 class Person {constructor(name, age) {this._name = name;this.age = age;}
// 原型方法say() {console.log("sss");}}
set和get除了可以在Object.defineProperty中使用,还可以直接在对象中使用(不常用)
let obj = {name1: "zd",_age: 18,get age() {return this._age;},set age(val) {this._age = val;},}; 所以在类中也可以使用set和get
以下案列的好处是,当要获取坐标,都是成对出现的,我们还需要进行处理(写一个方法…)
使用get就不用写方法,直接调用position属性即可
class Rectangle {constructor(x, y, width, height) {this.x = x;this.y = y;this.width = width;this.height = height;}get postion() {return { x: this.x, y: this.y };}get size() {return { width: this.width, height: this.height };}}let rect1 = new Rectangle(10, 20, 100, 200);console.log(rect1.position); // {"x": 10,"y": 20}console.log(rect1.size); // {"width": 100,"height": 200} 就是ES6之前的类方法
通常用于定义直接使用类来执行的方法,不需要有类的实例,使用static关键字来定义
// 静态方法的this也是指向他的调用者,通常就是类本身
class Foo {constructor() {}static say() {console.log("我睡觉哦");console.log(this);}
}
Foo.say(); extends来实现继承super关键字 必须要先使用super才能使用this(将super()放到constructor代码块的首行),否则会报错子类的构造方法、实例方法、静态方法class Person {constructor(name, age) {this.name = name;this.age = age;}say() {console.log(`${this.name} is say`);}drink() {console.log(`${this.name} is drank`);}
}class Student extends Person {constructor(name, age, sno) {super(name, age);this.sno = sno;}drink() {super.drink();}
}let s1 = new Student("zs", 18, 1001);
s1.say();
s1.drink();
console.log(s1);
// 有running和flayer两个类
// bird类想要继承这两个类// class Running {
// run() {
// console.log('running~')
// }
// }
// class Flayer {
// flay() {
// console.log('flay~')
// }
// }// 使用mixin混入的思想
// return class extends basicClass
// 表示 返回一个新的继承basicClass的类
// 外界接收到这个值就是一个新类并且继承了basicClasss类
function mixinRunning(basicClass) {return class extends basicClass {run() {console.log("running~");}};
}function mixinFlayer(basicClass) {return class extends basicClass {flay() {console.log("flayer~");}};
}class Bird {eat() {console.log("eating~");}
}// 方案1
// let NewBird = mixinRunning(mixinFlayer(Bird));// 方案2
class NewBird extends mixinRunning(mixinFlayer(Bird)) {}
let bird1 = new NewBird();
console.log(bird1);
维基百科定义:
多态(polymorphism)指为不同数据类型的实体提供统一的接口,或用一个单一的符号来表示多个不同的类型
总结:不同数据类型进行同一个操作,表现的行为是不同的,这就是多态
// 为不同数据类型的实体提供统一的接口
function sum(a1, a2) {return a1 + a2
}
// 调用了同一个接口,但是返回的结果的数据类型却不一样
// 符合多态的定义
sum(10,20)
sum('qq', 'nn')// 用一个单一的符号来表示多个不同的类型
// foo可以是任意类型的,也符合多态的定义
var foo = 1
foo = 'aa'
foo = []
一定存在多态的let name = 'zs'
let obj = {name,age: 18
}
let obj = {drink:function() {}say() {}
}
//
let key = 'address'let obj = {[key]: '河南'
}console.log(obj) // {address: '河南'}
// 采用下标对下标的方式进行解构
// 如果不想声明下标是2的变量,直接空出去,后面的继续写
let arr = [1,2,3,4]
let [c1,c2, ,c3] = arr
console.log(c1,c2,c3) // 1,2,4// 拓展练习,解构出数组
// 利用了解构和剩余参数的语法
let [c1, c2, ...c3] = arr
console.log(c1,c2,c3) // 1,2,[3,4]// 解构的默认值
// 如果解构对应的值是undefined, 变量的值就是默认值
let [c1='ad',c2,c3='ss',,c4='de'] = arr
// c1 = 1 c4 = de
let obj = {name: 'zs',age: 18
}let {name,address:wAddress='hhh'} = obj
// wAddress = hhh// 对象解构配合剩余参数
let obj = {name: 'zs',age: 19,address: '河南'
}
let {name,...info} = obj
// name='zs' info = {age:19, address:'河南'}
英文:TDZ(temporal dead zone)社区给的定义,官方没有这个定义
从块级作用域的顶部一直到变量声明完成之前,这个变量处在暂时性死区
暂时性死区和定义的位置没有关系,和代码的执行顺序有关系
function foo() {console.log(message)
}
let message = 'zs'
foo() // zs 暂时性死区形成后,再该区域内这这个标识符不能够被访问
let message = 'zs'
function foo() {console.log(message)let message = 'aa'
}
foo() // 报错 在块级作用域内用let或const声明的变量外界无法访问
但是在块级作用域内声明的函数在外界允许访问,但是只能在声明之后才能访问
foo() // 报错
{let a = 123function foo() {}
}
console.log(a) // 报错
foo() b = 123
a = `${b} + ddd`
//a 123 + ddd
react中会用,暂做了解
// function foo(...arg) {console.log(arg);}foo`hahha is ${123} dog ${222}.banner {color:red;}`;
// 打印结果// [// [// "hahha is ",// " dog ",// "\n .banner {\n color:red;\n }\n ",// ],// 123,// 222,// ];
有默认参数的形参尽量写在后面
有默认参数的形参,是不会计算在length之内的
默认参数之后的所有参数都不参与计算在length之内
function foo(age,name=123,height=2) {}
foo.length // 1 剩余参数也是放到最后面,但是要放到默认参数之前
function foo(name='zs',age=18) {console.log(name,age)
}
foo() // zs,18
const obj = {name:'zs'}
const {name = 'jack'} = obj// 原始写法
// 当形参是一个对象的时候,设置默认值
function foo(obj = {name:'zs'} ) {console.log(obj.name)
}
// 优化
function foo({name} = {name: 'zs'} ) {console.log(name)
}
// 进一步优化
function foo({name = 'zs'} = {} ) {console.log(name)
}
foo()
函数调用时使用数组构造时使用构建对象字面量时,也可以使用展开运算符,ES9(ES2018)新添加的 对象字面量的形式才可以使用// 基本使用
let obj = {name: 'zs'
}
let info = {...obj}let arr = [1,2,3]
let arr1 = [...arr,9] // [1,2,3,9]let hello = 'hello'
let arr2 = [...hello]
基本数据类型,翻译为符号。Symbol出现的原因:
在ES6之前,对象的key只能是字符串,很容易造成属性名的冲突
比如传入一个函数的参数是一个对象,函数体中对对象添加一个属性,那么如果传入的对象中已经有函数代码块中同名的key那么就会进行覆盖
function foo(obj) {obj.name = 'zs'
}
foo({name:'jack'}) Symbol就是为了解决这一问题的,用来生成一个独一无二的值
const s1 = Symbol()
const obj = {[s1]: 'li'
}
obj[Symbol()] = 'sss' Symbol即使创建多次,他们也是不同的
也可以在创建Symbol值的时候传入一个描述description(ES2019新增的特性)
const s1 = Symbol('我是描述哈哈') 获取用Symbol属性名的值
Object.getOwnPropertySymbol()获取const s1 = Symbol()
const obj = {[s1]: 'hhh'
}
let symbolKeys = Object.getOwnPropertySymbol(obj)
获取两个相同的Symbol
const s1 = Symbol()
const s2 = Symbol()
consolel.log(s1 === s2) //trueconst s3 = ('sdfs')
console.log(Symbol.keyFor(s3)) // sdfs
数据不能重复可迭代的元素常见的属性和方法
返回Set对象本身返回Boolean类型返回Boolean类型没有返回值const s1 = new Set([1,2,3])
s1 // {1,2,3}
弱引用和强引用
强引用(strong reference):
设有个一变量obj,他的值是{name:‘zs’},当js的垃圾回收(GC)运行时,根据可达性的原理,发现{name:‘zs’}这个对象被obj变量所引用着,那么就不会将其清理掉,这就是强引用
弱引用(weak reference):
设有一个变量obj,他的值是{name:‘zs’},虽然他也被obj引用着,但是通过别的方式将这个引用变成弱引用了,那么当GC运行时,就会忽略这个引用,然后{name:‘zs’}这个对象就可能被回收
WeakSet就是弱集合的意思,也就是说他的引用的值,就是弱引用
WeakSet和Set的区别
只能存放对象类型,不能存放基本数据类型对元素的引用是弱引用,如果他引用的值没有被其他变量引用,那么GC可以对该引用的值进行回收WeakSet是弱引用,如果我们遍历获取其中的元素,那么可能造成对象不能正常的销毁cosnt arr = [{name: 'zs'}]
const weakS = new weakSet(arr)
任意类型常见的属性和方法
并返回整个Map对象返回Boolean返回Booleanconst m1 = new Map([['建','值'],[1,2]
])
key只能使用对象,不接受其他类型作为keykey对对象的引用是弱引用,如果没有其他引用引用这个对象,那么GC可以回收该对象不支持遍历const obj1 = {name: 'zs'}
const wm = new WeakMap()
wm.set(obj1, 123)
查询mdn
求一个数的多少次方,用**表示
Math.pow(2,3)
2 ** 3
获取对象中所有对值
const obj = {name: 'zs',age: 19
}
const val = Object.values(obj)
// ['zs', 19] 会存放可枚举属性的键值对的数组const obj = {name: 'zs',age: 19
}
const ent = Object.entries(obj)
// ent [ ["name", "zs"], ["age", 19] ]
for (let item of ent) {const [key,value] = itemconsole.log(key,value)
}// 如果是一个数组
console.log(Object.entries([1,2,3]))
// [['0', 1], ['1', 2], ['2', 3]]// 如果是字符串
console.log(Object.entries('aa'))
//[['0','a'],['1','a']]
如果小于这个长度将用padString进行填充// 格式化日期
const hh = "3".padStart(2, "0");
const mm = "15".padStart(2, "0");
console.log(`${hh}:${mm}`);
// 03:15// 加密身份证或者银行卡
const cardNum = '411989199901011234'
const lastFour = cardNum.slice(-4)
const resNum = lastFour.padStart(cardNum.length, '*')
// 更多案例查询mdn文档
const nums = [1,2,[1,2],[[1,2]]]
const flatNum = nums.flat(1)
console.log(flatNum) // [1,2,1,2,[1,2]]
两步操作 map操作flat(1)的操作const msg = ["hello world", "hello zs"];
const newMsg = msg.flatMap((item) => item.split(" "));
console.log(newMsg); // ['hello', 'world', 'hello', 'zs']
const obj = {name: 'zs',age: 19
}
let objEntries = Object.entries(obj)
// [ ['name', 'zs'],['age', 19] ]
let transObj = Object.fromEntries(objEntries)
// {name: 'zs', age: 19}
const str = ' hello world'
const trimS = str.trimStart()
// 'hello world'
const str = ' hello world '
const trimS = str.trimEnd()
// ' hello world'
Number.MAX_SAFE_INTEGER的数值,表示的可能不正确BigInt即可,在要表示的数后面添加nconsole.log(Number.MAX_SAFE_INTEGER)
// 9007199254740991
console.log(9007199254740991 + 1) // 9007199254740992
console.log(9007199254740991 + 2) // 9007199254740992// 使用BIGINT
console.log(9007199254740991n + 2n) // 9007199254740993
console.log(9007199254740991n + 5n) // 9007199254740996
??,如果??前面的值是null或undefined,那么就使用??后面的值let info = undefined
info = info ?? '默认值'
// info 默认值
undefinedconst obj = {
name:'zs'
}
obj?.asy?.()
globalThis当一个在注册表中的对象被垃圾回收时,请求在某个时间点上调用一个清理回调(清理回调有时被称为finalizer)调用register方法,注册热河想要清理回调的对象,传入该对象和所含的值// register的第二个参数,自定义标识符
// 如果有第二个参数,那么在执行回调的时候,参数就是传入的自定义标识符
let obj = [{ name: "zs" }];
let info = { a: 123 };
const finalization = new FinalizationRegistry((val) => {console.log("对象被回收了", val);
});finalization.register(obj, "obj");
finalization.register(info, "info");obj = null;// 检测WeakMap是否会被GC删除
// 结果会调用回调,证明会被GC回收
// 如果将WeakMap换成Map就不会执行回调
let obj = { name: "123" };
let wp = new WeakMap();wp.set(obj, 123);const finalization = new FinalizationRegistry(() => {console.log("first");
});
finalization.register(obj);
obj = null;
let obj = { name: "zs" };
let wr = new WeakRef(obj);let finalization = new FinalizationRegistry((val) => {console.log("对象被回收", val);
});finalization.register(obj, "obj");// 此时obj指向的对象已经被回收了,
obj = null;
function foo(message) {// 1.||逻辑运算符// message = message || '默认值'// message ||= '默认值'// 2. ?? 逻辑运算符message = message ?? '默认值'message ??= '默认值'console.log(message)
}
foo(0) // 0
foo() // 默认值let obj = {name:'zs'
}
obj = obj && obj.name
obj &&= obj.name
const num = 123_0000_0000
class Person {// 所有实例都可有这个属性height = 1.88constructor() {}// 类的静态属性static address = '河南'// 类的静态私有属性, 只有类内部能够访问// 用this.访问static #number = 12// 类的静态代码块static {console.log('只有类初始化时候执行一次')}
}
一个对象进行代理,之后对该对象的所有操作都是通过代理对象来完成的const obj = {name: "zs",age: 19,
};// target表示被代理的源对象
// key 被查看、操作的对象的属性
// val 被修改的新值
// receiver 被代理的对象
const proxyObj = new Proxy(obj, {set(target, key, val, receiver) {console.log(`监听了${key},值是${val}`);target[key] = val;},get(target, key) {console.log(`监听了${key}`);return target[key];},deleteProperty(target, key) {delete target[key];},has(target, key) {console.log(`${key}被判断是否存在${target}中`);return key in target;},
});proxyObj.name = "jack";
proxyObj.address = "河南";
proxyObj.age;
delete proxyObj.address;
console.log("age" in proxyObj);
反射Object对象中对 对象本身进行操作的API迁移到了Reflect上,并且又新增了一些别的方法Reflect和Object的区别
对 对象本身的操作如何设计会更加规范,所以将API放到了Object上Object作为一个构造函数这些操作实际放到它身上并不合适类似于in、delete操作符让js看起来会有一些奇怪新增了Reflect,让我们这些操作都集中到了Reflect对象上不操作原对象const obj = {name: "zs",age: 19,
};
// 返回 对象本身
console.log(Object.defineProperty(obj, "name", {configurable: false,})
);
// 返回Boolean值 false上面设置了configurable:false
console.log(Reflect.defineProperty(obj, "name", {enumerable: false,})
);// 都返回Boolean 但是后者的书写的语义更加明确
console.log(delete obj.name); // false
console.log(Reflect.deleteProperty(obj, "name")); // false
好处三const obj = {name: "zs",age: 19,
};Reflect.defineProperty(obj, "name", {configurable: false,writable: false,
});
// 使用Reflect对对象进行操作,可以进行间接操作对象
// 可以返回一个Boolean值
// 如果对象的属性被保护起来了,那么可以判断操作是否成功
// 避免了严格模式下报错
const proxyObj = new Proxy(obj, {set(target, key, newVal, receiver) {let isConfig = Reflect.set(target, key, newVal, receiver);// let isConfig = target[key];console.log(isConfig);if (!isConfig) {console.log(`${key}属性不支持该操作`);}},
});
proxyObj.name = "河南";
console.log(proxyObj);
案例好处三
const obj = {_name: "zs",set name(newVal) {this._name = newVal;},get name() {return this._name;},
};// Reflect的receiver参数表示
// set/get(obj中的set/get) 方法执行的时候的this// 如果没有传入receiver,那么obj中的set在调用的时候只监听了一次
// 当调用proxyObj.name的时候其实是调用了两次set方法
// 第一次是调用了proxyObj的set,然后执行了Reflect.set()
// 调用了obj的set方法,但是此时obj的set中的this指向的是obj
// 操作源对象,proxyObj无法监听到,所以只监听了一次// 如果传入receiver,
// 当执行Reflect.set(),时候,obj的this已经变成了proxyObj这个代理对象
// this._name其实就是proxyObj._name
// 那么又会执行proxyObj的set方法,所以就可以监听两次// get方法和上述同理// 如果不使用Reflect的方法操作对象,那么也只会监听一次,因为obj的this就是指向obj本身// 所以 如果想要监听两次操作,就使用Reflect,如果不在乎的话,也可以使用target[key]let proxyObj = new Proxy(obj, {set(target, key, newVal, receiver) {console.log("设置了值");Reflect.set(target, key, newVal, receiver);},get(target, key, receiver) {console.log("获取了值");return Reflect.get(target, key, receiver);},
});proxyObj.name = "123";
proxyObj.name;
调用resolve回调函数时,会执行Promise对象的then方法传入的回调函数调用reject回调函数时,会执行Promise对象的catch方法传入的回调函数这个回调函数会被立即执行,并且传入两个参数(Executor是传入的回调的叫法)状态一旦被确定下来,就不会更改,也不能在执行某一个回调函数来改变状态 调用了resolve或者reject,就不能在调用这两个中的任意一个,调用了也没有反应一个普通的值或者对象,那么这个值将作为then方法的回调的参数另外一个Promise,那么这个新Promise会决定原来的Promise的状态对象,并且这个对象中有then方法,那么会执行then方法,并根据then方法的结果来决定promise的状态const p = new Promise((resolve, reject) => {resolve("p的resolve");
});
const promise = new Promise((resolve, reject) => {// 1. resolve() 普通的值// resolve(123);// 2. resolve() 一个promise, 这个新的promise会决定原promise的状态// resolve(p);// 3. resolve() 一个带有then方法的对象resolve({name: "22",then(resolve, reject) {// resolve("hhh");reject("ss");},});
});promise.then((val) => console.log(`结果:${val}`)).catch((err) => console.error(err));
const promise = new Promise((reslove, reject) => {reslove('成功的回调')// reject('失败的回调')
})promise.then(() => {})
promise.then(() => {})promise.then(res => {}, err => {})
返回值也是一个Promise,所以可以支持链式调用// then方法返回一个新的promise,这个新的promise的决议(resolve/reject)是等到then方法传入的回调函数有返回值时进行决议的
const promise = new Promise((resolve, reject) => {resolve("aaa");// reject("ss");
});// then方法中的返回值的类型(见上面的10.3)
// 普通值
// 新的Promise
// thenable对象(对象中有then方法)
promise.then((res) => {console.log(res); // aaareturn "hhh";// return {// then(resolve, reject) {// reject(0.11);// },// };
}).then((res) => {console.log(res); // hhh
});// then方法相当于做了以下的操作
// 所以如果then方法中没有返回值,那么下个then方法中的结果就是undefined
function then(cb) {let res = cb();return new Promise((resolve, reject) => {resolve(res);});
}
const promise = new Promise((reslove, reject) => {reject('失败的回调')// reject('失败的回调')
})promise.reject(() => {})
promise.reject(() => {})
返回值也是一个Promise,所以可以支持链式调用const promise = new Promise((resolve, reject) => {reject("ee");
});// catch方法也会返回一个新的Promise
// catch方法默认返回undefined
// 如果写了return 或者默认的返回,那么决议就是resolve,链式调用的下一级就会进入到then方法中
// 所以默认情况下不能一直.catch().catch()调用
// 如果想使用.catch.catch,那么可以在每个catch中抛出一个异常,这样就可以一直被catch所捕获
promise.catch((err) => console.log(err)) //ee.then((res) => console.log(res)) // undefined.then((res) => console.log(res)); // undefinedconst promise1 = new Promise((resolve, reject) => {resolve("ee");
});// 如果有promise的决议是reject的,那么会找到最近的catch方法进行调用
// 所以下面代码会在执行第一个then后,直接执行catch方法
// 使用throw 或者 return一个新的promise(决议是reject)都会进入catch中
promise1.then((res) => {console.log(res, "asa");throw new Error("报错");
}).then((res) => console.log(res)).catch((err) => console.log(err));// catch方法相当于做了以下的操作
function catch1(cb) {let res = cb();return new Promise((resolve, reject) => {resolve(res);});
}
无论Promise对象无论变成fulfilled还是rejected状态,最终都会被执行的代码new promise(resolve => resolve())的语法糖new promise((resolve,reject) => reject())的语法糖将多个Promise包裹在一起形成一个新的Promise新的Promise状态由包裹的所有Promise共同决定 所有的Promise状态变成fulfilled状态时,新的Promise状态为fulfilled,并且会将所有的Promise的返回值组成一个新的数组有一个Promise状态为reject时,新的Promise状态为reject,并且会将第一个reject的返回值作为参数,给catch方法const promise = new Promise((reslove, reject) => {setTimeout(() => {reslove("11");}, 3000);
});
const promise1 = new Promise((reslove, reject) => {setTimeout(() => {reslove("22");}, 2000);
});
const promise2 = new Promise((reslove, reject) => {setTimeout(() => {reslove("33");}, 1000);
});// 如果上面的三个promise中有一个reject,那么不用等到全部promise执行完毕,直接调用catch,剩余没有执行的promise也不会执行了
Promise.all([promise, promise1, promise2]).then((res) => {console.log(res); //["11", "22", "33"];
}).catch((err) => console.log(err));
会在所有的Promise都有结果(settled),(无论是fulfilled还是rejected),才会有最终的状态 结果一定是fulfilled状态的const promise = new Promise((reslove, reject) => {setTimeout(() => {reject("11");}, 3000);
});
const promise1 = new Promise((reslove, reject) => {setTimeout(() => {reslove("22");}, 2000);
});
const promise2 = new Promise((reslove, reject) => {setTimeout(() => {reslove("33");}, 1000);
});
Promise.allSettled([promise, promise1, promise2]).then((res) => {console.log(res);
});
// [
// { status: "rejected", reason: "11" },
// { status: "fulfilled", value: "22" },
// { status: "fulfilled", value: "33" },
// ];
谁先有结果那么就使用谁的结果无论Promise的状态是fulfilled还是rejectedconst promise = new Promise((reslove, reject) => {setTimeout(() => {reject("11");}, 300);
});
const promise1 = new Promise((reslove, reject) => {setTimeout(() => {reslove("22");}, 2000);
});
const promise2 = new Promise((reslove, reject) => {setTimeout(() => {reslove("33");}, 1000);
});
Promise.race([promise, promise1, promise2]).then((res) => {console.log(res);
}).catch((err) => console.log(err));
// 11
有一个Promise是fulfilled,那么直接调用then方法,只会将当前的内容传进去AggregateError的错误const promise = new Promise((reslove, reject) => {setTimeout(() => {reject("11");}, 300);
});
const promise1 = new Promise((reslove, reject) => {setTimeout(() => {reject("22");}, 2000);
});
const promise2 = new Promise((reslove, reject) => {setTimeout(() => {reject("33");}, 1000);
});
Promise.any([promise, promise1, promise2]).then((res) => {console.log(res);
}).catch((err) => console.log(err));
// AggregateError: All promises were rejected
产生一系列值(无论是有限还是无线个)的标准方式特点的next方法next方法有如下的要求
无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象可以产生序列中的下一个值,则为false已将序列迭代完毕,则为true,这种情况下,value是可选的,如果它依然存在,即为迭代结束之后的默认返回值(一般情况是undefined)done为true时可以省略// namesIterator对象就是names的迭代器
const names = ["a", "b", "c"];
let i = 0;
const namesIterator = {next() {if (i < names.length) {return { done: false, value: names[i++] };} else {return { done: true, value: names[i++] };}},
};
console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());
实现了iterable protocol协议时,他就是一个可迭代对象必须实现@@iterator方法,在代码中使用Symbol.iterator访问该属性对象中返回一个迭代器属性名是[Symbol.iterator]方法 要用计算属性名const infos = {name: "jack",age: 19,height: 1.88,[Symbol.iterator]() {// 获取key// let keys = Object.keys(this);// 获取值// let values = Object.values(this);// 获取entries(键值对)let entries = Object.entries(this);let index = 0;let iterator = {next: () => {if (index < entries.length) {return { done: false, value: entries[index++] };} else {return { done: true, value: undefined };}},};return iterator;},
};for (let info of infos) {console.log(info);
}
JavaScript中语法(常用)
创建一些对象时:
一些方法的调用
class Person {constructor(name, age, height, friends) {this.name = name;this.age = age;this.height = height;this.friends = friends;}[Symbol.iterator]() {let index = 0;let values = Object.values(this);return {next() {if (index < values.length) {return { done: false, value: values[index++] };} else {return { done: true, value: values[index++] };}},};}
}const p1 = new Person("jack", 18, 1.88, ["rose", "lily"]);for (let item of p1) {console.log(item);
}
return方法const infos = {name: "jack",age: 19,height: 1.88,[Symbol.iterator]() {// 获取值let values = Object.values(this);let index = 0;let iterator = {next: () => {if (index < values.length) {return { done: false, value: values[index++] };} else {return { done: true, value: undefined };}},return() {console.log("进入终端监测");return { done: true };},};return iterator;},
};
const [name] = infos;
// console.log(name);
生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等
生成器是由生成器函数所创造的
生成器函数也是函数,但是和普通函数有些区别
首先,生成器函数需要在function后面加一个符号 *
符号挨着function后面,或者挨着函数名前面都行
如果没有function关键字声明的函数,*直接加在名字前面
let info = {*next() {}
} 其次,生成器函数可以通过yield关键字来控制函数的执行流程
最后,生成器函数返回值是一个Gererator(生成器):
function* foo() {console.log("first");console.log("first");yield;console.log("first`q");console.log("first`q");
}const gererator = foo();
生成器函数的代码直接调用是不会执行的
返回一个生成器通过调用返回的生成器的next方法来执行生成器函数中的代码块
当执行代码的过程中遇到yield关键字的时候会暂停执行
如果想继续执行,需要再次调用next函数
yield 后面可以跟值
调用next的时候可以传参,但是这个参数会在yield之后的代码中接收到
// next函数传参 第一次执行next无法接收到,一般直接传在函数中
function* foo(res) {console.log("111", res);console.log("111", res);const res1 = yield "萘萘萘";// return "萘萘萘";console.log("222", res1);console.log("222", res1);const res2 = yield "哈哈哈";console.log("333", res2);console.log("333", res2);
}const gererator = foo("第一个");
console.log(gererator.next());
console.log(gererator.next("第二个"));
console.log(gererator.next("第三个"));// 执行结果如下
// {value: '萘萘萘', done: false} 这种是next函数的返回值
// 如果遇到return后续代码不会执行,next的返回值是 {value: '萘萘萘', done: true}
// 之后在执行next 返回值都是 {value: undefined, done: true}
/*111 第一个111 第一个{value: '萘萘萘', done: false}222 第二个222 第二个{value: '哈哈哈', done: false}333 第三个333 第三个{value: undefined, done: true}*/
function* foo() {try {console.log("1111");const c1 = yield "aaa";console.log("2222", c1);const c2 = yield "bbb";console.log("3333", c2);} catch (error) {console.log(error);}
}
return传值后这个生成器函数就会结束,之后调用next不会继续生成值了
const genetarorFoo = foo();console.log(genetarorFoo.next("@@@"));console.log(genetarorFoo.return('###');console.log(genetarorFoo.next("$$$")); 抛出异常后可以在生成器函数中捕获异常
但是在catch语句中不能继续yield新的值了,但是可以在catch语句外使用yield继续中断函数的执行
const genetarorFoo = foo();
console.log(genetarorFoo.next("@@@"));
console.log(genetarorFoo.throw(new Error()));
console.log(genetarorFoo.next("$$$")); const names = ['jack', 'rose', 'lily']function* createIterator(iter) {
for (let i = 0; i < iter.length; i++) {
yield iter[i]
}
// yield iter[0]
// yield iter[1]
// yield iter[2]
}const namesIterator = createIterator(names)console.log(namesIterator.next()) // {value: 'jack', done: false}
console.log(namesIterator.next()) // {value: 'rose', done: false}
console.log(namesIterator.next()) // {value: 'lily', done: false}
console.log(namesIterator.next()) // {value: undefined, done: true}
可迭代对象的优化版本
const infos = {firends: ["jack", "rose", "lily"],*[Symbol.iterator]() {yield* this.firends;},
};for (let info of infos) {console.log(info);
}
可迭代的对象依次迭代后面跟着的可迭代对象的内容const names = ['jack', 'rose', 'lily']
yield* names相当于yield 'jack'
yield 'rose'
yield 'lily'// 简化代码
function* createIterator(arr) {yield* arr
}
情况一:异步函数也是有返回值的,但是异步函数的返回值相当于被包裹到Promise.resolve中 情况二:如果异步函数的返回值是Promise,那么这个返回值的状态由Promise决定情况三:如果异步函数的返回值是一个对象,并且实现了thenable,那么状态会由对象的then方法来决定 async function foo() {a.map(ss); // 报错 会在catch中被捕获return "aaa";}// 代码中的报错会在catch中执行foo().then((res) => console.log(res)).catch((err) => console.log(err));
async函数另外一个特殊之处就是可以在他内部使用await关键字,而普通函数是不可以的
await关键字的特点
会跟上一个表达式,这个表达式会返回一个Promise等到Promise的状态变为fulfilled状态,之后继续执行异步函数如果await后面是一个普通值,那么会直接返回这个值
如果该值不是一个 Promise,await 会把该值转换为已正常处理的 Promise,然后等待其处理结果。
async function foo() {await 20;//相当于 await Promise.resolve(20)
} 如果await后面是一个thenable对象,那么会根据thenable的then方法来调用后续的值
如果await后面的表达式,返回的Pomise是rejected的状态,那么会将这个reject结果直接作为Promise的reject值
面试题相关的知识储备
进程(process):计算机已经运行的程序,是操作系统管理程序的一种方式线程(thred):操作系统能够运行运算调度的最小单位,通常情况下他被包含在进程中JavaScript是单线程的浏览器的进程
浏览器都是多进程的,当我们打开一个标签页就会开启一个新的进程,这样做的目的是为了防止一个页面卡死而造成所有页面无法响应,整个浏览器需要强制退出JavaScript的代码是在一个单独的线程中执行的
JavaScript的代码同一时刻只能做一件事非常耗时的,就意味着当前的线程会被阻塞耗时的操作不是由JavaScript线程执行的
其余的线程可以来做这个耗时的操作特定的时候执行应该有的回调即可遇到了异步的操作,浏览器会将这些异步操作交给处理异步操作的线程添加到事件队列里面(调用栈空了),去任务队列中寻找任务,将任务队列中的任务添加到执行上下文调用栈中执行事件循环队列是先进先出的
事件循环中并非只维护着一个队列,事实上有两个队列
宏任务队列(macrotask queue):Ajax、setTimeout、setInterval、DOM监听、UI Rendering等微任务队列(microtask queue):Promise的then回调/catch回调、Mutilation Obrserver API、queueMicrotask等两个队列的优先级
main script中的代码优先执行(编写的顶层script代码)执行任何一个宏任务之前(不是队列,是一个宏任务),都会先查看微任务队列中是否有任务需要执行 Promise面试题
console.log("script start");setTimeout(() => {console.log("setTimeout1");new Promise(function (resolve) {resolve();}).then(function () {console.log("then4");});console.log("then2");
});new Promise(function (resolve) {console.log("promise1");resolve();
}).then(function () {console.log("then1");
});setTimeout(() => {console.log("setTimeout2");
});console.log(2);queueMicrotask(() => {console.log("queueMicrotask1");
});new Promise(function (resolve) {resolve();
}).then(function () {console.log("then3");
});console.log("script end");// script start
// promise1
// 2
// script end
// then1
// queueMicrotask1
// then3
// setTimeout1
// then2
// then4
// setTimeout2/* 解析1. main script代码执行2. 打印script start3. 遇到第一个setTimeout,将其放入宏任务队列4. 遇到第一个new Promise 打印promise1,resolve()时 将then回调放入微任务队列5. 遇到第二个setTimeout, 将其放入宏任务队列5. 打印26. 遇到queueMicrotask,将回调放入微任务队列7. 遇到第二个new Promise,resolve()时 将then回调放入微任务队列8. 打印script end9. 当前调用栈的代码执行完毕,去任务队列中找,根据浏览器在执行宏任务队列之前,会先清空微任务队列的机制,(队列是先进先出的数据结构)10. 依次打印 then4 queueMicrotask1 then311. 执行宏任务的第一个回调 打印setTimeout112. 遇到一个new Promise resolve()时 将then的回调放入微任务队列13. 打印then214. 当前宏任务执行完毕,执行下一个宏任务,监测微任务队列是否有任务15. 打印then4(刚刚推入微任务的回调)16. 执行宏任务队列的任务 打印setTimeout2*/
async、await面试题
默认返回undefined 等到Promise的状态是fulfilled时,才执行后续代码后面的代码相当于被包裹在then函数中,所以后续代码会被放入到微任务队列中async function async1() {console.log("async1 start");await async2();console.log("async end");
}async function async2() {console.log("async2");
}console.log("script start");setTimeout(() => {console.log("setTimeput");
}, 0);async1();new Promise((resolve) => {console.log("promise1");resolve();
}).then(() => {console.log("promise2");
});console.log("script end");// script start
// async1 start
// async2
// promise1
// script end
// async end
// promise2
// setTimeput
/*1. main script执行2. 打印script start3. 执行setTimeout,将其给执行异步操作的线程,当时间结束后,异步操作的线程将其放入宏任务队列4. 执行async1()5. 打印async1 start6. 执行 await async2()7. 打印 async28. async2函数返回值相当于 Promise.resolve(undefined)9. async1中的await等到了fulfilled状态,将其后续代码加入到微任务中10. 执行new Promise 打印promise111. 执行resolve() 将then代码推入微任务队列12. 打印script end13. 执行微任务队列代码 依次打印async end promise214. 执行宏任务队列任务, 打印setTimeput*/
抛出一个用户自定义的异常当前函数执行会被停止(throw后面的语句不会被执行)一个表达式来表示具体的异常信息基本数据类型: 比如number、string、boolean对象类型message: 创建Error对象时传入的messagename:Error的名称,通常和类的名称一致stack:整个Error的错误信息,包括函数的调用栈,当我们直接打印Error对象时,打印的就是stackfunction foo() {// throw new RangeError("我是错误信息", "ss");// throw new TypeError("我是错误信息", "ss");throw new SyntaxError("我是错误信息", "ss");
}foo();
当程序出现异常,这个异常会一层一层往上抛
如果整个程序没有捕获异常,那么就会抛给浏览器,浏览器拿到异常会在控制台报错,后续代码就不会执行
所以我们应该手动捕获异常
使用try catch来捕获异常
catch后面可以跟finally
es10中catch后面绑定的error可以省略
try{}catch{//不用error的信息,可以省略
}finally {console.log("无论是否异常都会执行");
} function foo() {// throw new RangeError("我是错误信息", "ss");// throw new TypeError("我是错误信息", "ss");throw new SyntaxError("我是错误信息", "ss");
}
try {foo();
} catch (error) {console.log(error.name); //SyntaxErrorconsole.log(error.message); // 我是错误信息console.log(error.stack);// SyntaxError: 我是错误信息// at foo (异常处理.html:14:15)// at 异常处理.html:17:9
}
localStorage:本地存储,提供的是一种永久性的存储方法,在关掉网页重新打开时,存储内容依然保留sessionStorage:会话存储,提供本次会话的存储,在关掉会话(网页)时,存储的内容会被清除同一页面内实现跳转,localStorage和sessionStorage都会被保存跳转到不同的网页时(打开一个新的页面),localStorage保存,sessionStorage不保存localStorage.key(0)
sessionStorage.key(0)
定义
使用
JavaScript中,正则表达式使用RegExp类来创建,也有对应的字面量方式
正则表达式主要由两部分构成:模式(patterns)和修饰符(flags)
const reg = new RegExp('aa','ig')
const reg1 = /aa/i RegExp实例方法
true 或 false。null。字符串中的方法(可以使用正则)(部分方法)
加g常见的修饰符
字符类(character classes)是一个特殊的符号,匹配特定集中的任何符号
下面每个符号只能匹配一个
反向类(inverse classes)
符号^和符号$在正则中具有特殊意义,称之为锚点
^ 表示匹配开头$ 表示匹配结尾词边界
// 表示匹配紧挨着name左边的不可以是\w相关的(字母、数字、下划线)这之外其他的都行,
//右边则可以是任意
// 还可以这样
/\bname\b/ // 表示name两侧紧挨着的不能是\w相关的const str = "my 我namea is zs";
if (/\bname/gi.test(str)) {console.log("first");
}
// first//案列 取出时间
const info = `time 12:00 eat some food 234:88`;
const timeRe = /\b\d\d:\d\d\b/g;
console.log(info.match(timeRe));// [12:00]
集合
[]范围
排除范围
/a{3,5}/
// 表示匹配3到5直接数量的a
量词的缩写
贪婪模式(Greedy)懒惰模式中的量词与贪婪模式中是相反的 贪婪模式(Greedy)// 获取书名
const message = "我最喜欢的一本书是《肖申克的救赎》和《小猪佩奇》";
//默认情况 .+ 是贪婪模式
// 获取的结果就是["《肖申克的救赎》和《小猪佩奇》"]
// 因为 》也是 .能够匹配到的
// const bookRe = /《.+》/g;// 在后面加?表示开启惰性模式
// 表示找到一个符合的就将结果储存,然后继续后续查找
const bookRe = /《.+?》/g;
console.log(message.match(bookRe)); // ['《肖申克的救赎》', '《小猪佩奇》']
捕获组 const message = "我最喜欢的一本书是《肖申克的救赎》和《小猪佩奇》";// 匹配结果没有加g 也可用matchAllconst bookRe = /《(.+?)》/;console.log(message.match(bookRe)); // 索引0 "《肖申克的救赎》"// 索引1 "肖申克的救赎"
命名组
const reg = /(?hel)lo/i
// 基本实现 -取消防抖、是否立即执行
function ymDebounce(cb, delay, immediate = false) {// 创建变量let timer = null;let isInvoke = false;// 返回一个函数用来作为oninput的事件const _debounce = function (...args) {// 判断是否立即执行if (immediate && !isInvoke) {cb.apply(this, args);isInvoke = true;return;}// 如果timer不是空 那么就清空计时器// 如果delay时间内再次调用此函数那么上一次的计时器就会被清空// 这就是防抖的核心代码if (timer) clearTimeout(timer);// 创建一个计时器timer = setTimeout(() => {cb.apply(this, args);// 执行过后 将此次的计时器改为null// 不写也行,写了更加严谨,让一起回归原本timer = null;isInvoke = false;}, delay);};// 取消防抖_debounce.cancel = function () {if (timer) clearTimeout(timer);timer = null;isInvoke = false;};return _debounce;
}// 有返回值
function ymDebounce(cb, delay, immediate = false) {// 创建变量let timer = null;let isInvoke = false;let res = undefined;// 返回一个函数用来作为oninput的事件const _debounce = function (...args) {return new Promise((resolve, reject) => {try {// 判断是否立即执行if (immediate && !isInvoke) {res = cb.apply(this, args);resolve(res);isInvoke = true;return;}if (timer) clearTimeout(timer);// 创建一个计时器timer = setTimeout(() => {res = cb.apply(this, args);resolve(res);timer = null;isInvoke = false;}, delay);} catch (error) {reject(error);}});};// 取消防抖_debounce.cancel = function () {if (timer) clearTimeout(timer);timer = null;isInvoke = false;};return _debounce;
}
const debounce = ymDebounce((...args) => {console.log(args);return "eres";},1000,false
);
debounce("jack", 19, 1.88).then((res) => {console.log(res);
});
function ymThrottle(cb, interval, leading = true, trailing = false) {// startTime不用Date.now(),用0// 因为节流函数默认情况第一次是要触发的// 用0的话第一次触发awaitTime一定是<=0let startTime = 0;const _throttle = function (...args) {const nowTime = Date.now();// 判断是否立即执行// 让awaitTime 大于0即可if (!leading && startTime === 0) {startTime = nowTime;}// 这一行代码是核心代码// nowTime - startTime 表示代码执行开始到执行中任意时间的间隔// interval - (nowTime - startTime) 表示用户传入的时间间隔减去执行的时间间隔// 如果这个差值小于等于0的话就执行函数const awaitTime = interval - (nowTime - startTime);//if (awaitTime <= 0) {cb.apply(this, args);// 将开始时间重新赋值,目的是计算下次的时间间隔startTime = Date.now();}};return _throttle;
}// 实现尾部控制相关-取消和返回值
function ymThrottle(
cb,interval,{ leading = true, traling = false } = {}
) {let startTime = 0;let timer = null;const _throttle = function (...args) {return new Promise((resolve, reject) => {try {const nowTime = Date.now();if (!leading && startTime === 0) {startTime = nowTime;}const waitTime = interval - (nowTime - startTime);if (waitTime <= 0) {console.log("first");if (timer) clearTimeout(timer);const res = cb.apply(this, args);resolve(res);startTime = nowTime;timer = null;return;}// 实现尾部取消// 在间隔点之后添加一个定时器// 如果是间隔点那么就会取消这个定时器if (traling && !timer) {timer = setTimeout(() => {console.log("timer");const res = cb.apply(this, args);resolve(res);startTime = Date.now();timer = null;}, waitTime);}} catch (error) {reject(error);}});};// 取消_throttle.cancel = function () {if (timer) clearTimeout(timer);};return _throttle;
}
// 要用weakMap 用完后没有对newObj进行强引用,newObj会被垃圾回收function deepCopy(originValue, map = new WeakMap()) {// 如果是symbol类型if (typeof originValue === "symbol")return Symbol(originValue.description);// 判断传入的是否是非对象if (!isObject(originValue)) return originValue;// 函数一般不会深拷贝,函数是用来执行的,深拷贝会造成占用内存if (originValue instanceof Function) return originValue;// 如果是set类型if (originValue instanceof Set) {const set = new Set();for (const item of originValue) {set.add(deepCopy(item));}return set;}// 循环引用if (map.get(originValue)) return map.get(originValue);// 判断是对象还是数组const newObj = Array.isArray(originValue) ? [] : {};// 循环引用map.set(originValue, newObj);for (const key in originValue) {newObj[key] = deepCopy(originValue[key], map);}// 如果key是symbolconst symbolKeys = Object.getOwnPropertySymbols(originValue);for (const symbolKey of symbolKeys) {newObj[Symbol(symbolKey.description)] = deepCopy(originValue[symbolKey],map);}return newObj;
}const s1 = Symbol("我是s1");
const info = {name: "zs",age: 19,address: {province: "HENAN",city: "信阳",},color: ["red", "blue", "green"],
};
// 循环引用
info.self = info;const newInfo = deepCopy(info);
console.log(newInfo);
class ymEventBus {constructor() {this.eventMap = {}}// 监听on(eventName, eventFn) {let eventFns = this.eventMap[eventName]if (!eventFns) {eventFns = []this.eventMap[eventName] = eventFns}eventFns.push(eventFn)}// 发射emit(eventName, ...args) {let eventFns = this.eventMap[eventName]if (!eventFns) returneventFns.forEach((fn) => fn(...args))}// 取消off(eventName, eventFn) {let eventFns = this.eventMap[eventName]if (!eventFns) returnthis.eventMap[eventName] = eventFns.filter((fn) => fn !== eventFn)if (!this.eventMap[eventName].length) {delete this.eventMap[eventName]}}
}const bus = new ymEventBus()const fn1 = (...args) => {console.log('vabClick 01', args)
}
const fn2 = (...args) => {console.log('vabClick 02', args)
}
bus.on('navClick', fn1)
bus.on('navClick', fn2)const btnEl = document.querySelector('.nav-btn')
btnEl.onclick = function () {console.log('自己触发')bus.emit('navClick', '男', 19, 1.88)bus.off('navClick', fn1)console.log(bus)
}
console.log(bus)
只支持GET请求方法获取文本数据,当时主要是为了获取HTML页面内容每次请求都需要与服务器建立一个TCP连接,请求处理完成后立即断开TCP连接,每次建立连接增加性能损耗持久连接(Connection:keep-alive),多个请求可以共用一个TCP连接XMLHttpRequest的state(状态)
| 值 | 状态 | 描述 |
|---|---|---|
| 0 | UNSENT | 代理被创建,但尚未调用open()方法 |
| 1 | OPENED | open()方法已被调用 |
| 2 | HEADRES_RECEIVED | send()方法已被调用,并且头部和状态已经可获得 |
| 3 | LOADING | 下载中,responseText属性已经 包含部分数据 |
| 4 | DONE | 下载操作已完成 |
XMLHttpRequest的事件
获取HTTP的响应状态
xhr.status
xhr.statusText
timeout&&abort
abort方法,强制取消请求无法查看进度早期的XMLHttpRequest的替代方案,他提供了一种更加现代的处理方案 json类型,那么就调用json()方法 Response(类)对象fetch('http://180.76.235.241:3000/media/list', {method: 'get',headers: {},body: {},
}).then(async (resp) => {const res = await resp.json()console.log(res)
})
上一篇:刀锋怎么造句