# JS篇

# 1. 普通事件绑定和事件流绑定有啥区别(中级)

普通事件绑定

  • 如果给同⼀个元素绑定了两次或者多次相同类型的事件,那么后⾯的绑定会覆盖前⾯的绑定。

  • 不支持dom事件流(捕获-目标-冒泡)。

事件流绑定

  • 如果说给同⼀个元素绑定了两次或者多次相同类型的事件,所以的绑定将会依次触发。
  • 支持dom事件流(捕获-目标-冒泡)。
  • 不加on事件。
function addEvent(obj,type,callBack){
    if(obj.addEventListener){
        obj.addEventListener(type,callBack);
    }else{
        obj.attachEvent("on"+type,callBack);
    }
}
addEvent(document,"click",function(){alert("兼容写法")});

# 2. 如何阻止事件冒泡和默认事件(中级)

e.preventdefault?e.preventdefault():return value=false

e.stoppropagation?e.stoppropagation():e.cancelbubble=true

# 3. document load 和 document ready 的区别(初级)

  • Document.onload 是在结构和样式加载完才执⾏ js。
  • window.onload:不仅仅要在结构和样式加载完,还要执⾏完所有的样式、图片这些资源文件,全部 加载完才会触发window.onload事件。
  • Document.ready 原⽣种没有这个⽅法,jquery 中有$().ready(function)

# 4. undefined 的三种情况(初级)

  • ⼀个变量定义了却没有被赋值。
  • ⼀个对象上不存在的属性或者⽅法。
  • ⼀个数组中没有被赋值的元素。

# 5. 对象模型 BOM 里常用的至少4个对象(初级)

  • Window
  • document
  • location
  • screen
  • history
  • navigator

# 6. 会造成内存泄漏的情况(中级)

  • setTimeout的第⼀个参数使⽤字符串⽽非函数的话,会引发内存泄漏。
  • 闭包

# 7. 解释什么是jsonp(中级)

  • jsonp 并不是⼀种数据格式,而json是一种数据格式。

  • jsonp 是⽤来解决跨域获取数据的⼀种解 决⽅案。

具体是通过动态创建script标,通过标签的src属性获取js文件中的js脚本,

该脚本的内容是⼀个函数调⽤,参数就是服务器返回的数据,为了处理这些返回的数据,需要事先在⻚⾯定义好回调函数,本质上使⽤的并不是ajax技术。

# 8. 生成随机数(初级)

Math.round(Math.random()*差值)+最小的值
  • link 不仅仅可以加载css,import 只能加载css
  • link 同时加载,import 先载入页面内容。
  • link 无兼容问题。
  • link ⽀持使⽤Javascript控制DOM去改变样式;⽽@import不⽀持。

# 10. 如何优化自己的代码(中级)

  • 重复使用。
  • 避免过多使用全局变量。
  • 闭包中变量的释放。
  • 适当的注释。

# 11. 简述一下你对 web 性能优化的方案(高级)

  • 尽量减少HTTP请求。
  • 使⽤浏览器缓存。
  • 使⽤压缩组件。
  • 图片、JavaScript的预载入。
  • 将脚本放在底部。
  • 将样式文件放在⻚⾯顶部。
  • 使⽤外部的JSCSS
  • 精简代码。

# 12. new 操作符具体干了什么(高级)

  • 创建⼀个空对象,并且 this 变量引⽤该对象,同时还继承了该函数的原型。
  • 属性和⽅法被加入到 this 引⽤的对象中。
  • 新创建的对象由 this 所引⽤,并且最后隐式的返回 this 。

# 13. js 的垃圾回收机制(高级)

  • 什么是垃圾

    一般来说:没有被引用的对象就叫做垃圾,还有一种可能是好几个变量之间相互引用形成闭环,没有其他的对象引用该闭环内的对象,也会被当做垃圾。

  • 如何回收垃圾

    浏览器会对内存中的变量进行标记,从根对象开始进行标记,层层递进,将被引用的进行标记,要是没有被引用就不标记,最后将没有标记的进行回收,释放内存。

# 14. 如何实现浏览器不同页面之间的通信(高级)

  • 通过本地存储的方式。
  • vue 中通过传值的几种方式。
  • react 中的几种传值方法。

# 15. document.write 和 innerHtml 的区别(初级)

  • document.write 会重绘页面整体,innerhtml 只是更新使用的那一部分。

# 16. 浏览器对象模型有哪些(中级)

  • document

  • location

  • screen

  • navigation

  • event

  • history

window中常用的是:一个事件onload,两个定时器,三个弹出框。

# 17. dom 对象模型(中级)

  • document.documentElement 表示html 元素。

  • document.body 返回 body 元素。

  • document.head 返回 head 元素。

    • 节点的添加操作1**【父加子】**

      var h1=document.creatElement("h1")
      父节点.appendchild(h1)
      
    • 节点的添加操作2**【将新节点插入到目标节点之前】**

      父节点.insertBefore(新节点, 目标节点)
      
    • 删除操作

      目标节点.remove()
      

# 18. 自定义属性的读写(中级)

  • setAttribute(“属性名称”,“属性值名称”)

  • getAttribute(“属性名称”)

    oDiv.setAttribute("heihei",123);//写
    document.write(oDiv.getAttribute("heihei"));//读
    

# 19. 数组去重的三种方法(中级)

  • 方法一:

    let arr=[3,5,3,1,3,1]
    function(arr){
        for(var i=0;i<arr.length;i++){
            for(var j=i+1;j<arr.length;j++){
                if(arr[i]==arr[j]){
                    arr.splice(j,1)
                }
            }
        }
        return arr
    }
    
  • 方法二:

    let arr=[1,4,1,6,9]
    function(arr){
        let newarr=[]
        this.arr.forEach((v,i)=>{
            if(newarr.indexof(arr[i])==-1){
                newarr.push(arr[i]))
            }
        })
        return newarr 
    }
    
  • 方法三:

    let arr=[1,4,1,6,9]
    function(arr){
        let newarr=[]
        this.arr.forEach((v,i)=>{
            if(!newarr.includes(arr[i])){
                newarr.push(arr[i]))
            }
        })
        return newarr 
    }
    

# 20. 数组排序的三种方法(中级)

JavaScript中常见的三种数组排序方式,分别为:冒泡排序、选择排序、插入排序。

  • 冒泡排序(Bubble Sort):

思路:

1、比较相邻的元素。如果第一个比第二个大,就交换他们两个。

2、对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。

3、针对所有的元素重复以上的步骤,除了最后一个。

4、持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

const arr = [1, 5, 7, 9, 16, 2, 4]

