前端面经总结(四)

前端面经总结(三)

JavaScript

typeof和instance of区别

二者都用来判断变量是否为空

  • typeof返回值为字符串,说明变量的数据类型

  • instanceof判断变量是否属于某个对象的实例。判断逻辑为从当前引用的proto一层一层顺着原型链向上找,若能找到对应的prototype则返回true

深拷贝/浅拷贝

  • 浅拷贝:只是拷贝了基本类型的数据,而引用类型数据,复制后也会发生引用。即浅拷贝仅指向被复制的内存地址,若原地址对象改变,则复制后的对象也相应改变。

    1
    2
    3
    4
    5
    6
    //使用=直接赋值
    let newArr = arr;
    //使用slice()
    let newArr = arr.slice();
    //使用concat()
    let newArr = arr.concat();
  • 深拷贝:创建新对象,属性中引用的其他对象也会被克隆,且不再指向原对象地址。使用JSON.parse()/JSON.stringfy()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//使用JSON.stringify和JSON.parse
var newArr = JSON.parse(JSON.stringify(arr));//该方法可以拷贝数组和对象,但不能拷贝函数。对于RegExp类型和Function类型无法完全满足,且不支持有循环引用的对象。
//拷贝时判断属性类型
var deepCopy = function(obj) {
// 只拷贝对象
if (typeof obj !== 'object') return;
// 根据obj的类型判断是新建一个数组还是一个对象
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
// 遍历obj,并且判断是obj的属性才拷贝
if (obj.hasOwnProperty(key)) {
// 判断属性值的类型,如果是对象递归调用深拷贝
newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
}
}
return newObj;
}

深拷贝用法

  1. 解决循环引用

使用哈希表存储已拷贝过的对象,再进行循环检测,检测到当前对象已存在于哈希表中则直接取出该值并返回

  1. 深拷贝只能拷贝一层原型链的属性和方法

es6新特性

  1. let/const/var

    • let:块级作用域,没有变量提升,函数内部使用let后,对函数外部无影响,必须先声明后使用。

    • const:定义的变量不可修改,必须初始化,块级作用域,没有变量提升

    • var:定义的变量可以修改,不初始化默认为undefined,不会报错,存在变量提升,不是块级作用域,其声明变量是全局的。

  2. 展开运算符…

  3. 箭头函数,即匿名函数,不能作为构造函数。

    • 和普通函数区别:不能作为构造函数,不能被new,没有arguments实参集合,也没有自己的this,箭头函数的this继承当前上下文中的this,且不能使用call/apply/bind改变this
  4. 模版字面量,即包含嵌入式表达式的字符串字面量,使用倒引号(``)

1
let message = `${student.name} please see ${teacher.name} in ${teacher.room} to pick up your report card.`;
  1. 数组/对象解构,提取值并赋值
1
2
3
const point = [10, 25, -34];
const [x, y, z] = point;
console.log(x, y, z);
  1. for…of循环,可以循环任何可迭代类型的数据,String/Array/Map/Set,不包含Object,默认情况下对象不可迭代

==和===区别

  1. =:赋值

  2. ==:返回布尔值,允许不同数据类型的比较,若不同类型先默认转换为相同的数据类型,若为对象比较,则比较空间地址

  3. ===:数据和数据类型完全相等

call/bind/apply区别

  1. call/apply第一个参数相同,即指定的对象,为该函数的执行上下文

  2. call/apply第二个参数不同,call传入的是所有的参数,apply传入参数的数组

  3. bind返回执行上下文被改变的函数而不会立即执行,call/apply直接执行该函数

