前端学习笔记(含面试问题--个人笔记比较粗糙更新中)

62 阅读16分钟

数据类型

值类型 (基本类型)

  • Number
  • String
  • Null
  • Undefined
  • Boloean
  • Symbo
  • Bigint

引用数据类型

  • Array
  • Function
  • Object
  • Date
  • RegExp

栈内存

主要用于存放基本类型和对象变量的指针,算是一种简单的存储;栈内存自动分配相对固定大小的内存空间,并由系统自动释放;

堆内存

主要用于存放引用类型,存储的对象类型数据对于大小这方面是未知的;堆内存是动态分配的内存,内存大小不一,也不会自动释放

如何判断数据类型

typeof :在基本数据类型(null除外) 和Function时,返回其对应类型;对于引用数据类型(Function除外)都返回object;

instanceof: 无法判断基本数据类型;对于引用数据类型(除判断数据类型是否是Object类型外)均可;

constructor :在基本数据类型(null与undefined除外)/引用数据类时,均返回对应类型;

Object.prototype.toString : 无论基本数据类型还是引用类型返回其对应类型

赋值和浅拷贝

赋值 赋值的对象和元数据指向同一个对象,第一个层数据和数据包含的子对象(引用数据类型) 》》 赋值后数据改变会使得元数据一同改变

浅拷贝 浅拷贝的数据改变不会使原数据的第一层基本数据类型一同改变,会使元数据包含的子对象(引用数据类型)一同改变

浅拷贝的实现 [当拷贝对象只有一层的时候,是深拷贝]

  • 展开运算符
  • Object.assign( ) 该方法用于将所有可枚举的属性的值从一个或多个源对象source复制到目标对象。它将返回目标对象target Object.assign 方法的第一个参数是目标对象,后面的参数都是源对象

什么是执行上下文

JavaScript代码都是执行在上下文中得: 执行上下文:指当前执行环境中的变量、函数声明、作用域链、this等信息 执行上下文是评估和执行JavaScript代码的环境的抽象概念。每当JavaScript代码在运行的时候,它都是执行上下文中运行

执行上下文的类型

JavaScript中有三种执行上下文类型

  • 全局执行上下文 这是默认或者说基础的上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的windows对象(浏览器的情况下),并且设置 this 的值等于这个全局对象。一个程序中只会有一个全局执行上下文
  • 函数执行上下文 每当一个函数执行被调用时,都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过在函数被调用时候创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序,执行一系列步骤。
  • Eval函数执行上下文 执行在eval函数内部的代码也会有它属于自己的执行上下文,但由于JavaScript开发者并不经常使用eval,所以这里不讨论它。

执行栈

执行栈,也就是在其他编程语言中所说的“调用栈”,是一种拥有LIFO(后进先出)的数据结构的栈,被用来存储代码运行时创建的所有执行上下文。
当JavaScript引擎第一次遇到你的脚本时,它会创建一个全局的执行上下文并且压入当前执行栈。每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部。 引擎会执行那些执行上下文中位于栈顶的函数。当函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。

怎么创建执行上下文

到现在,我们已经看过JavaScript怎么管理执行上下文了,现在让我们了解JavaScript引擎怎样执行上下文的。
创建执行上下文有两个阶段 1) 创建阶段
2) 执行阶段

The Creation Phase

在JavaScript代码执行前,执行上下文将经历的创建阶段。在创建阶段会发生三件事:

  1. this值的决定,即我们所熟知的This绑定。
  2. 创建词法环境组件
  3. 创建变量环境组件

This绑定

在全局执行上下文中,this的值指向全局对象。(在浏览器中,this引用widows对象) 在函数执行上下文中,this的值取决于该函数是如何被调用的。如果它是被一个引用对象调用,那么this会被设置成那个对象,否则this的值被设置为全局对象或者undefined(在严格模式下)

词法环境

简单来说词法环境是一种持有标识符—变量映射的结构。(这里的标识符指的是变量/函数的名字,而变量是对实际对象[包含函数类型对象]或原始数据的引用)。

