JS 常见知识点 (原理篇)

堆和栈

内存: 是计算机的运行空间
内存颗粒: 内存中分为独立的空间,空间中有独立的区域,其中最小的叫内存颗粒
内存颗粒中分为两个区域 栈 堆,

栈和堆默认关系: 一对一,多对一(两个栈中的地址对应一个堆的空间)
image.png
栈:
1. 保存的是 对象的(地址),函数调用时传递的是变量的(值)
2 . 空间小,稳定,里面都是地址 所以不可被修改
3. 先进后出

 js主线程的函数执行都压在这里

堆:
1. 保存的是值,数据
2. 空间大,里面的是值 所以可被修改。
3. 先进先出

垃圾回收就是检查这里
var a = 10
var b = a
b = 20
console.log(a)   //10
console.log(b)   //20
// 基本类型 (值传递的数据)
// 复制的就是值
// 除了对象和函数
var o ={name:'admin'}
var o2 = o 
o2.name = 'root'
console.log(o)    //root
console.log(o2)   //root
//复杂类型 (引用类型,引用传递的数据)
//复制的是地址,同一个地址,指向同一个值
//对象和函数

数据类型分类:

值传递:(基本数据)(栈)
内存中,值就是地址,地址就是值

引用传递:(复杂数据)(堆)
内存中,地址就是地址,值就是值

引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。
当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体

深浅拷贝

1.深浅拷贝的区别

浅拷贝

只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
修改新对象会改到原对象。

深拷贝

会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

2.实现深拷贝的方法

(json)实现深拷贝

let obj={
    name:'zs',
    age:18
}
obj2 =  JSON.parse( JSON.stringify(obj) )  

obj.age = 12
console.log(obj)   //name:'zs',age:18
console.log(obj2)  //name:'zs',age:12

// JSON不支持NaN,Infinity,甚至精确的浮点数,更别说循环引用和function了。
// 如果要深拷贝的对象属性值为undefined或者是function的时候,会被过滤掉!

(for-in遍历)实现深拷贝

let obj={
    name:'zs',
    age:18
}
let obj3 = {}
for(let attr in obj){
    obj3[sttr] = obj[attr]
}
obj.age = 12
console.log(obj)   //name:'zs',age:18
console.log(obj3)  //name:'zs',age:12

函数库lodash的_.cloneDeep方法

var _ = require('lodash')
var obj = {
  a: {
    c: 2,
    d: [9, 8, 7]
  },
  b: 4
}
var obj1 = _.cloneDeep(obj)
console.log(obj === obj1);//false

递归实现

function deepClone(obj){
    let objClone = Array.isArray(obj)?[]:{};
    if(obj && typeof obj==="object"){
        for(key in obj){
            if(obj.hasOwnProperty(key)){
                //判断obj子元素是否为对象,如果是,递归复制
                if(obj[key]&&typeof obj[key] ==="object"){
                    objClone[key] = deepClone(obj[key]);
                }else{
                    //如果不是,简单复制
                    objClone[key] = obj[key];
                }
            }
        }
    }
    return objClone;
}    
let a=[1,2,3,4],
    b=deepClone(a);
a[0]=2;
console.log(a,b);

(Object.assign)实现深拷贝

//对象的合并
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
如果对象的属性值为简单类型(string,number),得到的新对象为深拷贝;
如果属性值为对象或其他引用类型,那对于这个对象而言其实是浅拷贝的,

(…)实现深拷贝

let obj={
    name:'zs',
    age:18
}
let obj1 = {...obj}
obj.age = 12
console.log(obj)   //name:'zs',age:18
console.log(obj1)  //name:'zs',age:12
如果只是一层数组或是对象,其元素只是简单类型的元素,那么属于深拷贝

如果数组或对象中的元素是引用类型的元素,那么就是浅拷贝

作用域

对象类型
global/window全局作用域
{ }块级作用域 es6
this动态作用域

举例:

全局:  {}内未定义的,当我们打开网页,自动生成全局作用域

局部:  函数 或{}内部    
       从外层的作用域无法直接访问函数内部的作用域!    
       如果想读取函数内的变量,必须借助 return 或者闭包。

作用域对变量的限制:(作用域链)

