Appearance
Javascript 基础
什么是基础变量
- 可以变化的量, 由变量名和变量值组成, 名称是标识, 值是可以变化的
- 一个变量对应一块小内存, 变量名是这块内存的标识名称, 用于查找这块内存, 变量的值就是内存 中保存的数据
- 基本类型变量与引用类型变量的区别是什么?
- 根据变量内存中保存的数据来区分
- 基本类型变量: 内存中保存的基本类型的数据
- 引用类型变量: 内存中保存的是地址值
- 给变量赋值到底做了什么?
- 将基本类型数据赋给变量: 将数据保存在变量内存中
- 将对象赋给变量: 将对象在内存中的地址值保存在变量内存中
- 将另一个变量赋给变量: 将右侧变量内存中的数据(基本数据/地址值数据)拷贝保存到左侧变量内存
- 函数传参, 到底是值传递还是引用传递?
- 函数调用时, 是将实参变量的数据拷贝一份赋值给形参变量
- 只是实参变量数据可能是基本类型, 也可能是引用类型的(地址值)
- 有哪些改变变量值的方式?
- 只能通过赋值改变
- c.m = 2: 改的是 c 变量指向的对象内部的属性数据, c 本身没有变化的(对象内存的位置没变)
- 如何理解下面 2 句重要的话(编码演示说明)?
- 2 个引用变量指向同一个对象, 如果通过一个变量修改对象内部的数据 => 另一个变量也能看到原 对象(新数据)
- 2 个引用变量指向同一个对象, 让其中一个变量指向一个新的对象 => 另一个引用变量看到的是老的对象
js设计原理
- js引擎
- 运行上下文
- 调用栈
- 事件循环
- 回掉
js组成部分
- ECMAScript Js的核心内容,描达了语言的基础语法,比如var, for,数据类型(数组、字符串)
- DOM 把整个HTML页面规划为元素构成的文档
- BOM 对浏览器窗口进行访问和操作
数据类型
- 数据类型
基础数据类型 string number boolen null undefined symbol
基本数据类型保存在栈内存中、保存的是一个具体的值 是栈
引用数据类型 object function array 保存在堆内存当中是一个引用类型地址
- 检验数据类型
- typeof
- instanceof Array
- ('str').constructor === 'string'
- objet.prototype.toString.call(2)
- 通过原型链判断:实例的__proto__属性指向其构造函数的原型对象
- Constructor:实例constructor属性指向构造函数本身
- Instanceof:instanceof可以判类型是否是实例的构造函数
- isPrototypeof:判断类型的原型对象是否在某个对象的原型链上
- 通过object原型上的方法判断:比如array.isArray()来判断是不是一个数组
- undefined和null区别 null空对象指针而undefined表示无初始化值在双等于时为true,而在全等于时为false
- object.prototype.toString.call(null) ['object null']
- instanceof原理 判断其原型链中是否能够找到该类型的原型也就是instanceof左边值的__proto__是否能够找到instanceof右边值的prototype所以instanceof只能判断引用数据类型而不能判断基本数据类型
- 判断对象是否为空对象
- 对象转字符串后和'{}'对比
- forin循环有key则不是反之则是空对象
- Object.keys()方法查看长度
- 判断数组还是obejct类型
- 使用object.prototype.toString.call() === '[object Array]'
- 使用Array.isArray()
- 使用constructor
- 操作数组的方法有哪些? push () pop() sort() splice() unshift() shift() reverse() concat () jo. ervery () some () reduce() isArray () findIndex() 哪些方法会改变原数组? push () pop() unshift() shift() sort() reverse() splice()
栈/堆和队列
- 栈 一个出口 先进后出 函数调用栈和基础数据存储
- 推 无序键值对 引用数据类型存储
- 队列 一个入口 一个出口 先进先出 事件轮训机制
事件循环机制
- 主线程以同步执行任务为主
- 宏任务 setTimeout setInterval i/O操作 UI渲染
- 微任务 promise async await 主线程先执行同步任务,然后才去执行任务队列里的任务,如果在执行宏任务之前有微任务,那么要先执行微任务,全部执行完之后等待主线程的调用,调用完之后再去任务队列中查看是否有异步任务,这样一个循环往复的过程就是事件循环
- 原因(单线程是异步产生的原因 / 事件循环是异步的实现方式 / 微队列优先执行 / 交互队列优先延迟队列)
- 事件循环浆叫做消息循环,是浏览器渲染主线程的工作方式。
- 在 Chrome的源码中,它开启一个不会结束的 for 循环,每次循环从消息队列中取出第一个任务执行,而其他线程只需要在合适的时候将任务加入到队列未尾即可。
- 过去把消息队列简单分为宏队列和微队列,这种说法目前已无法满足复杂的浏览器环境,取而代之的是一种更加灵活多变的处理方式。
- 根据 W3C 官方的解释,每个任务有不同的类型,同类型的任务必须在同一个队列,不同的任务可以属于不同的队列。不同任务队列有不同的优先级,在一次事件循环中,由浏览器自行决定取哪一个队列的任务。但浏览器必须有一个微队列,微队列的任务一定具有最高的优先级,必须优先调度执行。
- 主线程以同步执行任务为主
- 宏任务 setTimeout setInterval i/O操作 UI渲染
- 微任务 promise async await
主线程先执行同步任务,然后才去执行任务队列里的任务,如果在执行宏任务之前有微任务,那么要先执行微任务,全部执行完之后等待主线程的调用,调用完之后再去任务队列中查看是否有异步任务,这样一个循环往复的过程就是事件循环
闭包
闭包原理
函数嵌套函数,内部函数被外部函数返回并保存下来时就会产生闭包
- 特点:可以重复利用变量,并且这个变量不会污染全局的一种机制,这个变量一直存储在内存中不会被垃圾回收机制回收
- 缺点,闭包较多的时候,会消耗内存,导致页面的性能下降,在IE浏览器中才会导致内存泄漏
- 使用场最:防抖,节流,函数嵌套函数避免全局污染的时候
内存泄漏如何理解
js里己经分配内存地址的对象,但是由于长时间没有释放或者没办法清除,造成长期浪费,最终号致运行速度慢,甚至崩溃的情况。
- 一些为声明直接赋值的变量:
- 一些未清空的定时器:
- 过度的闭包
- 一些引用元素没有被清除
- 消除闭包
- 自执行函数
- 方法挂载在this上后操作完后返回某个变量值
- 将闭包结果在使用完后进行赋值null操作
原型链
- 原型是一个普通对象,它是为构造函数的实例共享属性和方法,所有实例中引用的原型都是同一个对象
- 使用prototype可以把方法挂在原型上,内存只保留一份
- __proto__可以理解为指针,实力对象中的属性,指向构造函数的原型(prototype)
- 一个实例对象在调用属性和方法的时候会依次从实例本身、构造函数原型、原型的原型上去寻找这样称之为原型链
bind/apply/call
- 作用
call、apply、bind作用是改变函数执行时的上下文,简而言之就是改变函数运行时的this指向
- 区别
- apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次
- call方法的第一个参数也是this的指向,后面传入的是一个参数列表。跟apply一样,改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次
- bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入)(不需要立即执行)
- 结论
- 三者都可以改变函数的this对象指向
- 三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefined或null,则默认指向全局window
- 三者都可以传参,但是apply是数组,而call是参数列表,且apply和call是一次性传入参数,而bind可以分为多次传入
- bind是返回绑定this之后的函数,apply、call 则是立即执行
- 手写
javascript
function myCall (fn, obj, ...args) {
// 如果传入的是 null/undefined, this 指定为 window
if (obj===null || obj===undefined) {
obj = obj || window
}
// 给 obj 添加一个方法: 属性名任意, 属性值必须当前调用 call 的函数对象
obj.tempFn = fn
// 通过 obj 调用这个方法
const result = obj.tempFn(...args)
// 删 除 新 添 加的 方 法
delete obj.tempFn
// 返回函数调用的结果
return result
}
javascript
function myApply (fn, obj, args) {
// 如果传入的是 null/undefined, this 指定为 window
if (obj===null || obj===undefined) {
obj = obj || window
}
// 给 obj 添加一个方法: 属性名任意, 属性值必须当前调用 call 的函数对象
obj.tempFn = fn
// 通过 obj 调用这个方法
const result = obj.tempFn(...args)
// 删 除 新 添 加的 方 法
delete obj.tempFn
// 返回函数调用的结果
return result
}
javascript
function myBind (fn, obj, ...args) {
if (obj===null || obj===undefined) {
obj = obj || window
}
return function (...args2) {
return call(fn, obj, ...args, ...args2)
}
}
防抖和节流
都是应对页面中频繁触发事件的优化方案
- 防抖:避免事件重复触发
使用场景: - 频繁和服务器交互 - 输入框的自动保存事件
javascriptfunction debounce(fn) { // 4、创建一个标记用来存放定时器的返回值 let timeout = null; return function() { // 5、每次当用户点击/输入的时候,把前一个定时器清除 clearTimeout(timeout); // 6、然后创建一个新的 setTimeout, // 这样就能保证点击按钮后的 interval 间隔内 // 如果用户还点击了的话,就不会执行 fn 函数 timeout = setTimeout(() => { fn.apply(this, arguments); }, 1000); }; }
- 节流:把频繁触发的事件减少每隔一段时间执行 使用场景:scroll事件javascript
function throttle(fn) { // 4、通过闭包保存一个标记 let canRun = true; return function() { // 5、在函数开头判断标志是否为 true,不为 true 则中断函数 if(!canRun) { return; } // 6、将 canRun 设置为 false,防止执行之前再被执行 canRun = false; // 7、定时器 setTimeout( () => { fn.apply(this, arguments); // 8、执行完事件(比如调用完接口)之后,重新将这个标志设置为 true canRun = true; }, 1000); }; }
事件委托
又叫事件代理,原理就是利用了事件冒泡的机制来实现,也就是说把子元素的事件鄉定到了父元素身上。如果子元素阻止了事件冒泡,那么委托也就不成立
- 阻止事件冒泡:event. stoppropagation() addEventListener('click’,西数名,true/false)默认足false(事件冒泡),true
- 好处:提高性能,诚少非件的环定,也就诚少了内存的占用。
内存泄漏
- 原因
由于疏忽或错误造成程序未能释放已经不再使用的内存
- 如何产生
- 意外全局变量
- 定时器未清除
- 没有清理对创建DOM元素的引用同样造成内存泄露
- 闭包
- 事件监听addEventListener监听不使用removeEventListener
- 垃圾回收机制
- 标记清除
- 引用计数
函数缓存 函数运算过的结果进行缓存
- 过程分析
- 在当前函数作用域定义了一个空对象,用于缓存运行结果
- 运用柯里化返回一个函数,返回的函数由于闭包特性,可以访问到cache
- 判断输入参数是不是在cache的中。如果已经存在,直接返回cache的内容,如果没有存在,使用函数func对输入参数求值,然后把结果存储在cache中
- 使用场景
- 对于昂贵的函数调用,执行复杂计算的函数
- 对于具有有限且高度重复输入范围的函数
- 对于具有重复输入值的递归函数
- 对于纯函数,即每次使用特定输入调用时返回相同输出的函数
- 代码实现
javascript
const memoize = function (func, content) {
let cache = Object.create(null)
content = content || this
return (...key) => {
if (!cache[key]) {
cache[key] = func.apply(content, key)
}
return cache[key]
}
}
const sum = (a,b) => a+b;
const memoized = memoize(sum)
const num1 = memoized(1,2)
const num2 = memoized(1,2)
setTimeout最小执行时间是多少
- setTimeout最小执行时间4ms
- setInterval 最小执行时间10ms
一元运算符
如果不参与运算++在任何地方都一样
- 如果有运算++在前面(++i)则表示先加1在运算
- 如果有运算符在后面(i++)则表示先运算在加1
javascript
var num = 1;
var sum = ++num + ++num + num++ + ++num;
// sum = 2 + 3 + 3(4) + 5
console.log(sum,num)
commonjs与es6 export区别
- comonjs modules.export
- 基本数据类型 复制 不互相影响
- 引用数据类型 浅拷贝 若改变互相影响
- 使用reuqire是会立即执行且多次引用则使用缓存
- es6 export default
- 静态加载
- 不可以修改引用值 如果多次引用文件发生变化时都会统一更新
JS中的计时器能做到精确计时
不行,因为:
- 计算机硬件没有原子钟,无法做到精确计时
- 操作系统的计时函数本身就有少量偏差,由于JS的计时器最终调用的是操作系统的函数,也就携带了这些偏差
- 按照 W3C的标准,浏览器实现计时器时,如果嵌套层级超过 5层,则会带有 4毫秒的最少时间,这样在计时时间少于 4. 毫秒时又带来了偏差
Javascript 面试
递归和冒泡排序
- 冒泡排序 使用双循环进行排序 外部循环控制所有的回合,内部循环代表每一轮的冒泡处理进行变量比较和交换
- 递归就是函数的自我调用 需要有判断结束的条件和满足条件的调用
for…in循环和for…of循环的区别?
For…in循环:遍历的是数组则输出的是索引,如果遍历的是对象,输出的是对象的属性名,for…in循环需要分析出array中的每个属性,这个操作性能开销很大,用在key已知的数据上是非常不划算的,所以尽量不要使用for…in,除非不清楚要处理哪些属性,例如JSON对象这样的情况
For…of循环:循环一次,就要检查一下数组的长度,读取属性要比局部变量慢,尤其是当array里面存放的都是dom元素,因为每次读取都会扫描页面上的选择器相关元素,速度会大大降低
0.1+0.2 !== 0.3
十进制转二进制然后转十进制计算导致的精度丢失,原因就是二进制表示某些浮点数小数是除不尽的
表达式 a.b 的内部解析流程
- 查找 a: 查找变量, 沿着作用域链查找
- 找不到 => 报错(ReferenceError/引用错误 ): a is not defined
- 找到了: 得到 a 的值, 准备去.b, 但 a 的值不同, 处理结果不同
- undefined/null ==> 报错(TypeError/类型错误): can not read property b of undefined/null
- boolean/number/string 基本值 ==> 创建一个包含这个值的对应的包装类型对象
- 引用地址值 ==> 找到对应的对象 ==> 准备找 b
- 查找 b 属性: 先在对象自身上找, 如果没找到, 沿着原型链查找
- 找不到: 返回 undefined
- 找到了, 返回它的值(值的拷贝) a = null/undefined a.b
this
- this指向
- 全局对象中this指向window
- 全局作用域或普通函数中this指向window
- this永远指向最后调用它的对象(不是箭头函数情况下)
- new关键字改变了this指向
- apply call和bind 可以改变this指向(不是箭头函数情况下)
- 箭头函数中this 箭头函数没有this 看外层是否有函数 有就是外层函数的this 没有就是window
- 匿名函数中this永远指向window(匿名函数执行具有全局性
- 改变this指向
- 指向和函数的调用,call和apply的功能类似,只是传参的方式不同
- call方法传递是一个参数列表
- apply方法传递是一个数组
- bind传参后不会立刻执行,会返回一个改变了this指向的函数,这个函数还可以传参(bind()())
- call方法的性能要比apply好一些,所以call用的更多一点
数组转树结构
- 循环结构
javascript
function buildTree(array){
var obj = {}
var tree = []
array.foreach(item => {
obj[item.id] = {
...item,
children:[]
}
})
array.foreach(item => {
if(!item.parentId){
object[item.parentId].children.push(object[item.id])
} else {
tree.push(object[item.id])
}
})
return tree
}
- 递归
javascript
function buildTree(array,parentId = null){
var tree = []
arrar.foreach(item => {
if(item.id === parentId){
var children = buildTree(array, item.id)
if(children.length > 0){
item.children = children
}
tree.push(item)
}
})
return tree
}
new对象原理?(返回数字不返回对象)
- 先创建空对象
- 把空对象和构造函数通过原型链进行连接
- 把构造函数的this指针绑定到新空对象身上
- 根据构建函数返回类型判断,如果是原始值则被忽略如果引用类型就要返回新对象
javascript
function newObject(obj,..args){
let newObj = {}
newObject.__proto__ = obj.prototype
const rsl = obj.apply(newObj,args)
return rsl instanceof Object ? rsl : newObj
}
js如何实现继承
- 原型继承javascript
//让一个构造函数的原型是另一个类型的实例,那么这个构造区数new出来的实例就具有该实例的属性 function Parent() { this.isShow = true this.info = { name: 'abc', age: 18, } } Parent.prototype.getInfo = function() { console.log(this.info); console.log(this.isshow); } function child(){}; Child.prototype = new Parent(); let Child1 = new Child(); Child1.info.gender = '男'; Child1.getInfo(); let child2 = new child(); child2.isshow = false; console.log(child2.info.gender); child2.getInfo(); //优点:写法方使简洁,容易理解。 //缺点:对象实例共享所有继承的屆性和方法。无法向父类构造函数传参
- 借由构造函数继承javascript在父类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式
// 在子类型构造函数的内部调用父类型构造函数:使用 apply()或 call()方法将父对象的构造函数鄉定 function Parent (gender){ this.info = {name:"ууу", age: 18, gender: gender} } function Child(gender) { Parent.call(this, gender) } let child1 = new child(“男‘); child1. info.nickname = ‘xxxx’ console.log(child1. info); let child2 = new child('女’); console. log(child2.info); //优点:解决了原型链实现继示的不能传参的问题和父类的原型共享的问题 //缺点:借用构造函数的缺点是方法都在构造的数中定义,因此无法实现函数服用
- 组合式继承javascript
//将 原型链 和 借用构造函数 的组合到一块。使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法来实现函数复用,又能够保证每个实例都有自己的属性 function Person (gender) { console.1og("执行次数") this.info = { name:"ууу", age: 19, gender: gender} } Person.prototype.getInfo = function () { console. log (this.info.name, this.info.age)} function Child(gender) { Person.call(this, gender) //使用构造函数法传递参数 } Child.prototype = new Person () Let child1 = new child('男'); child1.info.nickname =’xxx’ child1. getInfo() console.log (childl.info); Iet child2 = new child('女'); console. log(child2. info); //优点:就是解决了原型链继承和借用构造函数继承造成的影响 //缺点:是无论在什么情況下,都会调用两次父类构造函数。一次是在创建手类原型的时候,另一次是在子类构造函数内部
- ES6的class
class通过extends关键字实现继承,其实质是先创造父类的this对象,然后用子类的构造函数修改this。子类的构造方法中必须调用super方法,且只有在调用了super方法后才能使用this,因为子类的this对象是继承父类的this对象,然后对其进行加工,而super方法表示的是父类的构造函数,用来新建父类的this对象
javascriptclass Animal { constructor (kind) { this.kind = kind} getkind() { return this.kind }} //继承Animal class Cat extends Animal { constructor (name) { //子类的构造方法中必须先调用super方法 super ('cat'); this.name = name; } } //继水Animal class Cat extends Animal { constructor (name) { //子类的构造方法中必须先调用super方法 super ('cat'); this.name = hame; } getCatInfo (){ console. log (this. name + + super. getkind()) } } const cat1 = new Cat ("buding"); cat1.getCatInfo (); //优点 语法简单易懂操作更方便 //缺点 不是所有的览器都支持c1ass关键字
深拷贝和浅拷贝区别
- 浅拷贝 Object.assgin()或者直接赋值
- 深拷贝
- 扩展运算符javascript
let obj = {name:’zhangsan’} let obj1 = (...obj) obj1.name = ‘lishi’ console. log (obj) console.log(obi1) //缺点,这个方法只能实现第一层,当有多层的时候还是浅拷贝
- JSON.parse(JSON.string())javascript
let obj = {name:’zhangsan’} let obj1 = JSON.parse(JSON.stringify(obj)) // 无法复制函数对象方
- 递归javascript
function deep(obj,deep){ let obj = {} if(obj instanceof Array){ obj = [] } for(let key in obj){ let val = obj[key] obj[key] = (!!deep && typeof val ==='object'&& val!==null)? deep(val,deep):val } return obj }
- 扩展运算符
设计模式
- 工厂模式 方便创建实例javascript
class A {} class B { create(){ return new A() } } const b = new B() export default b const b1 = b.create() const b2 = b.create()
- 单例模式 只产生并使用一个实例javascript
class A { name = '' constructor(ops){ this.name = ops.name } setName(name){ this.name = name } getName(){ return this.name } } const a = new A({}) export default a a.setName('aaaaa') const person = a.getName()
- 策略模式 不需要更改主函数 可拓展javascript
function getAge(age){ const ageMap = { 10: ()=>{}, 20: ()=>{}, 30: ()=>{}, 50: ()=>{} } return ageMap[age]?.() }
- 适配器模式 数据格式转换javascript
// 老数据结构 const oldData = { key1: 1111, key2: 2222, key3: 3333, }; // 新数据结构 const newData = [ { key: 'key4', value: 4444, }, { key: 'key5', value: 5555, }, ]; // 统一使用函数 const useData = (data) => { Object.entries(data).forEach(([key, value]) => { console.log(`使用数据${key} -- ${value}`); }); }; // 适配器函数 const newDataAdapter = (oldData) => { return newData.reduce((preData, { key, value }) => { preData[key] = value; return preData; }, oldData); }; useData(newDataAdapter(oldData));
- 装饰器模式 不改变原类 拓展功能javascript
class A { say(){ console.log('aaa') } } class B { a: A constructor(a){ this.a = a } say(){ console.log('bbb') } } const a = new A() const b = new B(a) b.say()
- 代理模式 为对象提供一种代理访问的是代理以后的数据 修改也是只支持复杂类型数据javascript
const a = { getpositon(obj){ return obj.x || '' } } const b = new Proxy({},a) b.a= '1111' console.log(b.getpositon({x:1}),a.getpositon({x:1})) console.log(b.a)
- 观察者模式javascript
class Subject{ count: Number observers: Array constructor(){ this.count = 0 this.observers = [] } getCount(){ return this.count } setCount(count){ this.count = count this.notify() } notify(){ this.observers.forEach(item =>{ item.update() }) } add(item){ this.observers.push(item) } } class Observer{ constructor(name:String,subject:Subject){ this.name = name this.subject = subject this.subject.add(this) } update(){ console.log(`${this.name}变了${this.subject.getCount()}`) } } const subject = new Subject() const observer1 = new Observer('张三',subject) const observer1 = new Observer('李四',subject) subject.setCount(2)
- 发布订阅模式javascript
class EventBus { constructor (){ this.cache = {} } on(name,fn){ const task = this.cache[name] if(task){ this.cache[name].push(fn) }else{ this.cache[name]=[fn] } } off(name,fn){ const task = this.cache[name] if(task){ const idx = task.find(item => item === fn) if(idx !== -1){ this.cache[name].splice(idx,1) } } } emit(name,...args){ const task = this.cache[name].splice() if(task){ for(let fn in task){ fn(...args) } } } once(name,callback){ function fn(...args){ callback(args) this.off(name,fn) } this.on(name,fn) } } const eventbus = new EventBus() eventbus.on('Event',val => {console.log(val)}) eventbus.emit('Event','aaaaa')
promise相关
- promise实现javascript
function MyPromise(fn) { // promise 的状态 this.PromiseState = "pendding"; // promise 的值 this.PromiseResult = undefined; this.thenCallback = undefined; var resolve = (value) => { // 更改promise的状态和值 if (this.PromiseState == "pendding") { this.PromiseState = "fulfilled"; this.PromiseResult = value; if (value instanceof MyPromise) { value.then((res) => { if (this.thenCallback) { this.thenCallback(res); } }); } else { setTimeout(() => { if (this.thenCallback) { this.thenCallback(value); } }); } } }; this.catchCallback = undefined; var reject = (errValue) => { if (this.promiseState == "pendding") { this.promiseState = "rejected"; this.promiseResult = errValue; // 判断写没写catch() setTimeout(() => { if (this.catchCallback) { this.catchCallback(errValue); } else if (this.thenCallback) { this.thenCallback(errValue); } else { throw "catch is not defined!!!!"; } }); } }; if (fn) { fn(resolve, reject); } } MyPromise.prototype.then = function (callback) { return new MyPromise((resolve, reject) => { this.thenCallback = (value) => { // 在使用链式调用的时候,可能第一个调用的不是catch // 使用我们在做检测时会借助then来将catch的信息向下传递 // 所以我们检测到触发thenCallback的对象是rejected时 // 我们就继续调用下一个reject if (this.promiseState == "rejected") { reject(value); } else { var res = callback(value); //这里防止中间返回是一个promise对象它会继续找then,直接让调用reject if (res instanceof MyPromise && res.promiseState == "rejected") { res.catch((errValue) => { reject(errValue); }); } else { //这里定义给变量res在调用resolve是为解决.then()的链式调 resolve(res); } } }; }); }; MyPromise.prototype.catch = function (callback) { return new MyPromise((resolve, reject) => { this.catchCallback = (errValue) => { var res = callback(errValue); reject(errValue); }; }); }; MyPromise.resolve = (value) => { return new MyPromise((resolve, reject) => { resolve(value); }); }; MyPromise.reject = (errValue) => { return new MyPromise((resolve, reject) => { reject(errValue); }); }; MyPromise.all = (promiseArry) =>{ let resArry = [] return new MyPromise((reslove,reject)=>{ promiseArry.forEach((item,index) => { item.then(res=>{ resArry[index] = res var allReslove = promiseArry.every(_item => _item.promiseState ==='fulfilled') if(allReslove){ reslove(allReslove) } }).catch(error => reject(error)) }); }) }
- promise状态和如何改变 状态分为pendding fullfiled和rejected
- promise 解决什么问题 解决地狱回调
- promise缺点 1. 首先就是我们无法取消promise一旦创建它就会立即执行,不能中途取消 2. 如果不设置回调,promise内部抛出的错误就无法反馈到外面 3. 若当前处于pending状态时,无法得知目前在哪个阶段
- 多个异步操作按序执行 promise 链式调用、async和await
- 如何中断promise执行 内部抛错 throw new Error
- promise中哪部分是同步那一部分是异步 promise执行部分属于同步 接收数据执行为异步
- 先then后finally 与先finall后then区别
finally方法是不管promise方法执行后的结果是什么,都会执行的,内部其实也是调用了then方法。且finally后仍可以跟then方法,还会把结果原封不动的传递给紧跟的then方法(不管结果是resolve或者reject)。
所以先then后finally,then接受的结果是前一个promise方法的。先finally后then,then接受的结果是从finally里传递出来的。
- promise和async await区别
- 都是处理异步请求的方法
- promise 是ES6 async 和await是ES7语法
- async和await是通过promise来实现 它们和promise都是非阻塞性的 优缺点
- promise是返回对象我们要用then, catch方法去处理和捕获异常,并且书写方式是链式,容易造成代码道叠,不好护,async await 是通过try catch进行捕获异常
- async await最大的优点就是能让代码看起来像同步一样,只要迎到await就会立刻返回结果,然后再执行后面的操作而promise.then()的方式返回,会出现请求还没返回,就执行了后面的操作
instanceof
javascript
function myInstanceOf(obj, Type) {
let protoObj = obj.__proto__
while(protoObj) {
if (protoObj === Type.prototype) {
return true
}
protoObj = protoObj.__proto__
}
return false
}
实现promise队列可配置
javascript
async function sleep(time,str){
return new Promise(resolve =>{
console.log(str,'开始')
setTimeout(()=>{
console.log(str,'结束')
resolve({time,str})
},time *1000)
})
}
async function promiseList({limit,items}){
const promises = []
const pool = new Set()
for(const item of items){
const fn = async item => await item()
const promise = fn(item)
promises.push(promise)
pool.add(promise)
const clear = () => pool.delete(promise)
promise.then(clear,clear)
if(pool.size>=limit){
await Promise.race(pool)
}
}
return Promise.all(promises)
}
async function start (){
await promiseList({
limit: 2,
items :[
()=> sleep(1,'吃饭'),
()=> sleep(2,'睡觉'),
()=> sleep(3,'打豆豆'),
()=> sleep(4,'玩游戏'),
()=> sleep(5,'健身'),
()=> sleep(6,'看电影')
]
})
}
start()
击鼓传花
javascript
class Queue {
#count = 0; //队列最大数量
#lowestCount = 0; //目前第一个元素的下标
#items = {}; //队列
constructor() { }
//增加元素
enqueue(element) {
this.#items[this.#count] = element;
this.#count++;
}
//移除元素
dequeue() {
if (this.isEmpty()) {
return undefined;
}
const result = this.#items[this.#lowestCount];
delete this.#items[this.#lowestCount];
this.#lowestCount++;
return result;
}
//队列是否为空
isEmpty() {
// return this.#count - this.#lowestCount === 0;
return this.size() === 0;
}
//查看队列头元素
peek() {
if (this.isEmpty()) {
return undefined;
}
return this.#items[this.#lowestCount];
}
//队列中有几个元素
size() {
return this.#count - this.#lowestCount;
}
//清空队列
clear() {
this.#count = 0;
this.#lowestCount = 0;
this.#items = {};
}
//toString
toString() {
if (this.isEmpty()) {
return '';
}
let objString = `${this.#items[this.#lowestCount]}`;
for (let i = this.#lowestCount + 1; i < this.#count; i++) {
objString = `${objString}, ${this.#items[i]}`;
}
return objString;
}
}
function hotPotato(elementsList,time) {
const queue = new Queue();
const elimitatedList = [];//移除了的人
for (let i = 0; i < elementsList.length; i++) {
queue.enqueue(elementsList[i]);
}
while (queue.size() > 1) {
//传花中
const num = time || Math.round(Math.random() * 5 + 5)//传花次数 随机5-10次
for (let i = 0; i < num; i++) {
queue.enqueue(queue.dequeue());//移除1人并添加1人 拿到花的是第一个 送出花的是最后一个 形成传递圈
}
let ycitem = queue.dequeue();//本次移除的人
elimitatedList.push(ycitem);
console.log('传花次数', num, '被淘汰人员', ycitem)
}
console.log('胜者', queue.dequeue())
}
交通灯
javascript
class TracfficLight{
contstuctor(options){
const {
red:60,
green:60,
yellow:10,
initColor='green'
} = options ||{}
this.colors = {
red:{
seconds:red,
next:'yellow'
},
green:{
seconds:green,
next:'yellow'
},
yellow:{
seconds:yellow
},
}
this._switchColor(initColor)
}
_switchColor(color){
this.currentColor = color
this.seconds = this.colors[color].seconds
this.time = Date.now()
}
_nextColor(){
if(this.currentColor !== 'red'){
this.colors.yellow.next='green'
} else if(this.currentColor !== 'green'){
this.colors.yellow.next='red'
}
this._switchColor(this.colors[this.currentColor].next)
}
getCurrentLight(){
const time = Math.ceil(this.seconds -(Date now() - this.time) / 1000 )
if(time<=0){
this._nextColor()
return this.getCurrentLight()
}
return{
light: this.currentColor,
time
}
}
}
使用splice一次删除数组多个元素
javascript
var arr = [1,2,3,5,8,9,22,11,45];
for (let i = arr.length - 1; i >= 0; i--) {
if (arr[i] === 8||arr[i] === 9) {
arr.splice(i, 1);
}
}
字符串或者数组中只出现一次的字符
- 字符串javascript
function fun(str){ var arr=[],arr1=[],obj={} arr=str.split('') for(var i=0;i<arr.length;i++){ !obj[arr[i]] ? obj[arr[i]]=1 : obj[arr[i]]++ } for(var i in obj){ if(obj[i]==1){ arr1.push(i) } } }
- 数组javascript
function firstUniqueChar(arr){ var obj={}, len=arr.length; for(var i=0;i<len;i++){ if(obj[arr[i]]){ obj[arr[i]]++; //存在次数+1 } else { obj[arr[i]] = 1; //不存在插入且次数=1 } } for(var prop in obj){ if(obj[prop] == 1 ) return prop; //返回第一个次数等于1的元素 } }
正常输出 1 2 3
javascript
// async function run (){
// console.log(1)
// setTimeout(()=>{
// console.log(2)
// },1000)
// console.log(3)
// new Promise((resolve, reject)=>{
// setTimeout(()=>{
// console.log(3)
// },1000)
// })
// }
// run()
在一段时间内每隔一个间隙随机生成一个数字,此数字在某个范围内则打印
function getName(){
var initTime = 100
var oneTime = 200
var maxTime = 2000
var minNum = 40
var maxNum =60
var times = Math.ceil((maxTime - initTime) / oneTime);
for (let index = 0; index < times; index++) {
const time = initTime+oneTime*index;
(function(time){
setTimeout(()=>{
const num = Math.random()*100;
console.log("num:"+num)
if(num>minNum&&num<maxNum){
console.log("正确num:"+num)
}
},time)
})(time)
}
}
getName()
setTimeout promise async await
javascript
//100 400 300 200
console.log(100)
setTimeout(()=>{console.log(200)},500)
Promise.resolve().then(()=>{console.log(300)})
console.log(400)
// 单个执行 1 3 1 2 3 1 2
Promise.resolve().then(()=>{console.log(1)}).catch(()=>{console.log(2)}).then(()=>{console.log(3)})
Promise.resolve().then(()=>{console.log(1);throw new Error('11')}).catch(()=>{console.log(2)}).then(()=>{console.log(3)})
Promise.resolve().then(()=>{console.log(1);throw new Error('11')}).catch(()=>{console.log(2)}).catch(()=>{console.log(3)})
// // promise 100 100
async function a(){return 100}
(async function(){ const a1 = a();console.log(a1); const a2 =await a();console.log(a2);})()
// start 100 200 抛错后续不执行
;(async function(){
console.log('start')
const a = await 100
console.log('a',a)
const b = await Promise.resolve(200)
console.log('b',b)
const c = await Promise.reject(300)
console.log('c',c)
console.log('end')
})()
// script start async1 start async2 promise1 script end async1 end promise2 setTimeout
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(()=>{
console.log('setTimeout')
},0)
async1()
new Promise(function(reslove){ console.log('promise1');reslove()}).then(()=>{ console.log('promise2')})
console.log('script end')
// 1 7 2 3 8 4 6 5 0
setTimeout(() => {console.log("0")}, 0)
new Promise((resolve,reject)=>{
console.log("1")
resolve()
}).then(()=>{
console.log("2")
new Promise((resolve,reject)=>{
console.log("3")
resolve()
}).then(()=>{
console.log("4")
}).then(()=>{
console.log("5")
})
}).then(()=>{
console.log("6")
})
new Promise((resolve,reject)=>{
console.log("7")
resolve()
}).then(()=>{
console.log("8")
})
es6
es6新特性
- 新增块级作用域 (let,const) - 不存在变量提升 - 存在智时性死区的问题 - 块级作用域的内容 - 不能在同一个作用域内重复声明
- 新增了定义类的语法糖 (class)
- 新增了一种基本数据类型 (symbol)
- 新增了解构赋值 从数组或者对象中取值,然后给变量赋值
- 新增了函数参数的默认值
- 给数组新增了ApI
- 对象和数组新增了扩展运笪符
- Promise
- 新增了模块化(import,export)
- 新增了set 和map数据结构
- set就是不重复
- map的key的类型不受限制
- 新增了generator
- 新增了箭头函数
- 不能作为构造函数使用,不能用new
- 箭头函数就没有原型
- 箭头的数没有arguments
- 箭头函数不能用call,apply ,bind去改
- this指向外层第一个参数的this
Proxy
Proxy其功能非常类似于设计模式中的代理模式,常用功能如下:
- 拦截和监视外部对对象的访问
- 降低函数或类的复杂度
- 在复杂操作前对操作进行校验或对所需资源进行管理
Module
- 使用好处:
- 代码抽象
- 代码封装
- 代码复用
- 依赖管理
- 对比
- AMD (典型代表:require.js) 异步模块定义,采用异步方式加载模块。AMD推崇依赖前置、提前执行
- CMD (典型代表:sea.js)CMD推崇依赖就近、延迟执行
- CommonJS(早期nodeJS,V14后使用esm)模块定义规范,采用CommonJS规范,将模块加载和执行分离,模块加载和执行是异步的,模块加载完成之后,会执行模块的回调函数,模块的回调函数中可以获取模块的依赖模块,并执行模块的代码
- ES6设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量