function bubbleSort(arr) {
  const len = arr.length
  // 外层循环用户控制比较的趟数
  for (let i = 0; i < len - 1; i++) {
    // 内层循环用于控制每趟比较的次数
    for (let j = 0; j < len - i - 1; j++) {
      // 如果前面一个数比后一个数大,就互换位置
      if (arr[j] > arr[j + 1]) {
        let temp = arr[j]
        arr[j] = arr[j + 1]
        arr[j + 1] = temp
      }
    }
  }
  return arr
}
  • 选择排序(Selection sort):

思路:

1、首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。

2、再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。

3、重复第二步,直到所有元素均排序完毕。

const arr = [1, 5, 7, 9, 16, 2, 4]

function selectionSort(arr) {
  const len = arr.length
  let minIndex, temp
  for (let i = 0; i < len - 1; i++) {
    minIndex = i
    for (let j = i + 1; j < len; j++) {
      // 找到最小的数
      if (arr[j] < arr[minIndex]) {
        minIndex = j
      }
    }
    temp = arr[i]
    arr[i] = arr[minIndex]
    arr[minIndex] = temp
  }
  return arr
}
  • 插入排序(Insertion sort):

思路:

1、将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。

2、从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)

const arr = [1, 5, 7, 9, 16, 2, 4]

function insertionSort(arr) {
  const len = arr.length;
  let preIndex, current;
  for (var i = 1; i < len; i++) {
    preIndex = i - 1;
    current = arr[i];
    while (preIndex >= 0 && arr[preIndex] > current) {
      arr[preIndex + 1] = arr[preIndex];
      preIndex--;
    }
    arr[preIndex + 1] = current;
  }
  return arr;
}
  • 其它:

可以使用自带的sort排序,需要注意的是该方法会改变原始数组。

const arr = [1, 5, 7, 9, 16, 2, 4]

// x-y>0 则是递增
arr.sort((x, y) => x - y)

// x-y<0 则是递减
arr.sort((x, y) => y - x)

# 21. 面向对象(中级)

  • 一般使用字面量的形式直接创建对象,但是这种创建方式对于创建大量相似对象的时候,会产生大量的重复代码。

  • 为了解决该问题,在 ES6 之前还没有出现类的概念的时候,采用的是函数来模拟类的实现,从而产生出可复用的对象创建方式。

  • ES5 之前通过构造函数实现面向对象:

    function Student(id,name){
        this.id=id
        this.name=name
        this.show=function(){
            console.log(this.name+"的id号是"+this.id)
        }
        
    }
    let s1=new Student(1,"李")
    s1.show()
    
  • ES6 实现面向对象:

    class Student{
        constructor(id,name){
           this.id=id
        	this.name=name 
        }
        show(){
             console.log(this.name+"的id号是"+this.id)
        }
    }
    let s1=new Student(1,"李")
    s1.show()
    

# 22. JavaScript中什么是基本数据类型什么是引用数据类型?以及各个数据类型是如何存储的?(初级)

基本数据类型有

  • Number
  • String
  • Boolean
  • Null
  • Undefined
  • Symbol(ES6新增数据类型)
  • bigInt

引用数据类型统称为 Object 类型,细分的话有

  • Object
  • Array
  • Date
  • Function
  • RegExp

基本数据类型的数据直接存储在栈中;而引用数据类型的数据存储在堆中,每个对象在堆中有一个引用地址。引用类型在栈中会保存他的引用地址,以便快速查找到堆内存中的对象。

顺便提一句,栈内存是自动分配内存的。而堆内存是动态分配内存的,不会自动释放。所以每次使用完对象的时候都要把它设置为 null,从而减少无用内存的消耗。

# 23. 在JS中为什么0.2+0.1>0.3?而0.2+0.3=0.5呢?(中级)

因为在 JS 中,浮点数是使用64位固定长度来表示的,其中的1位表示符号位,11位用来表示指数位,剩下的52位尾数位,由于只有52位表示尾数位。

0.1转为二进制是一个无限循环数0.0001100110011001100......(1100循环)。

由于只能存储52位尾数位,所以会出现精度缺失,把它存到内存中再取出来转换成十进制就不是原来的0.1了,就变成了0.100000000000000005551115123126,而为什么 0.2 + 0.1 > 0.3 是因为:

// 0.1 和 0.2 都转化成二进制后再进行运算
0.00011001100110011001100110011001100110011001100110011010 +
0.0011001100110011001100110011001100110011001100110011010 =
0.0100110011001100110011001100110011001100110011001100111 
// 转成十进制正好是 0.30000000000000004

0.20.3分别转换为二进制进行计算:在内存中,它们的尾数位都是等于52位的,而他们相加必定大于52位,而他们相加又恰巧前52位尾数都是0,截取后恰好是0.1000000000000000000000000000000000000000000000000000也就是0.5。


// 0.2 和 0.3 都转化为二进制后再进行计算
0.001100110011001100110011001100110011001100110011001101 +
0.0100110011001100110011001100110011001100110011001101 = 
0.10000000000000000000000000000000000000000000000000001 //尾数为大于52位
// 而实际取值只取52位尾数位,就变成了
0.1000000000000000000000000000000000000000000000000000   //0.5

# 24. 那既然0.1不是0.1了,为什么在console.log(0.1)的时候还是0.1呢?(中级)

console.log的时候会二进制转换为十进制,十进制再会转为字符串的形式,在转换的过程中发生了取近似值,所以打印出来的是一个近似值的字符串。

