2021年前端基础面试题 13-14 存储和http

3种存储

cookie 主要是设计用来做server和浏览器通讯,所以并不适合做本地存储
缺点:最大容量4k,每次http请求都会带上,使用document.cookie =”方式修改!api太简陋
localstorage 和 sessionstorage 每个域名最大5m,不会放入hhtp请求中,使用setItem(使用方便

http状态码

1xx 服务端收到请求
2xx 请求成功
3xx 重定向
4xx 客户端错误
5xx 服务端错误

常见的状态码

200 成功
301 永久重定向
302 临时重定向
304 资源未被修改
404 未找到
403 无权限
500 错误
504 超时

http methods

get 查
post 增
patch put 改
delete 删

restful api

相对于传统的api设计, restful不会把url设计成一个功能,而是一个资源。
例如restful不使用url参数
api/list?index=2
api/list/2

常见的header

request header

accept 接受的数据格式
accept-encoding 接受的压缩格式如gzip
accept-language
connection keep-alive
cookie
host
user-agent
content-type application/json

response header

content-type application-json
content-length
content-encoding 服务器返回的压缩格式
set-cookie 服务器通过这个来设置cookie

http缓存

通常静态资源都会被缓存,比如js,css,images 。
为了避免静态资源被缓存通常在webpack打包时会在静态资源文件名后面加上hash字符串,例如index.3hjdjyu73hd8.js

强制缓存

服务器会在response header之中返回catch-control 来控制缓存的逻辑,例如max-age=3500000(秒)
no-catch 客户端无需缓存
no-store 客户端和服务器都不缓存,不常见
expires 是老标准,已经被catch-control取代

协商缓存

服务端判断请求的资源是否需要缓存,例如发现请求资源没有变化,则返回304告诉客户端无需请求。
服务端通过下发资源标识给客户端,客户端下次访问的时候带上资源标识返回给服务端。服务端再根据资源标识来判断此资源是否过期。
客户端下发资源标识分为两种:
last-modified 资源最后修改时间(秒)
e-tag 唯一标识 优先级最高
对应客户端请求的资源标识
if-modified-since
if-none-match

2021年前端基础面试题 12 AJAX

手写简易的ajax

const xhr = new XMLHttpRequest();
// method:要是用的HTTP方法,url:请求的主体,async(可选):false为同步,true为异步,默认为同步
xhr.open('GET', '/api', false);
xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) {
        if (xhr.status === 200) {
            alert(xhr.responseText);
        };
    };
};
xhr.send(null); //如果请求方法是 GET 或者 HEAD,则应将请求主体设置为 null。

xhr.readyState

0 – (未初始化)还没有调用send()方法
1 – (载入) 已调用 send()方法,正在发送请求
2 – (载入完成) send()方法执行完成,已经接受到全部响应内容
3 – (交互)正在解析响应内容
4 – (完成)响应内容解析完成,可以在客户端调用

status 状态码

301 永久重定向
302 临时重定向
301表示旧地址A的资源已经被永久地移除了(这个资源不可访问了),搜索引擎在抓取新内容的同时也将旧的网址交换为重定向之后的网址;302表示旧地址A的资源还在(仍然可以访问),这个重定向只是临时地从旧地址A跳转到地址B,搜索引擎会抓取新的内容而保存旧的网址。

304 缓存
404 未找到
403 无权限
5xx 服务端报错

若想设置POST请求版,则需更改

xhr.open里的参数为xhr.open(‘POST’, url, true/false);
xhr.send里的参数为xhr.send(p);,

浏览器同源策略 协议 域名 端口 三者缺一不可

jsonp实现跨越
window.callback=fn
callback({data:1})

2021年前端基础面试题 9-11 DOM BOM

DOM (document object model)
BOM (browser object model)

DOM的数据结构是树型结构

attribute 是修改DOM标签,会改变HTML结构
P.setAttribute(‘style’,’width:100px’)
property 是修改Js对象,性能更好,推荐用这种方式
P.style.width =100px
2者都会触发DOM渲染

插入子节点

divEl.appendChild(P)
注意如果P存在,会变成移动节点

获取父节点

P.parentNode

获取子元素

divEl.childNodes
注意childNodes中会包含nodeType为3的text类型节点,需要过滤出nodeType等于1的b元素标签类型

nodeList转数组

Array.prototype.slice.call(NodeList)

删除子元素

divEl.removeChild(P1)

DOM操作性能优化注意点