js继承方法

  1. 原型链继承:父类的实例作为子类的原型

  2. 构造继承:使用父类的构造函数来继承,相当于复制父类的实例属性给子类

  3. 实例继承:为父类实例添加新特性作为子类实例返回

  4. 拷贝继承:把父类中的属性或方法复制给子类

  5. 组合继承:通过调用父类构造函数,继承父类的属性并保留传参,通过将父类实例作为子类原型实现函数复用,会调用两次父类构造函数,一次创建子类原型,一次子类构造函数内部

  6. 寄生组合继承:通过借用构造函数来继承属性,通过原型链的混成形式继承方法。相当于借用构造函数+浅拷贝父类的原型对象,不会初始化两次实例方法和属性

闭包

一种特殊的函数,绑定了外部环境变量的函数,允许在一个内层函数中访问到其外层函数的作用域。可以保护变量不受外界污染,一直存在内存中

原型及原型链

  1. 原型

原型prototype是一个简单的对象,用于实现对象的属性继承,可以理解为对象的父级。

每个JavaScipt对象包含一个__proto__的属性指向该对象的原型,通过obj.__proto__访问。

  1. 其他概念
  • 构造函数:通过new新建对象的函数

  • 实例:通过构造函数和new创建的对象,即实例

实例通过__proto__指向原型,通过constructor指向构造函数

1
2
3
4
//创建实例
const instance = new Object();//Object()为构造函数
//原型
const prototype = Object.prototype;

由上:

  • 实例.proto = 原型

  • 原型.constructor = 构造函数

  • 构造函数.prototype = 原型

  • 实例.constructor = 构造函数:实例并不真正拥有constructor指针,而是从原型链上获取

  1. 原型链

nullundefined外,每个对象都有原型,即__proto__属性,指向创建该对象的构造函数的原型。__proto__将对象连接起来组成了原型链,即一个用来实现继承和共享属性的有限的对象链。

  • 属性查找机制:原型链依赖对象的_proto_指向,当访问对象的成员时,若自身没有,则去原型链指向对象的构造函数的prototype中一层一层找,找到后使用,没找到则返回undefined或报错。

  • 属性修改机制:

    • 基本数据类型(Number/String/Boolean/Undefined/Null/Symbol):通过实例对象修改原型属性,不会修改原型对象的属性,若该属性不存在,则在该实例对象上创建一个同名属性

    • 引用类型(Object/Array/…): a.info = {name: "xxx", age: 18}

      • 整体修改:若通过实例对象整体修改引用类型,则不会修改原型对象对应的引用类型,而在该实例对象创建一个同名属性。即修改a.info = {name: "xxx", sex: "girl"}

      • 部分修改:若通过实例对象部分修改引用类型,即修改该引用类型的属性,则会直接修改原型上的引用类型。即修改a.info.age = 20,则原型上的a.__proto__.info = {name: "xxx", age: 20}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function A() {
this.name = 'a';
this.background = { color: 'green' };
}
function B(){}

B.prototype = new A();//B的原型为A
var b1 = new B();//b1为B的实例
var b2 = new B();//b2为B的实例

b1.name = 'change';//仅修改实例b1的属性值,若不存在则添加属性
b1.background.color = 'red';//修改实例b1的属性background的属性值color,此时原型A的background也被修改了

console.log(b1.name);//change
console.log(b2.name);//b2本身不存在属性name,故到原型链上找,即输出a
console.log(b1. background);//{color: "red"}
console.log(b2. background);//通过b1部分修改引用类型background的属性,则原型A改变,b2不存在background属性,则到原型链上找,即输出{color: "red"}

//若实现console.log(b2. background);输出{color: "green"},则b1如下修改
b1.background = {color: "red"};//整体修改b1属性background,b1不存在此属性,故在b1创建同名属性background,不改变原型的属性background
console.log(b1. background);//{color: "red"}
console.log(b2. background);//{color: "green"}

浏览器渲染的流程

  1. 将html代码按深度优先比例生成dom树

  2. 渲染css文件生成css渲染树

  3. dom树和css渲染树生成render树

  4. 浏览器通过render树将所有节点位置计算出来呈现到屏幕上