# 25. 判断数据类型有几种方法?(初级)

  • typeof

    • 缺点:typeof null的值为Object,无法分辨是null还是Object
  • instanceof

    • 缺点:只能判断对象是否存在于目标对象的原型链上。
  • constructor

  • Object.prototype.toString.call()

    • 一种最好的基本类型检测方式 Object.prototype.toString.call() ;它可以区分 null 、 string 、

      boolean 、 number 、 undefined 、 array 、 function 、 object 、 date 、 math 数据类型。

    • 缺点:不能细分为谁谁的实例。

    // -----------------------------------------typeof
    typeof undefined // 'undefined' 
    typeof '10' // 'String' 
    typeof 10 // 'Number' 
    typeof false // 'Boolean' 
    typeof Symbol() // 'Symbol' 
    typeof Function // ‘function' 
    typeof null // ‘Object’ 
    typeof [] // 'Object' 
    typeof {} // 'Object'
    
    // -----------------------------------------instanceof
    function Foo() { }
    var f1 = new Foo();
    var d = new Number(1)
    
    console.log(f1 instanceof Foo);// true
    console.log(d instanceof Number); //true
    console.log(123 instanceof Number); //false   -->不能判断字面量的基本数据类型
    
    // -----------------------------------------constructor
    var d = new Number(1)
    var e = 1
    function fn() {
        console.log("ming");
    }
    var date = new Date();
    var arr = [1, 2, 3];
    var reg = /[hbc]at/gi;
    
    console.log(e.constructor);//ƒ Number() { [native code] }
    console.log(e.constructor.name);//Number
    console.log(fn.constructor.name) // Function 
    console.log(date.constructor.name)// Date 
    console.log(arr.constructor.name) // Array 
    console.log(reg.constructor.name) // RegExp
    
    //-----------------------------------------Object.prototype.toString.call()
    console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]" 
    console.log(Object.prototype.toString.call(null)); // "[object Null]" 
    console.log(Object.prototype.toString.call(123)); // "[object Number]" 
    console.log(Object.prototype.toString.call("abc")); // "[object String]" 
    console.log(Object.prototype.toString.call(true)); // "[object Boolean]" 
    
    function fn() {
        console.log("ming");
    }
    var date = new Date();
    var arr = [1, 2, 3];
    var reg = /[hbc]at/gi;
    console.log(Object.prototype.toString.call(fn));// "[object Function]" 
    console.log(Object.prototype.toString.call(date));// "[object Date]" 
    console.log(Object.prototype.toString.call(arr)); // "[object Array]"
    console.log(Object.prototype.toString.call(reg));// "[object RegExp]"
    

# 26. instanceof 原理是什么?(高级)

instanceof 原理实际上就是查找目标对象的原型链。

function myInstance(L, R) {//L代表instanceof左边,R代表右边
    var RP = R.prototype
    var LP = L.__proto__
    while (true) {
        if(LP == null) {
            return false
        }
        if(LP == RP) {
            return true
        }
        LP = LP.__proto__
    }
}
console.log(myInstance({},Object)); 

# 27. 为什么 typeof null 是 Object ?(初级)

因为在JavaScript中,不同的对象都是使用二进制存储的,如果二进制前三位都是0的话,系统会判断为是Object类型,而null的二进制全是0,自然也就判断为Object

这个 bug 是初版本的 JavaScript 中留下的,扩展一下其他五种标识位:

  • 000 对象
  • 1 整型
  • 010 双精度类型
  • 100字符串
  • 110布尔类型

# 28.=====有什么区别?(初级)

===是严格意义上的相等,会比较两边的数据类型和值大小。

  • 数据类型不同返回false
  • 数据类型相同,但值大小不同,返回false

==是非严格意义上的相等。

  • 两边类型相同,比较大小

  • 两边类型不同,根据下方表格,再进一步进行比较

    • Null == Undefined ->true
    • String == Number ->先将 String 转为 Number,在比较大小
    • Boolean == Number ->现将 Boolean 转为 Number,在进行比较
    • Object == String,Number,Symbol -> Object 转化为原始类型

# 29. 手写call、apply、bind(中级)

  • call 和 apply 实现思路主要是:
    • 判断是否是函数调用,若非函数调用抛异常
    • 通过新对象(context)来调用函数
      • 给 context 创建一个fn设置为需要调用的函数
      • 结束调用完之后删除fn
  • bind 实现思路
    • 判断是否是函数调用,若非函数调用抛异常
    • 返回函数
      • 判断函数的调用方式,是否是被new出来的
        • new出来的话返回空对象,但是实例的__proto__指向_thisprototype
    • 完成函数柯里化
      • Array.prototype.slice.call()

call:

Function.prototype.myCall = function (context) {
      // 先判断调用myCall是不是一个函数
      // 这里的this就是调用myCall的
      if (typeof this !== 'function') {
        throw new TypeError("Not a Function")
      }
 
      // 不传参数默认为window
      context = context || window
 
      // 保存this
      context.fn = this
 
      // 保存参数
      let args = Array.from(arguments).slice(1)   //Array.from 把伪数组对象转为数组
 
      // 调用函数
      let result = context.fn(...args)
 
      delete context.fn
 
      return result
}

apply:

Function.prototype.myApply = function (context) {
      // 判断this是不是函数
      if (typeof this !== "function") {
        throw new TypeError("Not a Function")
      }
 
      let result
 
      // 默认是window
      context = context || window
 
      // 保存this
      context.fn = this
 
      // 是否传参
      if (arguments[1]) {
        result = context.fn(...arguments[1])
      } else {
        result = context.fn()
      }
      delete context.fn
 
      return result
}

blind:

Function.prototype.myBind = function(context){
      // 判断是否是一个函数
      if(typeof this !== "function") {
        throw new TypeError("Not a Function")
      }
      // 保存调用bind的函数
      const _this = this 
      // 保存参数
      const args = Array.prototype.slice.call(arguments,1)
      // 返回一个函数
      return function F () {
        // 判断是不是new出来的
        if(this instanceof F) {
          // 如果是new出来的
          // 返回一个空对象,且使创建出来的实例的__proto__指向_this的prototype,且完成函数柯里化
          return new _this(...args,...arguments)
        }else{
          // 如果不是new出来的改变this指向,且完成函数柯里化
          return _this.apply(context,args.concat(...arguments))
        }
      } 
}

# 30. 字面量创建对象和 new 创建对象有什么区别,new 内部都实现了什么,手写一个 new(高级)

字面量:

  • 字面量创建对象更简单,方便阅读
  • 不需要作用域解析,速度更快

new 内部:

  • 创建一个新对象
  • 使新对象的__proto__指向原函数的prototype
  • 改变this指向(指向新的obj)并执行该函数,执行结果保存起来作为 result
  • 判断执行函数的结果是不是 null 或 Undefined,如果是则返回之前的新对象,如果不是则返回 result

手写new

// 手写一个new
function myNew(fn, ...args) {
    // 创建一个空对象
    let obj = {}
    // 使空对象的隐式原型指向原函数的显式原型
    obj.__proto__ = fn.prototype
    // this指向obj
    let result = fn.apply(obj, args)
    // 返回
    return result instanceof Object ? result : obj
}

# 31. 字面量new出来的对象和 Object.create(null)创建出来的对象有什么区别(中级)

  • 字面量和new创建出来的对象会继承 Object 的方法和属性,他们的隐式原型会指向 Object 的显式原型
  • Object.create(null)创建出来的对象原型为 null,作为原型链的顶端,自然也没有继承 Object 的方法和属性

# 32. 什么是作用域,什么是作用域链?(中级)

  • 规定变量和函数的可使用范围称作作用域
  • 每个函数都有一个作用域链,查找变量或者函数时,需要从局部作用域到全局作用域依次查找,这些作用域的集合称作作用域链