1.变量只能在自己的作用域使用。   
2.作用域的变量,可以往上查找,使用上级作用域的变量,
不能往下查找,不能使用下级作用域的变量。
 (局部有上下级之分,只能从里往上不能往下)

作用域的赋值:

 先自己,有-直接赋值,没有-再往上,   
 最后全局   
 如果全局没有,则给他定义为全局变量,再给他赋值    

变量提升

就是把所有的变量声明提升到当前的作用域最前面,
(把var 提升到最前面(不赋值),剩下的依次罗列)

var sco ="aaa";
function fn(){
  console.log(sco);
  var sco ="bbb"  
}
fn(); //undefined

上面的代码输出是undefined,这是因为局部变量sco 变量提升了,等效于下面

var sco ="aaa";
function fn(){
  var sco;
  console.log(sco);
  sco ="bbb"  
}
fn(); //undefined

for循环中let和var的区别

for中var声明的是全局变量,由于变量提升的机制,var命令只在最初执行一次,后面的都是覆盖执行,所以最后显示的是循环完了的值
let不存在变量提升,所以每循环一次就会声明一个新变量

  var li =document.querySelectorAll('li')
  // 使用var 无论使用哪一个输出都是长度
   for(var i=0; i<li.length;i++){
        li[i].onclick=function(){
            console.log(i);
        }
    }
    // 使用let 点击哪一个则对应哪一个的下标
    for(let i=0; i<li.length;i++){
        li[i].onclick=function(){
            console.log(i);
        }
    }

闭包:

闭包是指有权访问另一个函数作用域中变量的函数,简单理解就是 一个作用域可以访问另一个函数内部的局部变量

function b () {
var a = 1
return function c () {
    console.log(a)
  }
}
var e = b()
e()//输出1
闭包的用途:
延长变量作用域。在函数的外部可以访问函数内部的局部变量,容易造成内存泄漏,
因为闭包中的局部变量影响不会被回收

在这里插入图片描述

递归

递归是自己调用自己,深拷贝是通过深度递归实现的,

同步异步

同步: 一个一个执行
异步: 一起执行

例
计时器,循环中的事件,事件中获取循环的变量,
数据请求

进程:
一个功能的开始执行到执行结束的过程
线程:
一个功能在执行过程中的每一个分支。多线程异步执行,线程越多,执行速度越快,但需要消耗大量性能

解决异步的四种方法:

在这里插入图片描述
1.回调函数(容易造成回调地狱)
在这里插入图片描述
2.promise
在这里插入图片描述
打印:
在这里插入图片描述![
3.generator(属于微任务)
在这里插入图片描述
4. async+await(可与promise混用)
在这里插入图片描述

宏任务和微任务

js是一门单线程语言,所以它本身是不可能异步的,但是js的宿主环境(比如浏览器、node)是多线程,宿主环境通过某种方式(事件驱动)使得js具备了异步的属性。而在js中,我们一般将所有的任务都分成两类,一种是同步任务,另外一种是异步任务。而在异步任务中,又有着更加细致的分类,那就是微任务和宏任务。
在这里插入图片描述
宏任务:

<script>整体代码
setInterval() setTimeout  定时器 延时器 
Ajax、
DOM事件
异步队列

微任务:

async/await
(Promise .then 放在异步队列-微任务)
nextTick

Promise  (立即执行,同步任务)

执行顺序

例1

主  console.log('1')  
宏  setTimeout(function(){
		console.log('2')
	},0)
同步	new Promise(function(resolve){
		console.log('3')
		resolve()
异队 }).then(function(){
		console.log('4')
	 })
输出: 1 3 4 2

例2

    console.log('1');
 
    setTimeout(function () {
      console.log('2');
      process.nextTick(function () {
        console.log('3');
      })
      new Promise(function (resolve) {
        console.log('4');
        resolve();
      }).then(function () {
        console.log('5')
      })
    })
    process.nextTick(function () {
      console.log('6');
    })
    new Promise(function (resolve) {
      console.log('7');
      resolve();
    }).then(function () {
      console.log('8')
    })
 
    setTimeout(function () {
      console.log('9');
      process.nextTick(function () {
        console.log('10');
      })
      new Promise(function (resolve) {
        console.log('11');
        resolve();
      }).then(function () {
        console.log('12')
      })
    })
输出:1、7 、6、8、2、4、3、5、9、11、10、12

答案 :
第一轮 执行外面同步代码 : 1     7   
第二轮 执行 微任务 : 6    8    
第三轮 宏任务  第一个setTimeout : 同步  2  4   微任务 3   5   第二个setTimeout:同步  9   11    微任务  10   12 

注意:

1.一个宏任务里面要是有微任务,等里面代码全部执行完,
再去执行下一个宏任务

2.如果两个JS文件,等第一个JS文件里面宏和微全部执行完再执行下一个JS文件,

在这里插入图片描述
对于宏任务和微任务请记住这几点:

微任务比宏任务执行要先输出。

宏任务里如果有宏任务,不会执行里面的那个宏任务,而是被丢进任务队列后面,
所以会最后执行。

promise

Promise 本身是同步的,是异步的一种解决方案, 

Promise 有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。  
一旦执行就无法改变

在promise中接受两个内置参数分别是resolve(成功)和reject(失败),   

Promise实例生成以后,可以用then方法分别指定resolved状态(成功)和rejected状态(失败)的回调函数。   
then方法可以传递两个回调函数第一个是成功,第二个是失败,失败回调也可以使用promise的catch方法回调,   
解决了回调地狱的问题,可以链式调用,
	Promise.all() 并发处理多个异步任务,所有任务都执行完成才能得到结果
    Promise.race() 并发处理多个异步任务,只要一个任务完成就能得到结果

白话:
解决回调地狱:例:ajax请求成功之后,再次去请求,则会一层一层嵌套,嵌套会形成闭包,闭包会导致变量一直在内存存储,导致内存泄露,


它自己本身是同步的,是异步的一种解决方案, .then方法的回调函数中再返回一个promise对象,这个promise对象 作为then方法的返回结果,然后一层一层调用、结合async await 使用,解决promise里不停.then的问题

async、await

async 放在函数前面,会将其后函数的返回值封装成一个 Promise 对象,
而 await 只能放在async里面,会等待这个 Promise 完成,并返回结果。
可以使异步代码看起来像同步代码一样,解决promise里不停.then的问题

async定义的异步函数与普通promise对象的回调函数(又称执行函数)是一样的,会立即执行,并不会阻塞后面的代码。

async 和 await 相⽐直接使⽤ Promise 来说,优势在于处理 then 的调⽤链,
能够更清晰准确的写出代码。缺点在于滥⽤ await 可能会导致性能问题,因为 await 会阻塞代码

new 在执行时会做四件事情:

在内存中创建一个新的空对象。
让this 指向这个新的对象。
执行构造函数里面的代码,给这个新对象添加属性和方法。
返回这个新对象(所以构造函数里面不需要return)。

原型链

对象就是实例
任何一个函数只要被new使用了,这个函数就是一个构造函数
image.png
解释:

什么是原型链?

所有引用类型都有一个__proto__(隐式原型)属性,属性值是一个普通的对象
所有函数都有一个prototype(原型)属性,属性值是一个普通的对象
所有引用类型的__proto__属性指向它构造函数的prototype

当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__隐式原型上查找
即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的__proto__中查找,

这样一层一层向上查找就会形成一个链式结构,我们称为原型链。

继承:

让一个不具有某些功能或属性的类或对象,通过某些方式 使用另一个具有这些功能或属性的类或对象的功能或属性

构造函数继承(改变this指向继承)
简单方便 多继承 只能继承构造函数内部的属性和方法,

  function Parent(n,s){
      this.name = n
      this.sex = s
      this.show =function(){
         console.log(this.name+ '和'+this.sex) 
      }
  }
  function Child(n,s){
      Parent.call(this,n,s)
      Parent.apply(this,[n,s])
  }
    
  var p = new Parent('张三''男')
  p.show()
  
  var c = new Child('李四''女')
  c.show()

原型对象继承
只能继承原型身上的属性和方法,不能继承构造函数身上的属性和方法

   function Parent(n,s){
      this.name = 'admin'
   }
   Parent.prototype.show =function(){
       console.log(this.name)
   }
   
   function Child(){
      this.name = 'root'
   }
   Child.prototype = Parent.prototype
   //改写show  注意深浅拷贝
   for(let i in Parent.prototype){
       Child.prototype[i] = Parent.prototype[i]
   }
   Child.prototype.show =function(){
       console.log('这是改写之后的show')
   }
   
   var p = new Parent()
   p.show()
   
   var c = new Child()
   c.show()

原型链继承
既能继承构造函数,又能继承原型
不方便传参

   function Parent(n,s){
      this.name = n
   }
   Parent.prototype.show =function(){
       console.log(this.name)
   }
   
   function Child(){}
   Child.prototype = new Parent('李四')

   var p = new Parent('张三')
   p.show()
   consloe.log(p) // name:张三
   
   var c = new Child()
   c.show()
   consloe.log(c) // name:李四

**组合继承 **
既可以继承原型,又可以继承构造函数,传参也方便,麻烦

   function Parent(n,s){
      this.name = n;
      this.sex = s;
   }
   Parent.prototype.show =function(){
       console.log(this.name+ '和' +this.sex )
   }
   
   function Child(n,s){
       Parent.call(this,n,s)
   }
   for(let i in Parent.prototype){
       Child.prototype[i] = Parent.prototype[i]
   }

   var p = new Parent('张三','男')
   p.show()
   consloe.log(p) 
   
   var c = new Child('李四','男')
   c.show()
   consloe.log(c) 

class继承 – es6

    class Parent{
       constructor(n){
          this.name=n
       }
       show(){
          console.log(this.name)
       }
    }
    
    class Child extends Parent{
       constructor(n){
           super(n)
       }
    }
    
    var p = new Parent('张三')
    p.show()
    
    var c =new Child('李四')
    c.show()

构造函数继承(改变this指向继承)
简单方便  多继承  只能继承构造函数内部的属性和方法,

原型对象继承
只能继承原型身上的属性和方法,不能继承构造函数身上的属性和方法

原型链继承
既能继承构造函数,又能继承原型,不方便传参

组合继承 
既可以继承原型,又可以继承构造函数,传参也方便,麻烦

class继承 – es6
既可以继承原型,又可以继承构造函数,传参也方便,方便

防抖节流

防抖就是将频繁触发的事件函数变为最后一次执行

节流就是将多次变为每隔一段时间执行

防抖 用于场景

在页面滚动的时候做一些逻辑,scroll DOM频繁操作,优化实现,    

浏览器自带的防抖节流工具 requestAnimationFrame 以及 延时器
debounce (func, delay) {
  let timer = null
  return function (...args) {
    if (timer)  clearTimeout(timer)
    timer  = setTimeout(() => {
      func.apply(this, args)
    }, delay)
  }
}

垃圾回收机制

标记清除(mark and sweep)

大部分浏览器以此方式进行垃圾回收,当变量进入执行环境(函数中声明变量)的时候,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”,在离开环境之后还有的变量则是需要被删除的变量。标记方式不定,可以是某个特殊位的反转或维护一个列表等。

垃圾收集器给内存中的所有变量都加上标记,然后去掉环境中的变量以及被环境中的变量引用的变量的标记。在此之后再被加上的标记的变量即为需要回收的变量,因为环境中的变量已经无法访问到这些变量

引用计数(reference counting)
这种方式常常会引起内存泄漏,低版本的IE使用这种方式。机制就是跟踪一个值的引用次数,当声明一个变量并将一个引用类型赋值给该变量时该值引用次数加1,当这个变量指向其他一个时该值的引用次数便减一。当该值引用次数为0时就会被回收,该方式会引起内存泄漏的原因是它不能解决循环引用的问题

javaScript 延迟加载方式

  1. defer 属性: HTML 4.01 为

回流重绘

回流一定会触发重绘,而重绘不一定会回流

回流,会改变元素的几何尺寸,影响布局

	添加或者删除可见的DOM元素,元素位置改变,元素尺寸改变
	——边距、填充、边框、宽度和高度,内容改变

重绘,不会改变元素的几何尺寸。不会影响布局的,比如颜色改变

当render tree中的一些元素需要更新属性,
而这些属性只是影响元素的外观,风格,而不会影响布局的,

导致回流发生的一些因素:

页面初始化渲染
调整窗口大小
改变字体
增加或者移除DOM元素
内容变化,比如用户在 input 框中输入文字, CSS3 动画等
内外边距
操作class属性
脚本操作DOM
计算offsetWidth和offsetHeight属性
设置 style 属性的值
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王小王和他的小伙伴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值