将循环插入或修改DOM的操作放入一个临时片段中,遍历完成后再统一插入到容器节点中,说白了就是减少不必要的渲染次数。

判断ua的方法

这个自行在网络上搜索吧,可能随着技术更新每年都不一样

location属性

hash 设置或返回从井号 (#) 开始的 URL(锚)。
host 设置或返回主机名和当前 URL 的端口号。
hostname 设置或返回当前 URL 的主机名。
href 设置或返回完整的 URL。
pathname 设置或返回当前 URL 的路径部分。
port 设置或返回当前 URL 的端口号。
protocol 设置或返回当前 URL 的协议。
search 设置或返回从问号 (?) 开始的 URL(查询部分)。

History 对象属性

length 返回浏览器历史列表中的 URL 数量。

History 对象方法

back() 加载 history 列表中的前一个 URL。
forward() 加载 history 列表中的下一个 URL。
go() 加载 history 列表中的某个具体页面。

如何阻止超链接跳转 e.preventDefault
如何为下拉加载的图片列表添加点击事件, 事件代理

2021年前端基础面试题 8 异步编程

1. event loop 画图说明

  • JS 是单线程运行
  • 异步是基于回调来实现
  • event loop 就是异步回调的实现原理

Call Stack 同步代码

代码执行时放入Call Stack 执行完成后移出

Web APIs 浏览器的接口

setTimeout()是web Api的定时器方法,不是es6的语言
执行setTimeout时,将它放入Web APIs里面等待5秒,后面到第5秒出发时会放入Callback Queue

接下来执行第三行代码打印Bye,同样也是执行时放入Call Stack 执行后移出Call Stack,这时整个代码同步的部分都已经执行完了。

Event Loop 事件轮询

5秒后 Web APIs里面的回调函数被触发,将cb方法放入了 CallBack Queque队列中

Event Loop 轮询CallBack Queue 发现有cb() 则放入Call Stack中执行并移出

cb()方法中有console.log() 则先执行console.log 然后执行完移出,最终再把cb()移出

Promise相关

promise的三种状态

resolved只能触发then,reject只能触发catch,例如下面的代码

Promise.resolve(100).then(data=>{
    console.log(data) // 100
})

catch 中如果正常返回,则后续状态为resolved,否则为rejected

面试题:then和catch的链式调用

Promise.resolve().then(()=>{
    console.log(1) // 1 
    throw new Error('err') // then里面有报错会触发后续的catch
}).catch(()=>{
    console.log(2) // 2 // catch中没有报错会触发后续的then
}).then(()=>{
    console.log(3) // 3
})

async/await

!(async  function (){
await  1
})()

async 返回Promise
await 等于promise的then
await 200 等于 await Promise.resolve(200)

宏任务和微任务

宏任务:settimeout ajax dom事件
微任务:promise await
宏任务在dom渲染后执行,微任务反之
微任务是es6语法 不会放到webapi区域,所以先执行

2021年前端基础面试题 1-7

1 CSS

line-height 的继承机制
父元素行高20px:子元素行高直接继承20px
父元素2或者1.5:子元素行高就是font-size的2倍或者1.5倍
父元素200%:子元素行高是200%乘以父元素的font-size而不是乘子元素的font-size

flex画筛子三

box  {
    display: flex;
    justify-content: sapce-between;
}

item:nth-child(2)  {
    align-self :  center ;
}

item:nth-child(3){
    aligin-self:flex-end;
}

响应式布局

rem vw vh vmax vmin 通过媒体查询的方式动态的设置跟字体的大小

rem的计算机制

(N)rem = html的font-size *( N)

例1:

默认的html的font-size是16px,div的font-size设置为1rem就是16*1=16px
为了便于理解,我们用F表示html的font-size,用R表示rem,用P表示px。
从上面的结果就可以得到公式 P = F * R
F = 16px
R = 1
P = 16 *1
结果P就是16px

例2:

如果html的font-size设置为100px
目标像数要求为16px
那div的font-size设置0.16rem
rem就是目标像数除以根元素
得到公式 R = P / F (rem = 目标px / html的font-size)
第三个公式
F = P / R

2 JS

变量类型,事件机制

值类型,引用类型,手写深拷贝,事件冒泡,事件代理

原型、原型链 、类 、继承

画出原型链的类图,解释supper,es5继承方式

// 构造函数 
function Parent1(name) {
  this.name = name;
  this.age = 12;
}
function Child1() {
  Parent1.call(this,'son');
  this.type = 'child1';
}
console.log( new Child1().name); //son

闭包

作用域,自由变量,手写bind

Function.prototype.myBind = function (){
    const args = Array.prototype.slice.call(arguments)
    const t = args.shift() // 第一项为传入的this
    const that = this
    return function (){
        retrun that.apply(t, args)    
    }
}

实际的应用场景
隐藏数据,比如jquery的自定义事件也是这样隐藏对象数据,只暴露方法

事件流,事件循环

事件流分为3个阶段:
(1)捕获阶段:事件从Document节点自上而下向目标节点传播的阶段;
(2)目标阶段:真正的目标节点正在处理事件的阶段;
(3)冒泡阶段:事件从目标节点自下而上向Document节点传播的阶段。

webpack构建流程

初始化 – 配置
编译 – loader,plugin
输出 – chunk

数组求和

// Accumulator (acc) (累计器)
// Current Value (cur) (当前值)
// Current Index (idx) (当前索引)
// Source Array (src) (源数组)
var sum = [0, 1, 2, 3].reduce(function(accumulator, currentValue) {
  return accumulator + currentValue
}, 100) // 从100开始累加
console.log(sum) // 106

使用promise 封装一个图片加载的方法

function loadImg(src) {
    const p = new Promise(
        (resolve, reject) => {
            const img = document.createElement('img')
            img.onload = () => {
                resolve(img)
            }
            img.onerror = () => {
                const err = new Error('img load err' + src)
                reject(err)
            }
            img.src = src
        }
    )
    return p
}

const url1 = 'xxx1'
const url2 = 'xxx2'
loadImg(usr1).then(img1 => {
    console.log(img1.width)
    return img1
}).then(img1 => {
    console.log(img1.height)
    return loadImg(url2)

}).then(img2 => {
    console.log(img2.width)
})

for of 和for in forEach的区别

for of 可以处理异步的遍历

const nums = [1, 2, 3]

// 先遍历完数组,先执行3次异步调用
nums.forEach(async (i) => {
    const res = await getList(i)
    console.log(res)
})

// 遍历移数组一次,执行一次异步调用,返回结果后再遍历数组下一项
!(async function () {
    for (let i of nums) {
        const res = await getList(i)
        console.log(res)
    }
})()

Generator函数设计的意思重点是把异步操作放到yeild表达式中,等调用next之后再执行,也是解决回调地狱的一种方案,其实后面es7的async await更好

生成器函数最基本的用法

// 定义个生成器函数,一般会在function后面加上一个星号来表示
let foo = function* () {
    for (let i = 0; i < 3; i++) {
        yield i
    }
}
// 将生成器函数肤质给一个变量,注意此时生成器函数内部的for循环并不会执行,可以在yield i 上面打印个log测试一下
let f = foo()
// 调用f下的next()方法,最后一次因为循环结束,所以没有值被返回
console.log(f.next()) // {value:0, done:false}
console.log(f.next()) // {value:1, done:false}
console.log(f.next()) // {value:2, done:false}
console.log(f.next()) // {value:undefined, done:true}

Generator不能被当做构造函数的方式被使用,换句话说就是不能被new,会报错foo is not a constructor
yield 关键字只能在Generator函数内部使用。例如将上面的for循环改成forEach则会因为作用域的问题报错

一个最典型的应用场景,写一个函数用来生成7得倍数

function* gen(i = 0) {
    while (true) {
        if (i % 7 === 0) {
            yield i
        }
        i++
    }
}
let g = gen()
console.log(g.next()) // 0
console.log(g.next()) // 7
console.log(g.next()) // 14

应用场景2:解决ajax的回调地狱。
例如有三个接口有依赖关系,常规的写法是嵌套的方式把下一个接口写到回调函数中,使用生成器可以用下面这种写法

function request(url,data = 0) {
    // 使用setTimeout来比喻ajax
    setTimeout(function () { 
        reportData.next(data + 1) // 每次都根据上一次的返回结果去请求下一个接口
    }, 1000)
}

function* gen() {
    let res1 = yield request('api/1')
    console.log(res1) // 1
    let res2 = yield request('api/2',res1)
    console.log(res2) // 2
    let res3 = yield request('api/3',res2)
    console.log(res3) // 3
}

let reportData = gen()
reportData.next() // 开始第一次

Proxy 使用

es5中对对象进行拦截使用 Object.defineProperty(),es6增加了new Proxy来增强代理拦截能力

1. 使用Proxy拦截对象,实现私有属性(下划线开头)禁止访问

let user = {
	name:’xxx’,
	age: 18,
	_gender: ‘male’	
}
user = new Proxy(user,{
	get(target, key) {
		if(key.startsWith(‘_’){
			throw new Error(‘不可访问’)
		} else {
			return target[key]
		}
	},
	set(target, key, val) {
		if(key.startsWith(‘_’){
			throw new Error(‘不可访问’)
		} else {
			return target[key] = val
		}
	}
	has (target, key) {
		if(key[0] === "_") { // key.startsWith(‘_’)
			return false;
		}
			return key in target;
	}
	deleteProperty(key){
		if(key.startsWith(‘_’){
			throw new Error(‘不可删除’)
		} else {
			delete target[key]
			return ture	
		}
	}
	ownKeys(target){
		return Object.keys(target).filter(key => !key.startsWith(‘_’))
	}
})

2. 使用Proxy拦截函数调用

let sum = (…args) =>{
	let num = 0
	args.forEach(item => {
		num += item
	})
	return num
}
sum = new Proxy(sum, {
	// 当前函数,上下文this,参数
	apply(target, ctx, agrs) {
		return target(…args) * 2
	}
})

3. 使用Proxy拦截类的构造函数

let User = class {
	constructor (name){
		this.name = name
	}
}
User = new Proxy(User, {
	// 当前类,参数,目标类
	constructor(target, args, newTarget){
		return new target(…args)
	}
})

前端规范,参考于JAVA开发手册第2版

编程规约

1.1 命名风格
  1. 名称不能以下划线和美元符号开头或结尾
  2. 禁止使用拼音和中文命名
  3. 避免使用性别、种族、地域等歧视性语言
  4. 类名使用大驼峰
  5. 方法、参数、变量使用小驼峰
  6. 常量命名全部大写,用下划线隔开,单词要写完整
  7. 抽象类使用Abstract或Base开头
  8. 避免在不同的代码块使用相同的命名
  9. 避免使用单词缩写
  10. 使用变量类型作为单词的结尾,例如startTime、nameList
  11. 如果使用了设计模式要用单词体现,例如:LoginProxy
  12. 如果是形容能力的接口名称,用对应的形容词结尾,例如:Translatable
  13. 枚举类使用Enum后缀
  14. 获取单个对象使用get开头,获取多个对象使用list开头,获取统计使用count,插入使用save/insert,删除使用remove/delete,修改update
1.2 常量定义
  1. 不允许使用魔法值
  2. 不要使用一个常量类维护所有的常量,要按功能归类分开维护
  3. 常量的五层:跨应用、应用内、子工程内、包内、类内
  4. 如果常量仅在一个固定的范围内变化,则用enum类型定义
1.3 代码格式
  1. 打括号前不换行
  2. 4个空格缩进,禁止Tab字符
  3. //注释内容前加一个空格
  4. 单行字符串长度不超过120字符
  5. 单个方法的总行数不超过80
  6. 不同的逻辑、语义、业务之间插入一个空行
1.4 OOP规约
构造方法禁止加入任何业务逻辑,如果有,那么放入init方法中
类内的方法定义顺序依次是:公有方法>getter/setter方法
setter方法中的参数名称和变量名要保持一致,且不要包含业务逻辑
1.5 日期时间
时间单位使用毫秒
不要1年写成365天
1.6 集合
判断对象是否为空使用isEmpty()方法
1.8 控制语句
switch必须包含default,哪怕什么代码都没有
switch括号内的变量如果为字符串时需要先进行null判断
单行的if语句要加打括号
尽量少用if-else,可以改写成
if(condition){
return obj;
}
// 接着写else的逻辑
超过3层的if-esle逻辑判断可以使用卫语句,策略模式、状态模式等实现,例如卫语句的实例:
function findBoyfriend ( man ){
if(man.isUgly()){
console.log(1)
return
}
if(man.isPoor()){
console.log(2)
return
}
if(man.isBad()){
console.log(3)
return
}
}
不要在条件判断中执行复杂的逻辑,可以把这些复杂的逻辑先赋值给一个变量,这样的代码看起来可读性性更高,例如
let existed = Obj.has(x) && obj.xxx ===1 || obj.xxx…
if(existed){ … }
不要在表达式中插入赋值语句,例如:
return (sync = fair)? new FairSync(): new NofairSync()
循环体内减少业务逻辑,移出体外
避免使用反逻辑运算符,例如:
if(!(x >= 1))
如果是对外开发的接口方法要做参数校验
1.9 注释规约
类和方法注释必须使用/**的方式,并且符合jsdoc规范
文件头部添加创建者和创建日期
代码内的注释要在上方另起一行使用// 注释,多行注释使用/ * */ 并对其文字
枚举类型字段必须注释用途
代码更新后注释要更新,保持一致
删除未使用的方法、属性、变量
如果注释掉的代码一定要保留,要说明用途和影响,否则一律删除
注释是给继任者看的,要准确的描述业务含义
TODO要注明标注人和计划处理的时间
1.10 前后端交互
http请求体body中必须控制长度,Nginx默认长度为1mb
1.11 其他
不要在视图层写复杂的业务逻辑

设计规约

如果系统交互的User类超过1类,相关的User Case 类超过5个,则使用用例图来表达结构
如果某个业务对象的状态超过3个,应使用状态图表达且明确状态变化的各个触发条件
如果系统中的调用链路上涉及的对象超过3个,则使用时序图表达各个环节的输入与输出
如果系统中的类超过了5个,则使用类图表达之间的关系
如果系统中的2个对象之间存在协作关系,需要表示复杂的处理流程,则需要使用活动图来表示
系统架构设计时明确以下原则
确定系统边界,确定在技术层面上做与不做
确定系统内模块之间的关系
确定后续演化的原则
确定非功能性需求,如安全性、可用性、扩展性等
在需求分析时要充分考虑异常流程和业务边界
在类的设计与实现时要符合单一原则
谨慎使用继承的方式进行扩展,优先使用聚合和组合
根据依赖倒置原则,尽量依赖抽象类与接口,更有利于扩展
对扩展开放,对修改闭合
共性的业务和行为抽取出来公共模块和配置
设计文档的主要目的是明确需求、理顺逻辑、后期维护、次要目的是知道编码
可扩展性的本质是找到系统的变化点,并隔离变化点
设计的本质就是识别和表达系统的难点
清晰的代码只是文档的某个片段,而不是全部

react脚手架在vscode编辑器中报错Using `babel-preset-react-app` requires that you specify `NODE_ENV` or `BABEL_ENV` environment variables

例如在app.js文件中的第一行import下面提示红色波浪线

错误提示为:Parsing error: [BABEL] …/src/App.js: Using `babel-preset-react-app` requires that you specify `NODE_ENV` or `BABEL_ENV` environment variables. Valid values are “development”, “test”, and “production”. Instead, received: undefined. (While processing: “…/node_modules/babel-preset-react-app/index.js”)eslint

原因:由 vscode 的 eslint 扩展插件启动的服务时,没有为其注入 BABEL_ENV 或 NODE_ENV。 导致 eslint 所使用的 babel-preset-react-app 解析器中 env 为 undefined 导致。

解决办法:
在vscode的配置文件setting.json中添加一行”eslint.nodeEnv”: “development”
重启

实现输入框@人名功能

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .label {
        display: inline-block;
        border: solid 1px #ccc;
        padding: 10px 20px;
      }
      .output {
        padding: 20px;
        margin: 20px 0;
        width: 400px;
      }

      #myInput {
        width: 400px;
        border: solid 1px #ccc;
        padding: 10px 20px;
      }
    </style>
  </head>
  <body>
    <div class="output" id="myOutput"></div>
    <div contenteditable="plaintext-only" id="myInput"></div>
    <div><button id="myButton">submit</button></div>
    <div class="label" contenteditable="false" data-name="张三">@张三</div>
    <div class="label" contenteditable="false" data-name="李四">@李四</div>
    <div class="label" contenteditable="false" data-name="王五">@王五</div>
    <script>
      let inputEl = document.getElementById("myInput")
      let outputEl = document.getElementById("myOutput")
      let buttonEl = document.getElementById("myButton")
      let labelsEl = document.querySelectorAll(".label")

      buttonEl.addEventListener("click", (e) => {
        let liEl = document.createElement("div")
        liEl.innerText = inputEl.innerText
        outputEl.appendChild(liEl)
      })
      labelsEl.forEach((m) => {
        m.addEventListener("click", function (e) {
          console.log(this.dataset)
          let spanEl = document.createElement("span")
          spanEl.innerHTML = "@" + this.dataset.name + " "
          spanEl.style = "color:blue"
          spanEl.contentEditable = "false"
          inputEl.appendChild(spanEl)
        })
      })
    </script>
  </body>
</html>