从输入url到页面发生了什么

  1. 输入url,浏览器查找当前url是否存在缓存,并比较缓存是否过期

  2. 若没有缓存,DNS解析url查找对应IP

  3. 根据IP通过三次握手建立TCP连接

  4. HTTP发起请求,服务器处理请求,浏览器接受响应

  5. 渲染页面构造DOM树和CSS渲染树

  6. 四次挥手关闭TCP连接,浏览器将渲染结果呈现在屏幕上

跨域方法

  1. 通过jsonp跨域-只能处理get

动态创建script标签,利用scriptsrc不受同源策略限制,故可以请求第三方服务器资源内容

  1. document.domain+iframe跨域:仅限主域相同,子域不同的场景

两个页面都通过js强者设置document.domain为基础主域实现同域

  1. window.name+iframe跨域:name在不同页面加载后依然存在

通过iframesrc属性由外域转本地域。跨域数据由iframewindow.name从外域传入本地域

  1. 使用postMessage(data, origin)跨域:html5的新特性

  2. 使用CORS跨域资源共享跨域

服务器设置Access-Control-Allow-OriginHTTP响应头后,浏览器允许跨域请求

  1. 使用websocket协议跨域

  2. 通过node.js中间件代理跨域:启动一个代理服务器,实现数据的转发。

同源策略

一种约定,同源指协议/域名/端口三者相同

限制:

  1. Cookie/LocalStorage/IndexDB无法读取

  2. DOM和JS对象无法获得

  3. Ajax请求无法发送

前端优化

  1. 减少HTTP请求

  2. 页面设计时简化页面

  3. 合理设置HTTP缓存

  4. 资源合并压缩

  5. 多图片网页使用图片懒加载

  6. 减少DOM操作

  7. JS中避免嵌套循环和死循环

ajax步骤

  1. 创建ajax实例

  2. 执行open确定要访问的连接和同步异步

  3. 监听请求状态

  4. 发送请求

1
2
3
4
5
6
7
8
9
10
var xhr = new XMLHttpRequest();
console.log(xhr.readyState);//0
xhr.open("GET","./data.json");
console.log(xhr.readyState);//1
xhr.send(null);
console.log(xhr.readyState);//1
xhr.onreadystatechange = function (res){
if(this.readyState === 4)
console.log(xhr.readyState);//4
}

如上,ajax请求状态有:

  • 0:xhr被创建,但没有使用open方法
  • 1:open方法被调用,建立了链接
  • 2:send方法被调用,取得了响应的状态值和响应头
  • 3:响应题正在下载中
  • 4:下载体已经完成,可以直接使用responseText

ajax实现

  1. 前端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">

<title>My JSP 'index.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
<script>
function TestAjax(){
var xmlHttp;
if (window.XMLHttpRequest) {
xmlHttp = new XMLHttpRequest();

} else {
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
}
xmlHttp.onreadystatechange = function(){
if (xmlHttp.readyState==4 && xmlHttp.status==200) {
document.getElementById("sp").innerHTML = xmlHttp.responseText;
}
}

xmlHttp.open("GET", "TestAjax?name=Ouyang", true);
xmlHttp.send();
}
</script>
</head>

<body>
<button onclick="TestAjax()">利用Ajax获取数据</button> <br>
<span id = "sp"></span>

</body>
</html>
  1. 后端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.ajax;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.swing.RepaintManager;

/**
* Servlet implementation class TestAjax
*/
@WebServlet("/TestAjax")
public class TestAjax extends HttpServlet {
private static final long serialVersionUID = 1L;

/**
* @see HttpServlet#HttpServlet()
*/
public TestAjax() {
super();
// TODO Auto-generated constructor stub
}

/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
//response.getWriter().append("Served at: ").append(request.getContextPath());
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
out.println("Hello " + request.getParameter("name"));
out.flush();
}

/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}

}

