Skip to content

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 保存在堆内存当中是一个引用类型地址

  • 检验数据类型
    1. typeof
    2. instanceof Array
    3. ('str').constructor === 'string'
    4. 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)
    }
}

防抖和节流

都是应对页面中频繁触发事件的优化方案

  • 防抖:避免事件重复触发

    使用场景: - 频繁和服务器交互 - 输入框的自动保存事件

    javascript
    function 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中的计时器能做到精确计时

不行,因为:

  1. 计算机硬件没有原子钟,无法做到精确计时
  2. 操作系统的计时函数本身就有少量偏差,由于JS的计时器最终调用的是操作系统的函数,也就携带了这些偏差
  3. 按照 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对象原理?(返回数字不返回对象)

  1. 先创建空对象
  2. 把空对象和构造函数通过原型链进行连接
  3. 把构造函数的this指针绑定到新空对象身上
  4. 根据构建函数返回类型判断,如果是原始值则被忽略如果引用类型就要返回新对象
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对象

    javascript
    class 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设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量

Released under the MIT License.