# 33. 什么是执行栈,什么是执行上下文?(中级)

执行上下文分为:

  • 全局执行上下文
    • 创建一个全局的 window 对象,并规定 this 指向 window,执行 js 的时候就压入栈底,关闭浏览器的时候才弹出
  • 函数执行上下文
    • 每次函数调用时,都会新创建一个函数执行上下文
    • 执行上下文分为创建阶段和执行阶段
      • 创建阶段:函数环境会创建变量对象:arguments 对象(并赋值)、函数声明(并赋值)、变量声明(不赋值),函数表达式声明(不赋值);会确定 this 指向;会确定作用域
      • 执行阶段:变量赋值、函数表达式赋值,使变量对象编程活跃对象
  • eval执行上下文

执行栈:

  • 首先栈特点:先进后出
  • 当进入一个执行环境,就会创建出它的执行上下文,然后进行压栈,当程序执行完成时,它的执行上下文就会被销毁,进行弹栈
  • 栈底永远是全局环境的执行上下文,栈顶永远是正在执行函数的执行上下文
  • 只有浏览器关闭的时候全局执行上下文才会弹出

# 34. 什么是闭包?闭包的作用?闭包的应用?(中级)

函数执行,形成私有的执行上下文,使内部私有变量不受外界干扰,起到保护和保存的作用

作用:

  • 保护
    • 避免命名冲突
  • 保存
    • 解决循环绑定引发的索引问题
  • 变量不会销毁
    • 可以使用函数内部的变量,使变量不会被垃圾回收机制回收

应用:

  • 设计模式中的单例模式
  • for循环中的保留i的操作
  • 防抖和节流
  • 函数柯里化

缺点

  • 会出现内存泄漏的问题

# 35. 什么是原型?什么是原型链?如何理解?(中级)

原型: 原型分为隐式原型和显式原型,每个对象都有一个隐式原型,它指向自己的构造函数的显式原型。

原型链: 多个__proto__组成的集合成为原型链

  • 所有实例的__proto__都指向他们构造函数的prototype
  • 所有的prototype都是对象,自然它的__proto__指向的是Object()prototype
  • 所有的构造函数的隐式原型指向的都是Function()的显示原型
  • Object 的隐式原型是 null

# 36. 说一说 JS 中的常用的继承方式有哪些?以及各个继承方式的优缺点(中级)

原型继承、组合继承、寄生组合继承、ES6 的 extend

原型继承:

// ----------------------方法一:原型继承
    // 原型继承
    // 把父类的实例作为子类的原型
    // 缺点:子类的实例共享了父类构造函数的引用属性   不能传参
 
    var person = {
      friends: ["a", "b", "c", "d"]
    }
 
    var p1 = Object.create(person)
 
    p1.friends.push("aaa")//缺点:子类的实例共享了父类构造函数的引用属性
 
    console.log(p1);
    console.log(person);//缺点:子类的实例共享了父类构造函数的引用属性

组合继承:

// ----------------------方法二:组合继承
    // 在子函数中运行父函数,但是要利用call把this改变一下,
    // 再在子函数的prototype里面new Father() ,使Father的原型中的方法也得到继承,最后改变Son的原型中的constructor
 
    // 缺点:调用了两次父类的构造函数,造成了不必要的消耗,父类方法可以复用
    // 优点可传参,不共享父类引用属性
    function Father(name) {
      this.name = name
      this.hobby = ["篮球", "足球", "乒乓球"]
    }
 
    Father.prototype.getName = function () {
      console.log(this.name);
    }
 
    function Son(name, age) {
      Father.call(this, name)
      this.age = age
    }
 
    Son.prototype = new Father()
    Son.prototype.constructor = Son
 
 
    var s = new Son("ming", 20)
 
    console.log(s);

寄生组合继承:

// ----------------------方法三:寄生组合继承
    function Father(name) {
      this.name = name
      this.hobby = ["篮球", "足球", "乒乓球"]
    }
 
    Father.prototype.getName = function () {
      console.log(this.name);
    }
 
    function Son(name, age) {
      Father.call(this, name)
      this.age = age
    }
 
    Son.prototype = Object.create(Father.prototype)
    Son.prototype.constructor = Son
 
    var s2 = new Son("ming", 18)
    console.log(s2);

extend:

// ----------------------方法四:ES6的extend(寄生组合继承的语法糖)
    //     子类只要继承父类,可以不写 constructor ,一旦写了,则在 constructor 中的第一句话
    // 必须是 super 。
 
    class Son3 extends Father { // Son.prototype.__proto__ = Father.prototype
      constructor(y) {
        super(200)  // super(200) => Father.call(this,200)
        this.y = y
      }
    }

# 37. 什么是内存泄漏?(中级)

内存泄露是指不再用的内存没有被及时释放出来,导致该段内存无法被使用就是内存泄漏。

# 38. 为什么会导致的内存泄漏?(中级)

内存泄漏指我们无法在通过js访问某个对象,而垃圾回收机制却认为该对象还在被引用,因此垃圾回收机制不会释放该对象,导致该块内存永远无法释放,积少成多,系统会越来越卡以至于崩溃。

# 39. 垃圾回收机制都有哪些策略?(中级)

  • 标记清除法
    • 垃圾回收机制获取根并标记他们,然后访问并标记所有来自它们的引用,然后在访问这些对象并标记它们的引用…如此递进结束后若发现有没有标记的(不可达的)进行删除,进入执行环境的不能进行删除
  • 引用计数法
    • 当声明一个变量并给该变量赋值一个引用类型的值时候,该值的计数+1,当该值赋值给另一个变量的时候,该计数+1,当该值被其他值取代的时候,该计数-1,当计数变为0的时候,说明无法访问该值了,垃圾回收机制清除该对象

# 40. 手写浅拷贝深拷贝(高级)