移动端兼容

  1. 设置缓存,手机页面在第一次加载会进行缓存,每次刷新会使用缓存而不会重新向服务器发送请求,不使用缓存设置no-cache

  2. 添加点击事件时引用fastclick.js文件,解决延迟问题

  3. 设置meta中的viewport

同步/异步

  • 同步:在同一时间内做一件事

  • 异步:在同一时间内做多件事,常见的异步任务:定时器/ajax/事件绑定/回调函数/async await/promise。实现方式:回调函数/发布订阅/事件绑定/promise

promise

异步编程的一种解决方案

1
2
3
4
5
6
7
8
var promise = new Promise(function(resolve, reject){

if (/* 异步操作成功 */) {
resolve(value);
} else {
reject(error);
}
})
  • resolve:将Promise对象状态由未完成变为成功,在异步操作成功时调用,将异步操作的结果作为参数传递出去

  • reject:将Promise对象状态由未完成变为失败,在异步操作失败时调用,传递错误error

  • then:then方法在Promise实例生成后分别指定两种状态回调函数。

1
2
3
4
5
6
7
8
9
10
let promise = new Promise(function(resolve, reject){
console.log("AAA");
resolve()
});
promise.then(() => console.log("BBB"));
console.log("CCC")

// AAA
// CCC
// BBB

Promise优点是可以解决回调/链式调用/减少嵌套。缺点为无法检测进行状态/新建立刻执行且无法取消/内部错误无法抛出。

Promise.all/Promise.race

  • Promise.all:将多个Promise实例包装成一个新的Promise实例,成功时返回结果数组,结果顺序与数组中数据顺序一致,失败时返回最先reject的值

  • Promise.race:多个Promise哪个结果先到达,则返回此结果,不考虑结果本身成功或失败。

async/await

  • async:异步,必定返回Promise

  • await:等待。

    • await命令后接Promise对象,返回该对象的结果

    • await命令后接非Promise对象,返回对应的值

    • await命令后接thenable对象(定义then方法的对象),await将其等同于Promise对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function sleep(ms) {
return new Promise(function(resolve, reject) {
setTimeout(resolve,ms);
})
}
async function handle(){
console.log("AAA")
await sleep(5000)
console.log("BBB")
}

handle();

// AAA
// BBB (5000ms后)

DOM diff原理

  1. 从根节点开始遍历所有节点

  2. 对于不同类型的标签,删除原标签,新建标签

  3. 对于类型相同属性不同的标签,只修改属性

  4. 对于同一个父节点下的复数同类型标签(列表类型),基于key对比修改

完全的DOM diff算法事件复杂度为O(n3),Vue中将其简化,只对比同级元素,将时间复杂度降低至O(n)

进程线程

  • 进程:并发执行的程序在执行过程中分配和管理资源的基本单位,一个程序至少一个进程

  • 线程:是进程的一个执行单元,处理器调度的基本单位。一个进程至少一个线程。

区别:

  1. 进程之间是独立的地址空间,同一进程的线程共享进程的地址空间

  2. 进程之间资源独立,同一进程的线程共享进程的资源

  3. 每个独立的进程有一个程序入口,线程不能独立执行,必须依存在程序中

  4. 进程是分配和管理资源的基本单位,线程是处理器调度的基本单位

宏任务和微任务

  • 宏任务:由Node/浏览器发起的任务,每次从宏任务事件队列中获取一个放入执行栈中执行

    • script

    • setTimeout/setInterval

    • I/O

    • UI交互事件

    • postMessage()

    • setImmediate: 遇到setTimeout,先于它执行

  • 微任务:由JavaScript发起的任务,当前任务执行后立刻执行,无需等渲染。

    • Promise

    • process.nextTick

    • Object.observe