现在,在词法环境的内部有两个组件:(1) 环境记录器和 (2) 一个外部环境的引用

  1. 环境记录器是存储变量和函数声明的实际位置。
  2. 外部环境的引用意味着它可以访问其父级词法环境(作用域)。

词法环境有两种类型:

  • 全局环境(在全局执行上下文中)是没有外部环境引用的词法环境。全局环境的外部环境引用是 null。它拥有内建的 Object/Array/等、在环境记录器内的原型函数(关联全局对象,比如 window 对象)还有任何用户定义的全局变量,并且 this的值指向全局对象。
  • 函数环境中,函数内部用户定义的变量存储在环境记录器中。并且引用的外部环境可能是全局环境,或者任何包含此内部函数的外部函数。

环境记录器也有两种类型(如上!):

  1. 声明式环境记录器存储变量、函数和参数。
  2. 对象环境记录器用来定义出现在全局上下文中的变量和函数的关系。

简而言之,

  • 全局环境中,环境记录器是对象环境记录器。
  • 函数环境中,环境记录器是声明式环境记录器。

变量环境:

它同样是一个词法环境,其环境记录器持有变量声明语句在执行上下文中创建的绑定关系。

如上所述,变量环境也是一个词法环境,所以它有着上面定义的词法环境的所有属性。

在 ES6 中,词法环境组件和变量环境的一个不同就是前者被用来存储函数声明和变量(letconst)绑定,而后者只用来存储 var 变量绑定。

我们看点样例代码来理解上面的概念:

let a = 20;
const b = 30;
var c;

function multiply(e, f) {
 var g = 20;
 return e * f * g;
}

c = multiply(20, 30);
复制代码

执行上下文看起来像这样:

GlobalExectionContext = {

  ThisBinding: <Global Object>,

  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // 在这里绑定标识符
      a: < uninitialized >,
      b: < uninitialized >,
      multiply: < func >
    }
    outer: <null>
  },

  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // 在这里绑定标识符
      c: undefined,
    }
    outer: <null>
  }
}

FunctionExectionContext = {
  ThisBinding: <Global Object>,

  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // 在这里绑定标识符
      Arguments: {0: 20, 1: 30, length: 2},
    },
    outer: <GlobalLexicalEnvironment>
  },

VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // 在这里绑定标识符
      g: undefined
    },
    outer: <GlobalLexicalEnvironment>
  }
}
复制代码

注意 — 只有遇到调用函数 multiply 时,函数执行上下文才会被创建。

可能你已经注意到 letconst 定义的变量并没有关联任何值,但 var 定义的变量被设成了 undefined

这是因为在创建阶段时,引擎检查代码找出变量和函数声明,虽然函数声明完全存储在环境中,但是变量最初设置为 undefinedvar 情况下),或者未初始化(letconst 情况下)。

这就是为什么你可以在声明之前访问 var 定义的变量(虽然是 undefined),但是在声明之前访问 letconst 的变量会得到一个引用错误。

这就是我们说的变量声明提升。

执行阶段

这是整篇文章中最简单的部分。在此阶段,完成对所有这些变量的分配,最后执行代码。

注意 — 在执行阶段,如果 JavaScript 引擎不能在源码中声明的实际位置找到 let 变量的值,它会被赋值为 undefined

闭包的概念

闭包允许函数访问并操作函数外部的变量。红宝书上对于闭包的定义:闭包是指有权访问另外一个函数作用域中的变量的函数。 MDN 对闭包的定义为:闭包是指那些能够访问自由变量的函数。这里的自由变量是外部函数作用域中的变量。
概述上面的话,闭包是指有权访问另一个函数作用域中变量的函数
(本质就是上级作用域内变量的生命周期,因为被下级作用域内引用,而没有被释放。就导致上级作用域内的变量,等到下级作用域执行完以后才正常得到释放。)

闭包中的变量存储的位置是堆内存。

假如闭包中的变量存储在栈内存中,那么栈的回收 会把处于栈顶的变量自动回收。所以闭包中的变量如果处于栈中那么变量被销毁后,闭包中的变量就没有了。所以闭包引用的变量是出于堆内存中的。