// ----------------------------------------------浅拷贝
    // 只是把对象的属性和属性值拷贝到另一个对象中
    var obj1 = {
      a: {
        a1: { a2: 1 },
        a10: { a11: 123, a111: { a1111: 123123 } }
      },
      b: 123,
      c: "123"
    }
    // 方式1
    function shallowClone1(o) {
      let obj = {}
 
      for (let i in o) {
        obj[i] = o[i]
      }
      return obj
    }
 
    // 方式2
    var shallowObj2 = { ...obj1 }
 
    // 方式3
    var shallowObj3 = Object.assign({}, obj1)
 
    let shallowObj = shallowClone1(obj1);
 
    shallowObj.a.a1 = 999
    shallowObj.b = true
 
    console.log(obj1);  //第一层的没有被改变,一层以下就被改变了
 
 
 
    // ----------------------------------------------深拷贝
 
    // 简易版  
    function deepClone(o) {
      let obj = {}
      for (var i in o) {
        // if(o.hasOwnProperty(i)){
        if (typeof o[i] === "object") {
          obj[i] = deepClone(o[i])
        } else {
          obj[i] = o[i]
        }
        // }
      }
      return obj
    }
 
 
    var myObj = {
      a: {
        a1: { a2: 1 },
        a10: { a11: 123, a111: { a1111: 123123 } }
      },
      b: 123,
      c: "123"
    }
 
    var deepObj1 = deepClone(myObj)
    deepObj1.a.a1 = 999
    deepObj1.b = false
    console.log(myObj);
 
 
 
    // 简易版存在的问题:参数没有做检验,传入的可能是 Array、null、regExp、Date
    function deepClone2(o) {
      if (Object.prototype.toString.call(o) === "[object Object]") {  //检测是否为对象
        let obj = {}
        for (var i in o) {
          if (o.hasOwnProperty(i)) {
            if (typeof o[i] === "object") {
              obj[i] = deepClone(o[i])
            } else {
              obj[i] = o[i]
            }
          }
        }
        return obj
      } else {
        return o
      }
    }
 
    function isObject(o) {
      return Object.prototype.toString.call(o) === "[object Object]" || Object.prototype.toString.call(o) === "[object Array]"
    }
 
    // 继续升级,没有考虑到数组,以及ES6中的map、set、weakset、weakmap
    function deepClone3(o) {
      if (isObject(o)) {//检测是否为对象或者数组
        let obj = Array.isArray(o) ? [] : {}
        for (let i in o) {
          if (isObject(o[i])) {
            obj[i] = deepClone(o[i])
          } else {
            obj[i] = o[i]
          }
        }
        return obj
      } else {
        return o
      }
    }
 
 
    // 有可能碰到循环引用问题  var a = {}; a.a = a; clone(a);//会造成一个死循环
    // 循环检测
    // 继续升级
    function deepClone4(o, hash = new map()) {
      if (!isObject(o)) return o//检测是否为对象或者数组
      if (hash.has(o)) return hash.get(o)
      let obj = Array.isArray(o) ? [] : {}
 
      hash.set(o, obj)
      for (let i in o) {
        if (isObject(o[i])) {
          obj[i] = deepClone4(o[i], hash)
        } else {
          obj[i] = o[i]
        }
      }
      return obj
    }
 
    // 递归易出现爆栈问题
    //  将递归改为循环,就不会出现爆栈问题了
    var a1 = { a: 1, b: 2, c: { c1: 3, c2: { c21: 4, c22: 5 } }, d: 'asd' };
    var b1 = { b: { c: { d: 1 } } }
    function cloneLoop(x) {
      const root = {};
      // 栈 
      const loopList = [  //->[]->[{parent:{a:1,b:2},key:c,data:{ c1: 3, c2: { c21: 4, c22: 5 } }}]
        {
          parent: root,
          key: undefined,
          data: x,
        }
      ];
      while (loopList.length) {
        // 深度优先
        const node = loopList.pop();
        const parent = node.parent; //{} //{a:1,b:2}
        const key = node.key; //undefined //c
        const data = node.data; //{ a: 1, b: 2, c: { c1: 3, c2: { c21: 4, c22: 5 } }, d: 'asd' }  //{ c1: 3, c2: { c21: 4, c22: 5 } }}
        // 初始化赋值目标,key 为 undefined 则拷贝到父元素,否则拷贝到子元素
        let res = parent; //{}->{a:1,b:2,d:'asd'} //{a:1,b:2}->{}
        if (typeof key !== 'undefined') {
          res = parent[key] = {};
        }
        for (let k in data) {
          if (data.hasOwnProperty(k)) {
            if (typeof data[k] === 'object') {
              // 下一次循环 
              loopList.push({
                parent: res,
                key: k,
                data: data[k],
              })
            } else {
              res[k] = data[k];
            }
          }
        }
      }
      return root
    }
 
 
    function deepClone5(o) {
      let result = {}
      let loopList = [
        {
          parent: result,
          key: undefined,
          data: o
        }
      ]
 
      while (loopList.length) {
        let node = loopList.pop()
        let { parent, key, data } = node
        let anoPar = parent
        if (typeof key !== 'undefined') {
          anoPar = parent[key] = {}
        }
 
        for (let i in data) {
          if (typeof data[i] === 'object') {
            loopList.push({
              parent: anoPar,
              key: i,
              data: data[i]
            })
          } else {
            anoPar[i] = data[i]
          }
        }
      }
      return result
    }
 
 
    let cloneA1 = deepClone5(a1)
    cloneA1.c.c2.c22 = 5555555
    console.log(a1);
    console.log(cloneA1);
 
 
    // ------------------------------------------JSON.stringify()实现深拷贝
 
    function cloneJson(o) {
      return JSON.parse(JSON.stringify(o))
    }
 
    // let obj = { a: { c: 1 }, b: {} };
    // obj.b = obj;
    // console.log(JSON.parse(JSON.stringify(obj))) // 报错 // Converting circular structure to JSON

深拷贝能使用hash递归的方式写出来就可以了 不过技多不压身,推荐还是看一看使用 while 实现深拷贝方法

# 41. 为什么JS是单线程的?(中级)

因为 JS 里面有可视的 Dom,如果是多线程的话,这个线程正在删除 DOM 节点,另一个线程正在编辑 Dom 节点,导致浏览器不知道该听谁的

# 42. 如何实现异步编程?(中级)

回调函数

# 43. Generator 是怎么样使用的以及各个阶段的变化如何?(高级)

  • 首先生成器是一个函数,用来返回迭代器的
  • 调用生成器后不会立即执行,而是通过返回的迭代器来控制这个生成器的一步一步执行的
  • 通过调用迭代器的 next 方法来请求一个一个的值,返回的对象有两个属性,一个是 value,也就是值;另一个是done,是个布尔类型,done为true 说明生成器函数执行完毕,没有可返回的值了
  • donetrue后继续调用迭代器的 next 方法,返回值的valueundefined

状态变化:

  • 每当执行到yield属性的时候,都会返回一个对象
  • 这时候生成器处于一个非阻塞的挂起状态
  • 调用迭代器的 next 方法的时候,生成器又从挂起状态改为执行状态,继续上一次的执行位置执行
  • 直到遇到下一次yield依次循环
  • 直到代码没有yield了,就会返回一个结果对象donetruevalueundefined

# 44. 说说 Promise 的原理?你是如何理解 Promise 的?(中级)

  • 做到会写简易版的 promise 和 all 函数就可以
