深入理解JS | 青训营笔记

95 阅读6分钟

JS的基本概念

诞生

JS诞生于1995年,由Brendan Eich开发,主要借鉴了c语言的基本语法、Java语言的数据类型和内存管理,Scheme语言将函数提升到“第一等公民”的地位,借鉴Self语言,使用基于原型的继承机制。

数据类型:

基本数据类型

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

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

  • Object
  • Array
  • Date
  • Function
  • RegExp

基本数据类型的数据直接存储在栈中;而引用数据类型的数据存储在堆中,在栈中保存数据的引用地址,这个引用地址指向的是对应的数据,以便快速查找到堆内存中的对象。

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

变量提升

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

  • var有变量提升,const、let没有变量提升,提前访问会报错;
  • function函数可以先调用再定义;
  • 赋值给变量的函数无法提前调用;
  • 对变量进行提升,只声明,不赋值,值为undefined;

JS的执行机制

同步: 前一个任务执行完之后再执行另一个任务,执行顺序就是排列顺序

异步: 在做一个任务的同时做另一个任务 同步任务(web API)都在主线上执行,形成一个执行线,异步任务是通过回调函数实现的,只要有三种类型:普通事件(click、resize等)、资源加载事件(load、error等)、定时器(sentInterval、setTimeout等) 异步任务相关回调函数添加到任务队列中(消息队列)

S执行机制:

  • 先执行主执行线上的任务,遇到回调函数,则把回调函数放入到任务队列中,不执行,接着执行主执行线上的其他任务
  • 主执行线上的所有同步任务执行完毕之后,系统会依次读取任务队列中的异步任务,于是异步任务进入主执行线,开始执行。
  • 将回调函数放入到任务队列的过程中,会进行异步进程处理,就是如果是一个点击事件,如果没有点击在,则异步进程处理不会将这个点击事件的回调函数放入到任务队列

例如如果是定时器,需要等待设定的定时时间才会放入到任务队列中。 由于主执行线(主线程)不断地重复获取任务、执行任务、再获取任务。再执行,所以这种机制被称为事件循环(eventLoop)

创建执行上下文的时候做了什么?

  • 绑定This;
  • 创建此法环境;
  • 创建变量环境;

宏任务、微任务:

先同步再异步,执行同步任务过程中,遇到微任务就放到微任务队列,宏任务放到宏任务队列; 执行完同步任务,将微任务调入主线程执行,清空队列; 再从宏任务队列中第一个宏任务出来,又分为同步任务、微任务、宏任务,按顺序执行; 直到最后一个宏任务执行完毕。

宏任务: script、setTimeOut、setInterval、setImmediate

微任务: promise.then、process.nextTick、Object.observe、MutationObserver

同步任务: console那些从上到下执行的,new Promise

JS的进阶知识点

垃圾回收机制

首先先理解什么是内存泄漏?

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

为什么会导致内存泄漏?

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

垃圾回收机制有哪些策略?

标记清除法

垃圾回收机制获取根并标记他们,然后访问并标记所有来自它们的引用,然后在访问这些对象并标记它们的引用…如此递进结束后若发现有没有标记的(不可达的)进行删除,进入执行环境的不能进行删除。

引用计数法

当声明一个变量并给该变量赋值一个引用类型的值时候,该值的计数+1,当该值赋值给另一个变量的时候,该计数+1,当该值被其他值取代的时候,该计数-1,当计数变为0的时候,说明无法访问该值了,垃圾回收机制清除该对象 缺点: 当两个对象循环引用的时候,引用计数无计可施。如果循环引用多次执行的话,会造成崩溃等问题。所以后来被标记清除法取代。

浅拷贝、深拷贝

浅拷贝: 将原对象/数组的引用直接赋给新对象/数组,对基本对象类型的值的拷贝,对引用类型的地址的拷贝,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化

深拷贝: 创建一个新对象/数组,将原对象/数组的值复制过来,两个对象修改其中任意的值另一个值不会改变。在另一个对象中开辟对应的空间,空间大小占用一样但是位置不同

1、浅拷贝

第一层原对象改变,不会改变浅拷贝后的对象; 第二层原对象改变,会改变浅拷贝后的对象

对象:

  • for in 循环遍历复制

     function shallowClone1(o) {
          let obj = {}
    
          for (let i in o) {
            obj[i] = o[i]
          }
          return obj
      }
    
  • 三点运算符

    var shallowObj2 = { ...obj1 }

  • assign()方法

    var shallowObj3 = Object.assign({}, obj1)

数组

  • Array.prototype.slice(0),

  • Array.prototype.concat() 2、深拷贝

  • for循环

    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
    }
    //升级版1,考虑了对象、数组类型
    function isObject(o) {
                return Object.prototype.toString.call(o) === "[object Object]" || Object.prototype.toString.call(o) === "[object Array]"
            }
    
    function deepClone(o) {
        if (isObject(o)) {
            let obj = Array.isArray(0) ? [] : {};
            for (let i in o) {
                if (isObject(o[i])) {
                    obj[i] = deepClone(o[i])
                } else obj[i] = o[i]
            }
            return obj
        } else return o;
    }
    
  • JSON.stringify()

    function cloneJson(o) {

    return JSON.parse(JSON.stringify(o))

    }

防抖、节流

1、防抖

  • n秒后执行该事件,若在n秒内被重复触发,则重新计时
  • 应用:多次点击,造成多次提交,每次点击前都重新计时
let timer;  //闭包,因为每次都不需要重新定义延时时间,只需要初始化一次,且每次调用只是不断赋值return function (){

  ​          	let context = this;      //改变this指向let args = arguments;   //增加参数给func使用clearTimeout(timer);     //重新计时

  ​          	timer = setTimeout(() => {

  ​                 	func.apply(context,args)    //修改this指向触发事件的DOM,原先是window

  ​          	}, delay);

  ​          }

  ​     }

  ​    btn.addEventListener('click',debounce(fun,1000))

2、节流

设置时间间隔,在时间间隔内,再次触发的事件会被忽略

   let timer;
   return function() {
   	if (timer) return;
       let context = this;
       let args = arguments;
       timer = setTimeout(() => {
           fn.apply(context, args);
           timer = null;
        }, delay)
    }
}