作用域

作用域只有全局作用域、函数作用域、块级作用域

上级作用域概念

函数的上级作用域在哪里创建的,上级作用域就是谁

箭头函数

箭头函数内的this是由外层作用域决定的

call apply bind 三者区别

1)三者都可以显式绑定函数的this指向
2)三者第一个参数都是this要指向的对象,若该参数是undefined或null,this则默认指向全局window
3)传参不同 apply是数组、call是参数列表、而bind可以分为多次传入,实现参数的合并
4)call、apply是立即执行,bind是返回绑定this之后的函数,如果这个新的函数作为构造函数被调用,那么this 不再指向传入给bind的第一个参数,而是指向新生成的对象

原型、原型链

原型的作用

原型被定义为其他对象提供共享属性的对象,函数的实例可以共享原型上的属性和方法

原型链

他的作用就是当你访问一个对象的属性,如果该对象内部不存在这个属性,就会去它的 proto 属性所在的指向
对象(原型对象) 上查找。如果原型对象依旧不存在这个属性,那么就会去其原型的 proto 属性所指向的原型对象上去查找。以此类推,直到找到null,而这个查找的线路,也就构成我们常说的原型链。

原型链和作用域的区别: 原型链是查找对象上的属性,作用域链是查找当前上下文中的变量

proto、proptotype、constructor属性介绍

1)js中对象分为两种,普通对象和函数对象
2) proto 和constructor是对象独有的。prototype属性是函数独有的,它的作用是包含可以给特定类型的所有实例提供共享的属性和方法;但是在 JS 中,函数也是对象,所以函数也拥有__proto__和 constructor属性
3)constructor属性是对象所独有的,它是一个对象指向一个函数,这个函数就是该对象的构造函数构造函数.prototype.constructor === 该构造函数本身
4)一个对象的__proto__指向其构造函数的prototype函数创建的对象.proto === 该函数.prototype
5)特殊的Object、Function

instanceof

instanceof 的基本使用方法,它可以判断一个对象的原型链上是否包含该构造函数的原型,经常用来判断是否为该构造函数的实例

instanceof 与 typeof 的区别

1) typeof 一般被用于判断一个变量的类型
typeof可以用来判断 Number、Symobl 、Boolean、String、 Undefined 、 Null 、 Function 、 Object
2) instanceof 判断一个对象的原型链上是否包含该构造函数的原型

New 关键字

New一个对象,到底发生了什么

1)创建一个对象,该对象的原型指向构造函数的原型
2)调用该构造函数,构造函数的this指向新生成的对象
3)判断构造函数是否有返回值,如果有返回值且返回值是一个对象或者方法,则返回该值;否则返回新生成的对象

继承

多种继承方式

1)原型链继承,缺点:引用类型的属性被所有实例共享

2)借用构造函数 (经典继承)
3)原型式继承
4) 组合继承
5) 寄生组合式继承
6)Es6继承

  constructor(){
  	this.age = 18;
  	}
  }
  class Child extends Parent{  
  	constructor(){  
  	super();  
  	this.name = '张三';  
  	}
  }

Js 事件轮询机制 Event Loop

JS语言的一大特点就是单线程,也就是说,同一个时间只能做一件事情。所有任务都需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着
所有任务可以分为两种,一种是宏任务,另一种是微任务
宏任务指的是,在主线程上排队执行的任务,只有前一个人任务执行完毕,才执行下一个任务 微任务指的是,不进入主线程、而进入‘微任务列表’的任务
当前宏任务执行完后,会判断任务列表中是否有任务。如果有,会把微任务放到主线程中并执行,如果没有,就继续执行下一个宏任务。

宏任务 微任务

结论 微任务 执行时机比宏任务早 (同步任务先执行,然后执行异步任务)
执行顺序:先执行同步代码,遇到异步宏任务则将异步宏任务放入宏任务队列中,遇到异步微任务则将异步微任务放入微任务队列中,当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,微任务执行完毕后再将异步宏任务从队列中调入主线程执行,一直循环直至所有任务执行完毕。
微任务 先于 DOM 先于 宏任务

  1. 宏任务 (Macrotasks)
    script全部代码(注意同步代码也属于宏任务)、DOM事件、AJAX请求、setTimeout、读取文件
  2. 微任务
    Promise.then、MutationObserve 、async/await (值得注意的是 Promise 在实例化的时候代码是同步进行的)