class MyPromise2 {
      constructor(executor) {
        // 规定状态
        this.state = "pending"
        // 保存 `resolve(res)` 的res值
        this.value = undefined
        // 保存 `reject(err)` 的err值
        this.reason = undefined
        // 成功存放的数组
        this.successCB = []
        // 失败存放的数组
        this.failCB = []
 
 
        let resolve = (value) => {
          if (this.state === "pending") {
            this.state = "fulfilled"
            this.value = value
            this.successCB.forEach(f => f())
          }
        }
        let reject = (reason) => {
          if (this.state === "pending") {
            this.state = "rejected"
            this.value = value
            this.failCB.forEach(f => f())
          }
        }
 
        try {
          // 执行
          executor(resolve, reject)
        } catch (error) {
          // 若出错,直接调用reject
          reject(error)
        }
      }
      then(onFulfilled, onRejected) {
        if (this.state === "fulfilled") {
          onFulfilled(this.value)
        }
        if (this.state === "rejected") {
          onRejected(this.value)
        }
        if (this.state === "pending") {
          this.successCB.push(() => { onFulfilled(this.value) })
          this.failCB.push(() => { onRejected(this.reason) })
        }
      }
    }
 
 
    Promise.all = function (promises) {
      let list = []
      let count = 0
      function handle(i, data) {
        list[i] = data
        count++
        if (count == promises.length) {
          resolve(list)
        }
      }
      return Promise((resolve, reject) => {
        for (let i = 0; i < promises.length; i++) {
          promises[i].then(res => {
            handle(i, res)
          }, err => reject(err))
        }
      })
    }

# 45. 以下代码的执行顺序是什么?(中级)

async function async1() {
   console.log('async1 start')
   await async2()
   console.log('async1 end')
  }
  async function async2() {
   console.log('async2')
  }
  async1()
  console.log('script start')
 
//执行到await时,如果返回的不是一个promise对象,await会阻塞下面代码(当前async代码块的代码),会先执行async外的同步代码(在这之前先看看await中函数的同步代码,先把同步代码执行完),等待同步代码执行完之后,再回到async内部继续执行
//执行到await时,如果返回的是一个promise对象,await会阻塞下面代码(当前async代码块的代码),会先执行async外的同步代码(在这之前先看看await中函数的同步代码,先把同步代码执行完),等待同步代码执行完之后,再回到async内部等promise状态达到fulfill的时候再继续执行下面的代码
//所以结果为
//async1 start
//async2
//script start
//async1 end

# 46. 宏任务和微任务都有哪些?(高级)

  • 宏任务:scriptsetTimeOutsetIntervalsetImmediate
  • 微任务:promise.then,process.nextTickObject.observeMutationObserver
  • 注意:Promise 是同步任务

# 47. 宏任务和微任务都是怎样执行的?(高级)

  • 执行宏任务 script
  • 进入 script 后,所有的同步任务主线程执行
  • 所有宏任务放入宏任务执行队列
  • 所有微任务放入微任务执行队列
  • 先清空微任务队列
  • 再取一个宏任务,执行,再清空微任务队列
  • 依次循环

例题1

setTimeout(function(){
    console.log('1')
});
new Promise(function(resolve){
    console.log('2');
    resolve();
}).then(function(){
    console.log('3')
});
console.log('4');
new Promise(function(resolve){
    console.log('5');
    resolve();
}).then(function(){
    console.log('6')
});
setTimeout(function(){
    console.log('7')
});
function bar(){
    console.log('8')
    foo()
}
function foo(){
    console.log('9')
}
console.log('10')
bar()
 

解析

  1. 首先浏览器执行Js代码由上至下顺序,遇到 setTimeout,把 setTimeout 分发到宏任务 Event Queue中

  2. new Promise 属于主线程任务直接执行打印2

  3. Promis 下的 then 方法属于微任务,把 then 分到微任务 Event Queue 中

  4. console.log(‘4’)属于主线程任务,直接执行打印4

  5. 又遇到 new Promise 也是直接执行打印5,Promise 下到 then 分发到微任务 Event Queue 中

  6. 又遇到 setTimouse 也是直接分发到宏任务 Event Queue 中,等待执行

  7. console.log(‘10’)属于主线程任务直接执行

  8. 遇到 bar() 函数调用,执行构造函数内到代码,打印8,在 bar 函数中调用 foo 函数,执行 foo 函数到中代码,打印9

  9. 主线程中任务执行完后,就要执行分发到微任务 Event Queue 中代码,实行先进先出,所以依次打印3,6

  10. 微任务 Event Queue 中代码执行完,就执行宏任务 Event Queue 中代码,也是先进先出,依次打印1,7

  • 最终结果:2,4,5,10,8,9,3,6,1,7

例题2

setTimeout(() => {
      console.log('1');
      new Promise(function (resolve, reject) {
        console.log('2');
        setTimeout(() => {
          console.log('3');
        }, 0);
        resolve();
      }).then(function () {
        console.log('4')
      })
    }, 0);
    console.log('5'); //5 7 10 8 1 2 4 6 3
    setTimeout(() => {
      console.log('6');
    }, 0);
    new Promise(function (resolve, reject) {
      console.log('7');
      // reject();
      resolve();
    }).then(function () {
      console.log('8')
    }).catch(function () {
      console.log('9')
    })
    console.log('10');

运行结果: 5 7 10 8 1 2 4 6 3

# 48. 变量和函数怎么进行提升的?优先级是怎么样的?(中级)

  • 对所有函数声明进行提升(除了函数表达式和箭头函数),引用类型的赋值

    • 开辟堆空间
    • 存储内容
    • 将地址赋给变量
  • 对变量进行提升,只声明,不赋值,值为undefined

# 49. var let const 有什么区别?(初级)

  • var

    • var声明的变量可进行变量提升,let 和 const 不会
    • var可以重复声明
    • var在非函数作用域中定义是挂在到 window 上的
  • let

    • let 声明的变量只在局部起作用
    • let 防止变量污染
    • 不可在声明
  • const

    • 具有 let 的所有特征

    • 不可被改变

      • 如果使用 const 声明的是对象的话,是可以修改对象里面的值的

# 50. 箭头函数和普通函数的区别?箭头函数可以当做构造函数 new 吗?(中级)

  • 箭头函数是普通函数的简写,但是它不具备很多普通函数的特性

  • 第一点,this 指向问题,箭头函数的 this 指向它定义时所在的对象,而不是调用时所在的对象

  • 不会进行函数提升

  • 没有 arguments 对象,不能使用 arguments,如果要获取参数的话可以使用rest运算符

  • 没有yield属性,不能作为生成器 Generator 使用

  • 不能 new

    • 没有自己的 this,不能调用 call 和 apply

    • 没有 prototype,new 关键字内部需要把新对象的_proto_指向函数的 prototype