代码输出判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//主线程执行,输出start
console.log('start')
//宏任务1,放入宏任务队列
setTimeout(() => {
console.log('setTimeout')
}, 0)
//new Promise执行输出Promise
new Promise((resolve) => {
console.log('promise')
resolve()
})//then为微任务1,放入微任务队列
.then(() => {
console.log('then1')
})//then为微任务2,放入微任务队列
.then(() => {
console.log('then2')
})
//输出end
console.log('end')
  1. 循环1:start -> promise -> end

  2. 执行微任务队列:then1 -> then2

  3. 执行宏任务队列:setTimeout

运行机制

  1. 执行一个宏任务

  2. 执行中遇到宏任务,将其放入宏任务队列,遇到微任务,将其放入微任务队列

  3. 宏任务执行完毕,立即执行微任务队列中所有微任务

  4. 全部微任务执行完毕,检查渲染,完毕后开始下一个宏任务

$nextTick作用

vm.$nextTick接受一个回调函数作为参数,用于将回调延迟到下次DOM更新周期(下次微任务执行时更新DOM)后进行,即将回调函数添加到微任务中。

判断变量是对象还是数组

使用array instanceof Array方法,返回true则为数组,否则对象

不能使用typeof,它判断对象和数组都返回Object,只用来判断基本类型Boolean/Number/Symbol/Undefined/String,其他引用类型,除了function外其他都返回object

1
2
3
4

### 执行上下文

> 执行上下文EC可简单理解为一个包含```变量对象```/```作用域链```/```this指向```的对象,可分为3类:```全局执行上下文```/```函数上下文```/```eval上下文
  1. 全局执行上下文:即window对象。window对象也是var声明的全局变量的载体,即通过var创建的全局对象,都能通过window直接访问

  2. 函数上下文:每当一个函数被调用时,都会创建一个函数上下文,通过一个函数都被多次调用,都会创建一个新的上下文

执行上下文栈

执行上下文栈,即调用栈。用于存储代码执行期间创建的所有上下文,具有先进后出LIFO的特性

js代码首次运行时,先创建一个全局执行上下文压入执行栈中,没胆该函数被调用,则创建一个新的函数执行上下文压入栈中。

1
2
3
4
5
6
7
8
9
10
11
12
function f1(){
f2();
console.log("1");
}
function f2(){
f3();
console.log("2");
}
function f3(){
console.log("3");
}
f1();//3 2 1

执行上下文创建阶段

执行上下文创建分为创建阶段执行阶段

创建阶段主要负责:

  1. 确定this指向

在全局执行上下文中,this总是指向全局对象。在函数执行上下文中,this的值取决于函数的调用方式,若被对象调用,则this指向此对象,否则this一般指向全局对象window或undefined

  1. 创建词法环境组件

词法环境是包含标志符变量映射的结构,此标识符表示变量/函数的名称,变量是对实际对象或原始值的引入。

词法环境由环境记录对外部环境引入记录两部分组成。

  • 环境记录:用于存储当前环境中的变量和函数声明的实际位置
  • 外部环境引入记录:用于保存自身环境可以访问到的其他外部环境

词法环境分为全局词法环境函数词法环境

  • 全局词法环境:对外部环境引入记录为null,全局词法环境本身即为最外层环境,此外,它还记录了当前环境下的所有属性和方法的位置
  • 函数词法环境:包含用户在函数定义的所有属性、方法、arguments对象。其外部环境引入由实际代码决定。
  1. 创建变量环境组件

变量环境具备词法环境所有属性。ES6中唯一的区别在于词法环境用于存储函数声明与let/const声明的变量,而变量环境仅存储var声明的变量

在执行上下文创建阶段,函数声明与var声明的变量在创建阶段已经被赋予了一个值,var被设置为undefined,函数被设置为自身函数。但let/const被设置为未初始化。即变量提升,在声明之前可以访问var定义的变量,因为其已经在创建阶段就被初始化为undefined,而let/const没有被赋值,故声明之前访问会提示引用错误。

而在执行阶段,则根据之前的环境记录对应赋值,若有值则对应赋值,若没有值,则赋予undefined

  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2020-2024 Aweso Lynn
  • PV: UV: