JS
改变this指向 -> bind、call、apply
闭包:如果一个函数内部引用了该函数作用域外的其他局部变量,那么该函数就是一个闭包
闭包的应用:函数柯里化(参数复用,延迟计算)、hooks
局部变量无法共享和长久的保存,而全局变量可能造成变量污染,闭包则提供了一种
普通函数的 [[Scopes]] 属性中只有一个 Global 全局对象。
闭包函数的 [[Scopes]] 属性中新增了Closure(闭包)对象
console.dir(函数) 查看对象的信息
eg:
const trimLowerCaseSplit = compose(trim, lowerCase, split);
trimLowerCaseSplit(" a,B,C"); // ["a","b","c"]
const compose = function(...funcs) {
return function(x) {
funcs.reduce((value, func) => func(value), x)
}
}
装饰器:其实就是一个函数,给类加装饰器,就是将类作为参数传入这个装饰器函数,在这个装饰器函数里会对这个类做处理(增删改查属性),最后返回处理过后的新类
**函数组合:**为了实现函数的复用,我们通常会尽量保证函数的职责单一,通过对函数进行组合,来实现特定的功能,有助于提高代码的可测试性和可维护性
ES6+ :
模块化(Module)
模块的功能主要由 export 和 import 组成。每一个模块都有自己单独的作用域,模块之间的相互调用关系是通过 export 来规定模块对外暴露的接口,通过import来引用其它模块提供的接口。同时还为模块创造了命名空间,防止函数的命名冲突。
箭头函数: 根据外部作用域来决定this,且不能修改,定义的时候就确定了
数组: find、findIndex、for…of、Array.from(类数组对象)、includes、flat、
对象: Object.values、Object.keys、Object.entries、for…in、??(空位合并运算符)、 ?. (可选链操作符),对象属性的简写、Object.assign(target, source1, source2)用于对象的合并,将源对象source的所有可枚举属性,复制到目标对象target
spread扩展操作符、rest剩余运算符、解构赋值、模板字符串、箭头函数、函数参数默认值、对象属性简写、class、promise、async await 、Generator、Map 和 Set 、??(空位合并运算符)、class类
Map VS Object:
**「Map」只包含你所定义的键值对,但是「Object」**对象具有其原型中的一些内置属性;
「Object」 is not iterable,不能直接使用for…of…, **「Map」**可以,但是对象通过Object.keys(),Object.values(),Object.entries()方法可生成可迭代对象
在函数调用时使用扩展操作符
function sum(x, y, z) {
return x + y + z;
}
const numbers = [1, 2, 3];
//不使用延展操作符
console.log(sum.apply(null, numbers));
//使用延展操作符
console.log(sum(...numbers));// 6
数组拷贝:
var arr = [1, 2, 3];
var arr2 = [...arr];
let arr = ['a', 'b']
for (let [index, value] of arr.entries()) {
console.log(index, value)
}
// 0 'a'
// 1 'b'
import和rquire的区别
Import 是ES6 规范语法,在编译时调用,确定模块的依赖关系进行静态优化,所以必须放在文件头部
require 是 AMD规范引入方式,在运行时调用,所以 require 在需要的时候在代码中引入
ES11 增加了动态导入语句 import()
var、let、const
- var声明的变量可以修改,如果不初始化会输出undefined,不会报错。其作用域为该语句所在的函数内,且存在变量提升现象
- let声明的变量可以修改,其作用域为该语句所在的代码块内,是块级作用域,不会发生变量提升,存在暂时性死区
- const定义的变量不可以修改,而且必须初始化
object.is():与严格比较运算符(===)的行为基本一致,修复了NaN不等于自身,以及+0等于-0的问题
promise和async await 都是异步编程的一种解决方案
Promise 是异步编程的一种解决方案,比传统的解决方案callback更加的优雅。Promise解决了传统callback函数回调地狱的问题,支持链式调用
Promise是一个构造函数,通过new来构建一个promise对象,该对象必须接受一个函数作为参数函数有resolve和reject两个参数,这两个函数, 用于改变 promise 的状态和传递异步操作的结果,promise有三种状态(pending、fulfilled、rejected)一旦函数执行完成 promise 有了结果状态无法改变,但是遇到复杂的业务场景,promise 嵌套 .then 的写法一样不够直观,也不方便处理异常
async-await 是基于promise实现的,async函数是一个 generator 函数的语法糖, 返回的是一个promise 对象,可以使异步代码看起来像同步代码一样,方便阅读和理解,可以用try-catch去捕获异常
目前项目里两个都会用到,promise 更多应用在函数封装中,async 用在函数的使用中
async 和 await 相比直接使用 Promise 来说,优势在于处理 then 的调用链,能够更清晰准确的写出代码。缺点在于滥用 await 可能会导致性能问题,因为 await 会阻塞代码,也许之后的异步代码并不依赖于前者,但仍然需要等待前者完成,导致代码失去了并发性
浅拷贝:
会创建一个新对象,对原有对象的成员进行依次拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存地址。因此如果新对象中的某个对象成员改变了地址,就会影响到原有的对象。
深拷贝:
JSON.parse(JSON.stringify(obj))可以实现数组和对象的基本数据类型的深拷贝,但不能处理函数、undefined、symbol、new Date、正则
介绍下JS对象Object
js常用的基本数据类型包括Undefined、Null、Number、Boolean、String;
Object是js的引用数据类型,用来存储键值集合,属性名必须是字符串,属性值可以是任意数据类型也可以是函数
可以通过new 构造函数或者字面量方式 创建;
是不可迭代的,不能使用for of 遍历;但是可以通过keys()、values()、entries()方法遍历对象自身的所有可枚举属性,返回数组(一般用于for…of循环)
Object.assign()通过复制一个或多个对象来创建一个新的对象
Object.create()使用指定的原型对象和属性创建一个新对象。
Object.defineProperty()给对象添加一个属性并指定该属性的配置。
对象的属性描述符有两种形式: 数据描述符和存取描述符。
**configurable,enumerable
**value,writable
get, set: 对某个属性设置存值函数和取值函数,拦截该属性的存取行为
New:
原型链 -> 继承(原型链继承、构造函数继承、组合继承)-> Class
排序: 冒泡,快排,归并,插入
Webpack
你都配置过哪些loader和plugin?
postcss-px-to-viewport
uglifyjs-webpack-plugin,html-webpack-plugin,optimize-css-assets-webpack-plugin
自动清理构建产物:clean-webpack-plugin
做过哪些优化?
配置环境变量,sourcemap,proxy,移动端页面自适应,eslint,cdn,treeshaking, HotModuleReplacementPlugin热更新, 预渲染,gzip压缩,文件指纹,代码分割和动态import
webpack默认只支持js和json文件类型,通过loaders转换可以使webpack支持其他文件类型;loader本身是一个函数,接受源文件作为参数,返回转换的结果
plugin是用来增强webpack的功能,通常用于打包文件的优化,资源管理和环境变量注入
热更新原理:
HMR即Hot Module Replacement是指当你对代码修改并保存后,webpack将会对代码进行重新打包,并将改动的模块发送到浏览器端,浏览器用新的模块替换掉旧的模块,去实现局部更新页面而非整体刷新页面,可以保存应用的状态,提高开发效率
内部实现主要使用了webpack、express、websocket
使用express启动本地服务,当浏览器访问资源时对此做响应。
服务端和客户端使用websocket实现长连接
webpack监听源文件的变化,即当开发者保存文件时触发webpack的重新编译。
- 每次编译都会生成hash值、已改动模块的json文件、已改动模块代码的js文件
- 编译完成后通过socket向客户端推送当前编译的hash戳
客户端的websocket监听到有文件改动推送过来的hash戳,会和上一次对比
- 一致则走缓存
- 不一致则通过ajax获取已改动模块和jsonp获取已改动模块代码向服务端获取最新资源
使用内存文件系统去替换有修改的内容实现局部刷新
使用框架开发的好处:
单页面应用,前端控制路由,数据双向绑定,数据驱动视图,可以更关注于逻辑,不用手动频繁操作dom,大大提高开发效率,虚拟Dom局部渲染,减少重排重绘优化性能
MVVM
View 与 Model 不直接发生联系,都通过ViewModel传递,ViewModel负责把Model的数据同步到view显出来,还负责把view修改同步到Model,各部分之间的通信,都是双向的
ViewModel做了两件事:数据的绑定(DataBindings)和Dom事件监听(DomListeners),从而实现双向数据绑定,View和Model之间的同步工作完全是自动的,开发者只需要关注业务逻辑,做到了视图和数据的解耦,利于前端业务组件化开发,提高了可复用性
你对虚拟DOM原理的理解 https://juejin.cn/post/6844903902429577229
Virtual DOM是对DOM的抽象,本质上是JavaScript对象,这个对象就是更加轻量级的对DOM的描述
已经有了DOM,为什么还需要虚拟dom?
1.频繁变动DOM会造成浏览器重绘回流,影响性能,虚拟dom这一层抽象, 会尽可能多地一次性将差异更新到DOM中,减少重排重绘优化性能
2.更好的跨平台, 比如Node.js就没有DOM,如果想实现SSR(服务端渲染),那么一个方式就是借助Virtual DOM
Virtual DOM 的 diff 目的就是比较新旧Virtual DOM Tree找出差异并更新, 「双端比较算法」
Key值的作用:
key值要确保唯一性(如id,不要使用index)key可以让新旧虚拟dom数比较时快速定位是否为同一个元素,提高效率和准确性
Vue和React有什么差别
很多人说Vue上手快,好学,我觉得是因为提供了丰富的指令,还可以自定义指令,计算属性和侦听属性语法糖,支持过渡动画,keep-alive,比如 Vue.use 可全局在实例中注入对象,不需要像 React 那样一遍遍引入组件,双向数据绑定,用起来很方便,html模板和js方法分开写,简洁明了 (babel-plugin-transform-vue-jsx)
react使用jsx语法,像vue提供的一些api功能,react只能自己去实现逻辑,但也正是因为这样react更灵活,也不用学习和记忆api,复用层面 React 通过高阶函数、自定义 Hooks 实现,写法灵活,复用性更高、社区活跃度高生态好
前端路由
-
hash路由:明显的标志是带有#,主要是通过监听url中的hash变化来进行路由跳转,优势就是兼容性更好,
window.addEventListener('hashchange', function(e) { let newURL = event.newURL; let oldURL = event.oldURL; console.log('The hash has changed!') }, false);
hash的改变会触发hashchange事件,通过监听hashchange事件来完成操作实现前端路由。hash值变化不会让浏览器向服务器发出请求
2. HTML5新路由方案:History API
history兼容能支持 HTML5 History Api 的浏览器,依赖HTML5 History API来实现前端路由。没有#,路由地址跟正常的url一样,但是初次访问或者刷新都会向服务器请求,如果没有请求到对应的资源就会返回404,所以路由地址匹配不到任何静态资源,则应该返回同一个index.html 页面,需要在nginx中配置。
2. HTML5新路由方案:History API
history兼容能支持 HTML5 History Api 的浏览器,依赖HTML5 History API来实现前端路由。没有#,路由地址跟正常的url一样,但是初次访问或者刷新都会向服务器请求,如果没有请求到对应的资源就会返回404,所以路由地址匹配不到任何静态资源,则应该返回同一个index.html 页面,需要在nginx中配置。
React
State:
Hooks:
Hooks是一套全新的 API,可以让你在不编写类,不使用 state 的情况下使用 Class 的状态管理,生命周期等功能。
如果你在编写函数组件并意识到需要向其添加一些 state,以前的做法是必须将其转化为 class。现在你可以在现有的函数组件中使用 Hook
useState 的setState不像 class 中的 this.setState, 更新 state 变量总是替换它而不是合并它
useEffect可以让你在函数组件中执行副作用操作,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。默认情况下,useEffect 会在第一次渲染之后和每次更新之后都会执行,每次渲染后都执行清理(return () => {})或者执行 effect 可能会导致性能问题。在 class 组件中,我们可以通过在 componentDidUpdate 中添加对 prevProps 或 prevState 的比较逻辑解决,useEffect 可以传递第二个可选参数 [依赖值],依赖值发生变化才会重新渲染。
不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层以及任何 return 之前调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确
只要 Hook 的调用顺序在多次渲染之间保持一致,React 就能正确地将内部 state 和对应的 Hook 进行关联
Redux:单一数据源的设计让组件之间通信更加方便,也有利于状态的统一管理
每个组件都有自己的 State(状态)和 State Setter(状态修改函数),这意味着跨组件的状态读取和修改是相当麻烦的。而 Redux 的核心思想之一就是将状态放到唯一的全局对象(一般称为 Store)中,而修改状态则是调用对应的 Reducer 函数去更新 Store 中的状态
react的生命命周期
用useMemo和useCallback 来 memoize 对象和函数,可以避免子组件做没必要的渲染, 提高性能
JSX 上写的事件并没有绑定在对应的真实 DOM 上,而是通过事件代理的方式,将所有的事件都统一绑定在了 document 上。这样的方式不仅减少了内存消耗,还能在组件挂载销毁时统一订阅和移除事件,而且不存在解决了浏览器之间的兼容问题。
冒泡到 document 上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件(SyntheticEvent)。因此我们如果不想要事件冒泡的话,要调用event.stopPropagation
什么是jsx,我们为什么使用jsx呢?
jsx是对js的一个语法的拓展,让我们可以用js的方式去描述view,其实就是一个描述页面的js对象,从控制台打印出来可以看到写起来更快,执行速度快,开发效率高
VUE
Vue的生命周期: 四阶段八函数
Vue 实例从创建到销毁的过程,就是生命周期,
created 表示完成数据观测、属性和方法的运算和初始化事件,此时 $el 属性还未显示出来
DOM 渲染在 mounted 中就已经完成了
vueX 干什么用的,有哪些核心概念,原理是什么
组件data为什么返回函数 :
组件中的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data。如果单纯的写成对象形式,就使得所有组件实例共用了一份data,造成了数据污染。
VUE双向绑定原理
Vue是通过数据劫持,结合发布订阅模式来实现的数据双向绑定,通过 object.defineProperty 将data属性转为的getter,setter来劫持各个属性实施监听, 当数据发生变化的时候,发布消息给订阅者,触发相应的监听回调
要实现mvvm的双向绑定,就必须要实现以下几点: 1、实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者 2、实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数 3、实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图 4、mvvm入口函数,整合以上三者
模板引擎(响应式)原理:
v-model 能实现双向绑定 v-model 本质上是语法糖,它负责监听用户的输入事件以更新数据 对 input 使用 v-model,实际上是指定其 :value 和 :input
nextTick的原理:
使用 vm.$nextTick 可以确保获得 DOM 异步更新的结果
在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。源码实现:Promise > MutationObserver > setImmediate > setTimeout
GUI渲染线程和JS引擎线程是互斥的,为了防止DOM渲染的不一致性,其中一个线程执行时另一个线程会被挂起。
宏任务 > 微任务 > 渲染 (当前宏任务执行后,会将在它执行期间产生的所有微任务立即执行一遍)
浏览器
EventLoop
javascript是一门单线程、异步、非阻塞、解析类型脚本语言
JavaScript 的设计就是为了处理浏览器网页的交互(DOM操作的处理、UI动画等),决定了它是一门单线程语言。如果有多个线程,它们同时在操作 DOM,那网页将会一团糟。
-
从上往下顺序执行,同步代码直接执行,异步代码的回调会分发到宏任务队列或微任务队列中
-
碰到macroTask直接执行,并且把回调函数放入macroTask队列中(下次事件循环执行);
-
碰到microtask直接执行,把回调函数放入microtask队列中(本次事件循环执行);
-
当同步任务执行完毕后,检查并执行当前微任务队列里的microtask
-
执行栈由此进入下一轮事件循环:执行宏任务 macroTask (setTimeout,setInterval,callback)
宏队列可以有多个,微任务队列只有一个,所以每创建一个新的settimeout都是一个新的宏任务队列,执行完一个宏任务队列后,都会去check微任务。
eg:微任务:EFG (E里面产生一个微任务H)宏任务:ABC (B里面产生一个微任务I)执行顺序?EFGH ABIC
浏览器存储:local,session,cookies 异同:(生命周期、存储大小、是否和服务器交互)
跨域(协议、域名、端口):
我最推荐的也是我司常用的解决跨域的方式就是cors全称为 Cross Origin Resource Sharing(跨域资源共享)。浏览器发送跨域请求时会先发起一次 OPTIONS 预请求,从而获知服务器端对跨源请求所支持 HTTP 方法。在确认服务器允许该跨源请求的情况下,再发送真正的请求。
token和session的区别和用法
XSS 和 CSRF(Cookie的SameSite属性设置为Strict;HttpOnly属性设置为true)
浏览器缓存 https://mp.weixin.qq.com/s/Wvc0lkLpgyEW_u7bbMdvpQ
当浏览器再次访问一个已经访问过的资源时
-
看看是否命中强缓存,如果命中,就直接使用缓存了;
(查看 header 头中的 Expire 和 Cache-control 来判断) -
如果没有命中强缓存,就发请求到服务器检查是否命中协商缓存;
(把资源标识,If-Modify-Since:Last-Modified 或 If-no-match:Etag 发送到服务器,确认资源是否更新) -
如果命中协商缓存,服务器会返回 304 告诉浏览器使用本地缓存;
-
否则,返回最新的资源。
HTTPS https://juejin.cn/post/6844904073376825352
HTTPS 协议之所以是安全的是因为HTTPS 协议会对传输的数据进行加密,证书验证阶段使用的是非对称加密,数据传输阶段使用的是对称加密。(一方面是效率问题,一方面是一对公私钥只能实现单向的加解密)
当客户端发起 HTTPS 请求,服务端返回证书(颁发机构信息、公钥、域名、有效期等),客户端对证书进行验证,如果不合法则提示告警,验证通过后在本地生成随机数,通过证书中的公钥对随机数进行加密传输到服务端,服务端通过私钥解密得到随机数,并构造对称加密算法,之后的数据交互通过对称加密算法进行加解密
处理过哪些兼容性问题
- Grid兼容性问题、ios中input自动获取焦点问题、1px像素问题、移动端input获取焦点弹出键盘遮挡问题、
- 微信里路由使用history模式在微信中打开单页面应用,copy出来的总是进来时的url,导致微信支付地址授权失败
项目中做过哪些优化
gzip压缩、CDN、控制文件上传大小,压缩上传文件、防抖节流、treeshaking、预渲染、sourcemap、代码分割和动态import、页面描述description,页面关键词keywords
SSR 常用于以下两个场景:
- 有 SEO 诉求,用在搜索引擎检索以及社交分享,用在前台类应用。
- 首屏渲染时长有要求,常用在移动端、弱网情况下。
什么是预渲染?
服务端渲染,首先得有后端服务器(一般是 Node.js)才可以使用,如果我没有后端服务器,也想用在上面提到的两个场景,那么推荐使用预渲染。
预渲染与服务端渲染唯一的不同点在于渲染时机,服务端渲染的时机是在用户访问时执行渲染(即服务时渲染,数据一般是最新的),预渲染的时机是在项目构建时,当用户访问时,数据不一定是最新的(如果数据没有实时性,则可以直接考虑预渲染)。
预渲染(Pre Render)在构建时执行渲染,将渲染后的 HTML 片段生成静态 HTML 文件。无需使用 web 服务器实时动态编译 HTML,适用于静态站点生成。
项目怎么做seo的: 1.设置title、description、keywords 2.HTML代码语义化 3.预渲染
Dva :
dva 是一个基于 redux 和 redux-saga 的数据流方案
dva 通过 model 的概念把一个领域的模型管理起来,包含同步更新 state 的 reducers,处理异步逻辑的 effects,订阅数据源的 subscriptions 。
数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过 dispatch 发起一个 action,如果是同步行为会直接通过 Reducers 改变 State ,如果是异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变 State
dva 为了控制副作用的操作,底层引入了redux-sagas做异步流程控制,还采用了generator 在effects中可以像写同步代码那样来处理异步
通过浏览器提供的 History API 可以监听浏览器url的变化,从而控制路由相关操作
什么是面向对象
面向对象是一种编程思想,模块化开发,增强代码的可复用性,降低耦合度,增加可读性,有三个基本特征:封装、继承、多态
封装:封装就是模块化编程,隐藏实现细节,使得代码模块化(你不需要知道我内部是怎么实现的,只需要按照要求传参调用,返回预期的结果,具备低耦合高内聚,易维护、易复用、易扩展);
继承:主要实现重用代码,节省开发时间、减少代码量
多态:“一个接口,多种方法”,同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果
面向对象软件开发具有以下优点:
- 代码开发模块化,更易维护和修改。
- 代码复用性强。
- 增强代码的可靠性和灵活性。
- 增加代码的可读性。
Generator 生成器
当它被调用时就会生成一个迭代器对象(Iterator Object),它有一个 .next()方法,每次调用返回一个对象{value:value,done:Boolean},value返回的是 yield 后的返回值,当 yield 结束,done 变为 true,通过不断调用并依次的迭代访问内部的值。
yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
Iterator就是遍历器,它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)
const p = new Proxy(target, handler)给对象创建代理,可以实现对对象基本操作的拦截和自定义