# 51. 说说你对代理的理解(中级)

  • 代理有几种定义方式

    • 字面量定义,对象里面的 get 和 set
    • 类定义, class 中的getset
    • Proxy 对象,里面传两个对象,第一个对象是目标对象 target,第二个对象是专门放 get 和 set 的handler对象。Proxy 和上面两个的区别在于 Proxy 专门对对象的属性进行 get 和 set
  • 代理的实际应用有

    • Vue 的双向绑定 vue2 用的是Object.defineProperty,vue3 用的是proxy
    • 校验值
    • 计算属性值( get 的时候加以修饰)

# 52. 为什么要使用模块化?都有哪几种方式可以实现模块化,各有什么特点?(中级)

  • 为什么要使用模块化

    • 防止命名冲突
    • 更好的分离,按需加载
    • 更好的复用性
    • 更高的维护性

# 53. exportsmodule.exports有什么区别?(中级)

  • 导出方式不一样

    • exports.xxx='xxx'
    • module.export = {}
  • exportsmodule.exports的引用,两个指向的是用一个地址,而 require 能看到的只有module.exports

# 54. JS模块包装格式有哪些?(高级)

  • commonjs

    • 同步运行,不适合前端
  • AMD

    • 异步运行

    • 异步模块定义,主要采用异步的方式加载模块,模块的加载不影响后面代码的执行。所有依赖这个模块的语句都写在一个回调函数中,模块加载完毕,再执行回调函数

  • CMD

    • 异步运行
    • seajs 规范

# 55. ES6和commonjs的区别(中级)

  • commonjs模块输出的是值的拷贝,而 ES6 输出的值是值的引用
  • commonjs是在运行时加载,是一个对象,ES6 是在编译时加载,是一个代码块
  • commonjs的 this 指向当前模块,ES6的 this 指向 undefined

# 56. 冒泡算法排序(中级)

// 冒泡排序
    /* 1.比较相邻的两个元素,如果前一个比后一个大,则交换位置。
   2.第一轮的时候最后一个元素应该是最大的一个。
   3.按照步骤一的方法进行相邻两个元素的比较,这个时候由于最后一个元素已经是最大的了,所以最后一个元素不用比较。 */
    function bubbleSort(arr) {
      for (var i = 0; i < arr.length; i++) {
        for (var j = 0; j < arr.length; j++) {
          if (arr[j] > arr[j + 1]) {
            var temp = arr[j]
            arr[j] = arr[j + 1]
            arr[j + 1] = temp
          }
        }
      }
    }
 
    var Arr = [3, 5, 74, 64, 64, 3, 1, 8, 3, 49, 16, 161, 9, 4]
    console.log(Arr, "before");
    bubbleSort(Arr)
    console.log(Arr, "after");
 

# 57. 快速排序(中级)

/*
    快速排序是对冒泡排序的一种改进,第一趟排序时将数据分成两部分,一部分比另一部分的所有数据都要小。
    然后递归调用,在两边都实行快速排序。  
    */
    
    function quickSort(arr) {
      if (arr.length <= 1) {
        return arr
      }
      var middle = Math.floor(arr.length / 2)
      var middleData = arr.splice(middle, 1)[0]
 
      var left = []
      var right = []
      
      for (var i = 0; i < arr.length; i++) {
        if (arr[i] < middleData) {
          left.push(arr[i])
        } else {
          right.push(arr[i])
        }
      }
 
      return quickSort(left).concat([middleData], quickSort(right))
    }
 
    var Arr = [3, 5, 74, 64, 64, 3, 1, 8, 3, 49, 16, 161, 9, 4]
    console.log(Arr, "before");
    var newArr = quickSort(Arr)
    console.log(newArr, "after");

# 58. 插入排序(中级)

function insertSort(arr) {
      // 默认第一个排好序了
      for (var i = 1; i < arr.length; i++) {
        // 如果后面的小于前面的直接把后面的插到前边正确的位置
        if (arr[i] < arr[i - 1]) {
          var el = arr[i]
          arr[i] = arr[i - 1]
          var j = i - 1
          while (j >= 0 && arr[j] > el) {
            arr[j+1] = arr[j]
            j--
          }
          arr[j+1] = el
        }
      }
    }
 
    var Arr = [3, 5, 74, 64, 64, 3, 1, 8, 3, 49, 16, 161, 9, 4]
    console.log(Arr, "before");
    insertSort(Arr)
    console.log(Arr, "after");

# 59. 是否回文(中级)

function isHuiWen(str) {
    return str == str.split("").reverse().join("")
}
console.log(isHuiWen("mnm")); 

# 60. 正则表达式,千分位分隔符(中级)

function thousand(num) {
    return (num+"").replace(/\d(?=(\d{3})+$)/g, "$&,")
}
console.log(thousand(123456789));

# 61. 斐波那契数列(中级)

// num1前一项
    // num2当前项
    function fb(n, num1 = 1, num2 = 1) {
      if(n == 0) return 0
      if (n <= 2) {
        return num2
      } else {
        return fb(n - 1, num2, num1 + num2)
      }
    }

# 62. 数组去重的方式(中级)

var arr = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
 
    // 最low1
    let newArr2 = []
    for (let i = 0; i < arr.length; i++) {
      if (!newArr2.includes(arr[i])) {
        newArr2.push(arr[i])
      }
    }
    console.log(newArr2);
    // 最low2
    let arr2 = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
    for (let i = 0; i < arr2.length; i++) {
      var item = arr2[i]
      for (let j = i + 1; j < arr2.length; j++) {
        var compare = arr2[j];
        if (compare === item) {
          arr2.splice(j, 1)
          j--
        }
      }
    }
    console.log(arr2);
 
 
    // 基于对象去重
    let arr3 = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
    let obj = {}
    for (let i = 0; i < arr3.length; i++) {
 
      let item = arr3[i]
      if (obj[item]) {
        arr3[i] = arr3[arr3.length - 1]
        arr3.length--
        i--
        continue;
      }
      obj[item] = item
 
    }
    console.log(arr3);
    console.log(obj);
 
    // 利用Set
    let newArr1 = new Set(arr)
    console.log([...newArr1]);
 
 
    let arr4 = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
 
    //利用reduce
    newArr4 = arr4.reduce((prev, curr) => prev.includes(curr)? prev : [...prev,curr],[])
    console.log(newArr4);
    console.log(document);