执行顺序

  1. 同步程序
  2. process.nextTick
  3. 微任务
  4. 宏任务
  5. setlmmediate

事件轮询机制执行过程

1)代码执行过程中,宏任务和微任务放在不同的任务队列中
2) 当某个宏任务执行完后,会查看微任务队列是否有任务,如果有,执行微任务队列中的所有微任务(注意这里执行所有的微任务)
3) 微任务执行完成后,会读取宏任务队列中排在最前的一个宏任务(注意:宏任务是一个一个取),执行 该宏任务,如果执行过程中,遇到微任务,依次加入微任务队列
4)宏任务执行完成后,再次读取微任务队列里的任务,依次类推
5) 同步任务会放在运行栈中运行
6) 异步任务会放在任务队列中

闭包

函数嵌套函数,内部函数就是闭包
正常情况下,函数执行完成,内部变量销毁(销毁:释放内存空间)
闭包,内部函数没有执行完成,外部函数变量不会销毁

防抖和节流

防抖:用户触发事件过于频繁,只要最后一次事件的操作 节流:控制执行次数(控制高频时间执行次数)

箭头函数和普通函数的区别

  1. this指向不同
  2. 普通函数,谁调用这个函数,this指向谁
  3. 箭头函数,在哪里定义函数,this指向谁

浏览器渲染机制

浏览器的内核是指支持浏览器运行的最核心的程序,分为两个部分,一个是渲染引擎,另一个是JS引擎。

页面加载过程

  • 浏览器根据DNS服务器得到域名的IP地址
  • 向这个IP地址的机器发送HTTP 请求
  • 服务器收到、处理并返回HTTP 请求
  • 浏览器得到返回内容

浏览器渲染过程

  1. 浏览器会解析三个东西
  • 一是HTML/SVG/XHTML ,HTML字符串描述了一个页面的结构,浏览器会把HTML结构字符串解析转换成DOM树结构
  • 二是CSS,解析CSS会产生CSS规则树,它和DOM结构比较像
  • 三是JavaScript脚本,等JavaScript 脚本加载后,通过DOM API 和CSSOM API 来操作DOM Tree 和 CSS Rule Tree
  1. 解析完成后,浏览器引擎会通过DOM Tree 和 CSS Rule Tree 来构造Rendering Tree
  • Rendering Tree 渲染树并不等同于DOM树,渲染树只会包括显示的节点和这些节点的样式信息
  • CSS 的Rule Tree 主要是为了完成匹配并把CSS Rules附加上Rendering Tree 上的每个ELement(也就是每个Frame)

浏览器渲染

1)构建DOM树;
2) 样式计算;
3) 布局定位;
4) 图层分层;
5) 图层绘制;
6) 合成显示;
在CSS属性改变时,重渲染会分为”回流" 、”重绘“、和”直接合成”三种情况,分别对应从”布局定位\图层绘制\合成显示,开始,再走一遍上面的流程

let,var,const

  1. var: ES5中引入的关键字,用于声明变量,其作用域为函数作用域,或者全局作用域,不存在块级作用域。使用var声明变量可以重复声明,可以在声明之前访问到,就是通常所说的变量提升。
  2. let:在ES6中引入关键字,用于声明块级作用域的变量。let声明的变量具有块级作用域。使用let声明的变量不可重复声明,但是可以修改其值。
  3. const:在Es6中引入的关键字,用于声明块级作用域的常量。const声明的的变量也具有块级作用域,且一但被赋值就不能修改其值。使用const声明的常量不能重复声明。如果希望const声明的对象的属性也不能被修改,也可以使用Object.freeze()方法来冻结对象。Object.freeze()方法会递归冻结对象的所有属性,使其不能被修改、添加或者删除。