# 63. 事件冒泡和事件捕捉有什么区别?(中级)

  • 事件冒泡

    • 在 addEventListener 中的第三属性设置为 false (默认)。
    • 从下至上(儿子至祖宗)执行。
  • 事件捕捉

    • 在 addEventListener 中的第三属性设置为 true。

    • 从上至下(祖宗到儿子)执行。

# 64. 什么是防抖?什么是节流?手写一个(高级)

  • 防抖
    • n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时。
  • 节流
    • n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效。
// ---------------------------------------------------------防抖函数
    function debounce(func, delay) {
      let timeout
      return function () {
        let arg = arguments
        if (timeout) clearTimeout(timeout)
        timeout = setTimeout(() => {
          func(arg)
        }, delay);
      }
    }
 
    // ---------------------------------------------------------立即执行防抖函数
    function debounce2(fn, delay) {
      let timer
 
      return function () {
        let args = arguments
        if (timer) clearTimeout(timer)
 
 
        let callNow = !timer
        timer = setTimeout(() => {
          timer = null
        }, delay);
        if (callNow) { fn(args) }
      }
    }
    // ---------------------------------------------------------立即执行防抖函数+普通防抖
    function debounce3(fn, delay, immediate) {
      let timer
 
      return function () {
        let args = arguments
        let _this = this
        if (timer) clearTimeout(timer)
 
        if (immediate) {
          let callNow = !timer
          timer = setTimeout(() => {
            timer = null
          }, delay);
 
          if (callNow) { fn.apply(_this, args) }
        } else {
          timeout = setTimeout(() => {
            func.apply(_this, arguments)
          }, delay);
        }
      }
    }
 
    // ---------------------------------------------------------节流 ,时间戳版
 
    function throttle(fn, wait) {
 
      let previous = 0
      return function () {
        let now = Date.now()
        let _this = this
        let args = arguments
        if (now - previous > wait) {
          fn.apply(_this, arguments)
          previous = now
        }
      }
    }
 
    // ---------------------------------------------------------节流 ,定时器版
    function throttle2(fn, wait) {
      let timer
      return function () {
        let _this = this
        let args = arguments
        if (!timer) {
          timer = setTimeout(() => {
            timer = null
            fn.apply(_this, arguments)
          }, wait);
        }
      }
    }

# 65. 函数柯里化原理是什么?(高级)

function add() {
    var args = Array.prototype.slice.call(arguments)

    var adder = function () {
        args.push(...arguments)
        return adder
    }

    adder.toString = function () {
        return args.reduce((prev, curr) => {
            return prev + curr
        }, 0)
    }

    return adder
}
 
let a = add(1, 2, 3)
let b = add(1)(2)(3)
console.log(a)
console.log(b)
console.log(add(1, 2)(3));
console.log(Function.toString)

# 66. 什么是requestAnimationFrame?(高级)

  • requestAnimationFrame 请求数据帧可以用做动画执行。
  • 可以自己决定什么时机调用该回调函数。
  • 能保证每次频幕刷新的时候只被执行一次。
  • 页面被隐藏或者最小化的时候暂停执行,返回窗口继续执行,有效节省 CPU。
var s = 0
function f() {
    s++
    console.log(s);
    if (s < 999) {
        window.requestAnimationFrame(f)
    }
}
window.requestAnimationFrame(f)

# 67. js常见的设计模式(高级)

  • 单例模式、工厂模式、构造函数模式、发布订阅者模式、迭代器模式、代理模式。

  • 单例模式

    • 不管创建多少个对象都只有一个实例:

      var Single = (function () {
            var instance = null
            function Single(name) {
              this.name = name
            }
            return function (name) {
              if (!instance) {
                instance = new Single()
              }
              return instance
            }
          })()
       
          var oA = new Single('hi')
          var oB = new Single('hello')
          console.log(oA);
          console.log(oB);
          console.log(oB === oA);
      
      
  • 工厂模式

    • 代替new创建一个对象,且这个对象想工厂制作一样,批量制作属性相同的实例对象(指向不同):

      function Animal(o) {
          var instance = new Object()
          instance.name = o.name
          instance.age = o.age
          instance.getAnimal = function () {
              return "name:" + instance.name + " age:" + instance.age
          }
          return instance
      }
      
      var cat = Animal({name:"cat", age:3})
      console.log(cat);
      
  • 构造函数模式

  • 发布订阅者模式

    class Watcher {
          // name模拟使用属性的地方
          constructor(name, cb) {
            this.name = name
            this.cb = cb
          }
          update() {//更新
            console.log(this.name + "更新了");
            this.cb() //做出更新回调
          }
        }
     
        class Dep {//依赖收集器
          constructor() {
            this.subs = []
          }
          addSubs(watcher) {
            this.subs.push(watcher)
          }
          notify() {//通知每一个观察者做出更新
            this.subs.forEach(w => {
              w.update()
            });
          }
        }
     
        // 假如现在用到age的有三个地方
        var w1 = new Watcher("我{{age}}了", () => { console.log("更新age"); })
        var w2 = new Watcher("v-model:age", () => { console.log("更新age"); })
        var w3 = new Watcher("I am {{age}} years old", () => { console.log("更新age"); })
     
        var dep = new Dep()
        dep.addSubs(w1)
        dep.addSubs(w2)
        dep.addSubs(w3)
     
     
        // 在Object.defineProperty 中的 set中运行
        dep.notify()
    
  • 代理模式

  • 迭代器模式

# 68. JS 性能优化的方式有哪些?(中级)

  • 垃圾回收。
  • 闭包中的对象清楚。
  • 防抖节流。
  • 分批加载(setInterval,加载10000个节点)。
  • 事件委托。
  • 少用 with。
  • requestAnimationFrame 的使用。
  • script 标签中的 defe r和 async。
  • CDN。

# 69. js延迟加载的方式有哪些?(中级)

defer和async、动态创建DOM方式(创建script,插入到DOM中,加载完毕后callBack)、按需异步载入js

# 70. 请解释一下 JavaScript的同源策略。(高级)

概念:同源策略是客户端脚本(尤其是Javascript)的重要的安全度量标准。它最早出自Netscape Navigator2.0,其目的是防止某个文档或脚本从多个不同源装载。 这里的同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议。

var xmlHttp = new XMLHttpRequest(); xmlHttp.open('GET','demo.php','true'); xmlHttp.send() xmlHttp.onreadystatechange = function(){ if(xmlHttp.readyState === 4 & xmlHttp.status=== 200){ } }指一段脚本只能读取来自同一来源的窗口和文档的属性。

指一段脚本只能读取来自同一来源的窗口和文档的属性。