Open lizheng0515 opened 3 years ago
2021.7 货拉拉、虾皮、有赞。。。高级前端岗面试整理
function Person() {} Person.prototype.name = 'Zaxlct'; Person.prototype.sayName = function() { alert(this.name); } var person1 = new Person(); //JS 在创建对象的时候,都有一个__proto__ 的内置属性,用于指向创建它的构造函数的原型对象。 // 对象 person1 有一个 __proto__属性,创建它的构造函数是 Person,构造函数的原型对象是 Person.prototype console.log(person1.__proto__ == Person.prototype) //true //所有函数对象的__proto__都指向Function.prototype String.__proto__ === Function.prototype // true String.constructor == Function //true
详解
原型继承
function Parent () { this.name = 'Parent' this.sex = 'boy' } function Child () { this.name = 'child' } // 将子类的原型对象指向父类的实例 Child.prototype = new Parent() //优:继承了父类的模板,又继承了父类的原型对象 //缺:1.无法实现多继承(因为已经指定了原型对象了) // 2.创建子类时,无法向父类构造函数传参数
构造函数继承
在子类构造函数内部使用call或apply来调用父类构造函数,复制父类的实例属性给子类。
call或apply
function Parent (name) { this.name = name } function Child () { //用.call 来改变 Parent 构造函数内的指向 Parent.call(this, 'child') } //优:解决了原型链继承中子类实例共享父类引用对象的问题,实现多继承,创建子类实例时,可以向父类传递参数 //缺:构造继承只能继承父类的实例属性和方法,不能继承父类原型的属性和方法
组合继承
组合继承就是将原型链继承与构造函数继承组合在一起。
寄生组合继承
class继承
在class 中继承主要是依靠两个东西:
class
extends
super
class Parent { constructor (name) { this.name = name } getName () { console.log(this.name) } } class Child extends Parent { constructor (name) { super(name) this.sex = 'boy' } }
同步与异步、宏任务和微任务分别是函数两个不同维度的描述。
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务; 异步任务指的是,不进入主线程、而进入任务队列(task queue)的任务,只有等主线程任务执行完毕,任务队列开始通知主线程,请求执行任务,该任务才会进入主线程执行。
task queue
当某个宏任务执行完后,会查看是否有微任务队列。 如果有,先执行微任务队列中的所有任务; 如果没有,在执行环境栈中会读取宏任务队列中排在最前的任务; 执行宏任务的过程中,遇到微任务,依次加入微任务队列。 栈空后,再次读取微任务队列里的任务,依次类推。
同步(Promise)>异步(微任务(process.nextTick ,Promises.then, Promise.catch ,resove,reject,MutationObserver)>宏任务(setTimeout,setInterval,setImmediate))
await阻塞 后面的代码执行,因此跳出async函数执行下一个微任务
async/await是基于Promise实现的,看起来更像同步代码,
const makeRequest = () => { try { getJSON().then(result => { // JSON.parse可能会出错 const data = JSON.parse(result) console.log(data) }) // 取消注释,处理异步代码的错误 // .catch((err) => { // console.log(err) // }) } catch (err) { console.log(err) } }
使用aync/await的话,catch能处理JSON.parse错误:
aync/await
JSON.parse
const makeRequest = async () => { try { // this parse may fail const data = JSON.parse(await getJSON()) console.log(data) } catch (err) { console.log(err) } }
实现链式调用:使用.then()或者.catch()方法之后会返回一个promise对象,可以继续用.then()方法调用,再次调用所获取的参数是上个then方法return的内容
.then()
.catch()
promise
then
return
promise的三种状态是 fulfilled(已成功)/pengding(进行中)/rejected(已拒绝)
fulfilled
pengding
rejected
状态只能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但发生改变便不可二次修改;
Promise 中使用 resolve 和 reject 两个函数来更改状态;
resolve
reject
then 方法内部做的事情就是状态判断:
柯里化(Currying) 是把接收多个参数的原函数变换成接受一个单一参数(原来函数的第一个参数的函数)并返回一个新的函数,新的函数能够接受余下的参数,并返回和原函数相同的结果。
柯里化(Currying)
// 普通的add函数 function add(x, y) { return x + y } // Currying后 function curryingAdd(x) { return function (y) { return x + y } } add(1, 2) // 3 curryingAdd(1)(2) // 3
递归遍历对象,解决循环引用问题
解决循环引用问题,我们需要一个存储容器存放当前对象和拷贝对象的对应关系(适合用key-value的数据结构进行存储,也就是map),当进行拷贝当前对象的时候,我们先查找存储容器是否已经拷贝过当前对象,如果已经拷贝过,那么直接把返回,没有的话则是继续拷贝。
function deepClone(target) { const map = new Map() function clone (target) { if (isObject(target)) { let cloneTarget = isArray(target) ? [] : {}; if (map.get(target)) { return map.get(target) } map.set(target,cloneTarget) for (const key in target) { cloneTarget[key] = clone(target[key]); } return cloneTarget; } else { return target; } } return clone(target) };
nodeJS里面的模块是基于commonJS规范实现的,原理是文件的读写,导出文件要使用exports、module.exports,引入文件用require。 每个文件就是一个模块;每个文件里面的代码会用默认写在一个闭包函数里面 AMD规范则是非同步加载模块,允许指定回调函数,AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。
nodeJS
commonJS
exports
module.exports
require
AMD
RequireJS
AMD推崇依赖前置, CMD推崇依赖就近。对于依赖的模块AMD是提前执行,CMD是延迟执行。
CMD
在ES6中,我们可以使用 import 关键字引入模块,通过 exprot 关键字导出模块,但是由于ES6目前无法在浏览器中执行,所以,我们只能通过babel将不被支持的import编译为当前受到广泛支持的 require。
ES6
import
exprot
babel
CommonJs 和 ES6 模块化的区别:
前端模块化:CommonJS,AMD,CMD,ES6
import 的ES6 标准模块; require 是 AMD规范引入方式;
import是编译时调用,所以必须放在文件开头;是解构过程 require是运行时调用,所以require理论上可以运用在代码的任何地方;是赋值过程。其实require的结果就是对象、数字、字符串、函数等,再把require的结果赋值给某个变量
(function(){ var scriptEle = document.createElement("script"); scriptEle.type = "text/javasctipt"; scriptEle.async = true; scriptEle.src = "http://cdn.bootcss.com/jquery/3.0.0-beta1/jquery.min.js"; var x = document.getElementsByTagName("head")[0]; x.insertBefore(scriptEle, x.firstChild); })();
// async属性规定一旦加载脚本可用,则会异步执行 <script type="text/javascript" src="xxx.js" async="async"></script>
// defer属性规定是否对脚本执行进行延迟,直到页面加载为止 <script type="text/javascript" src="xxx.js" defer="defer"></script>
Set对象可以存储任何类型的数据。 值是唯一的,没有重复的值。
Map对象保存键值对,任意值都可以成为它的键或值。
WeakSet 结构与 Set 类似,也是不重复的值的集合 . WeakMap 对象是一组键值对的集合
不同: WeakSet 的成员只能是对象,而不能是其他类型的值。 WeakSet 不可遍历。
WeakSet
WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
WeakMap
null
WeakMap的键名所指向的对象,不计入垃圾回收机制。
call( this,a,b,c ) 在第一个参数之后的,后续所有参数就是传入该函数的值。 apply( this,[a,b,c] ) 只有两个参数,第一个是对象,第二个是数组,这个数组就是该函数的参数。
call( this,a,b,c )
apply( this,[a,b,c] )
共同之处:都可以用来代替另一个对象调用一个方法,将一个函数的对象上下文从初始的上下文改变为由thisObj指定的新对象。
所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次 所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。
addEventListener的第三个参数干嘛的,为true时捕获,false时冒泡
Object.prototype.toString.call() 判断对象类型
Object.prototype.toString.call()
// new Set是实现数组去重,// Array.from()把去重之后转换成数组let arr2 = Array.from(new Set(arr));
作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。
ES5只有全局作用域没和函数作用域,ES6增加块级作用域
暂时性死区:在代码块内,使用 let 和 const 命令声明变量之前,该变量都是不可用的,语法上被称为暂时性死区。
JavaScript 采用词法作用域(lexical scoping),也就是静态作用域。
函数的作用域在函数定义的时候就决定了。
当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
function _new(constructor, ...arg) { // 创建一个空对象 var obj = {}; // 空对象的`__proto__`指向构造函数的`prototype`, 为这个新对象添加属性 obj.__proto__ = constructor.prototype; // 构造函数的作用域赋给新对象 var res = constructor.apply(obj, arg); // 返回新对象.如果没有显式return语句,则返回this return Object.prototype.toString.call(res) === '[object Object]' ? res : obj; }
this/arguments
this
Array.isArray() 判断
instanceof 判断: 检验构造函数的prototype属性是否出现在对象的原型链中,返回一个布尔值。 let a = []; a instanceof Array; //true
let a = []; a instanceof Array; //true
constructor判断: 实例的构造函数属性constructor指向构造函数 let a = [1,3,4]; a.constructor === Array;//true
let a = [1,3,4]; a.constructor === Array;//true
Object.prototype.toString.call() 判断 let a = [1,2,3]; Object.prototype.toString.call(a) === '[object Array]';//true
let a = [1,2,3]; Object.prototype.toString.call(a) === '[object Array]';//true
静态输入:静态类型化是一种功能,可以在开发人员编写脚本时检测错误。
大型的开发项目:使用TypeScript工具来进行重构更变的容易、快捷。
更好的协作:类型安全是在编码期间检测错误,而不是在编译项目时检测错误。
更强的生产力:干净的 ECMAScript 6 代码,自动完成和动态输入等因素有助于提高开发人员的工作效率。
interface 只能定义对象类型。type声明可以声明任何类型。
interface 能够声明合并,两个相同接口会合并。Type声明合并会报错
type可以类型推导
代理,可以理解为在对象之前设置一个“拦截”,当该对象被访问的时候,都必须经过这层拦截。意味着你可以在这层拦截中进行各种操作。比如你可以在这层拦截中对原对象进行处理,返回你想返回的数据结构。
ES6 原生提供 Proxy 构造函数,MDN上的解释为:Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。
const p = new Proxy(target, handler); //target: 所要拦截的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理) //handler:一个对象,定义要拦截的行为 const p = new Proxy({}, { get(target, propKey) { return '哈哈,你被我拦截了'; } }); console.log(p.name);
新增的属性,并不需要重新添加响应式处理,因为 Proxy 是对对象的操作,只要你访问对象,就会走到 Proxy 的逻辑中。
Vue3.x 推出了Composition API。 setup 是组件内使用 Composition API的入口。setup 执行时机是在 beforeCreate 之前执行.
Vue3.x
Composition API
setup
beforeCreate
Vue3.x 可以使用reactive和ref来进行数据定义。
// props 传入组件对属性 // context 一个上下文对象,包含了一些有用的属性:attrs,parent,refs setup(props, context) { // ref 定义数据 const year = ref(0); // reactive 处理对象的双向绑定 const user = reactive({ nickname: "xiaofan", age: 26, gender: "女" }); setInterval(() => { year.value++; user.age++; }, 1000); return { year, // 使用toRefs,结构解构 ...toRefs(user), }; }, // 提供isRef,用于检查一个对象是否是ref对象
用于定义可更改的计算属性
const plusOne = computed({ get: () => count.value + 1, set: val => { count.value = val - 1 } });
setup现在支持返回一个渲染函数,这个函数返回一个JSX,JSX可以直接使用声明在setup作用域的响应式状态:
JSX
export default { setup() { const count = ref(0); return () => (<div>{count.value}</div>); }, };
相同点:
不同点: Vue 是MVVM框架,双向数据绑定,当ViewModel对Model进行更新时,通过数据绑定更新到View。
MVVM
ViewModel
Model
View
React是一个单向数据流的库,状态驱动视图。State --> View --> New State --> New View ui = render (data)
State --> View --> New State --> New View
ui = render (data)
模板渲染方式不同。React是通过JSX来渲染模板,而Vue是通过扩展的HTML来进行模板的渲染。
组件形式不同,Vue文件里将HTML,JS,CSS组合在一起。react提供class组件和function组
Vue封装好了一些v-if,v-for,React什么都是自己实现,自由度更高
vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,dep.addSub来收集订阅的依赖,watcher监听数据的变化,在数据变动时发布消息给订阅者,触发相应的监听回调。
Object.defineProperty()
setter
getter
dep.addSub
watcher
监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。 订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而调用对应update更新视图。
Observer
Watcher
v-model 指令,它能轻松实现表单输入和应用状态之间的双向绑定。
v-model
computed: 支持缓存,只有依赖数据结果发生改变,才会重新进行计算,不支持异步操作,如果一个属性依赖其他属性,多对一,一般用computed
watch: 数据变,直接触发相应操作,支持异步,监听数据必须data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数
data
props中
端路由简介以及vue-router实现原理 原理核心就是 更新视图但不重新请求页面。路径之间的切换,也就是组件的切换。 vue-router实现单页面路由跳转模式:hash模式、history模式。根据设置mode参数
hash模式:通过锚点值的改变,根据不同的值,渲染指定DOM位置的不同数据。每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用”后退”按钮,就可以回到上一个位置。 history模式:利用 window.history.pushState API 来完成 URL 跳转而无须重新加载页面。
hash模式
#
history模式
window.history.pushState
Vue.use(vuex)会调用vuex的install方法
Vue.use(vuex)
在beforeCreate钩子前混入vuexInit方法,vuexInit方法实现了store注入vue组件实例,并注册了 vuex store的引用属性$store。
vuexInit
store
vue组件实例
vuex
$store
Vuex的state状态是响应式,是借助vue的data是响应式,将state存入vue实例组件的data中;
Vuex
state
vue
Vuex的getters则是借助vue的计算属性computed实现数据实时监听。
getters
computed
nextTick的源码分析
vue进行DOM更新内部也是调用nextTick来做异步队列控制。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。
DOM至少会在当前事件循环里面的所有数据变化完成之后,再统一更新视图。而当我们自己调用nextTick的时候,它就在更新DOM的microtask(微任务队列)后追加了我们自己的回调函数,
从而确保我们的代码在DOM更新后执行,同时也避免了setTimeout可能存在的多次执行问题。 确保队列中的微任务在一次事件循环前被执行完毕。
高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。在不改变对象自身的前提下在程序运行期间动态的给对象添加一些额外的属性或行为。
// 高阶组件(HOC)接收到的 props 应该透传给被包装组件即直接将原组件prop传给包装组件// 高阶组件完全可以添加、删除、修改 propsexport default function Console(BaseComponent) { return { props: BaseComponent.props, mounted() { console.log("高阶组件"); }, render(h) { console.log(this); // 将 this.$slots 格式化为数组,因为 h 函数第三个参数是子节点,是一个数组 const slots = Object.keys(this.$slots) .reduce((arr, key) => arr.concat(this.$slots[key]), []) .map((vnode) => { vnode.context = this._self; // 绑定到高阶组件上,vm:解决具名插槽被作为默认插槽进行渲染 return vnode; }); // 透传props、透传事件、透传slots return h( BaseComponent, { on: this.$listeners, attrs: this.$attrs, // attrs 指的是那些没有被声明为 props 的属性 props: this.$props, }, slots ); }, };}
Vue.component()方法注册全局组件。
import Vue from 'vue';// 引入loading组件 import Loading from './loading.vue';// 将loading注册为全局组件,在别的组件中通过<loading>标签使用Loading组件Vue.component('loading', Loading);
Vue.use注册插件,这接收一个参数。这个参数必须具有install方法。Vue.use函数内部会调用参数的install方法。
import Vue from 'vue';// 这个插件必须具有install方法const plugin = { install (Vue, options) { // 添加全局方法或者属性 Vue.myGlobMethod = function () {}; // 添加全局指令 Vue.directive(); // 添加混入 Vue.mixin(); // 添加实例方法 Vue.prototype.$xxx = function () {}; // 注册全局组件 Vue.component() }}// Vue.use内部会调用plugin的install方法Vue.use(plugin);
将Hello方法挂载到Vue的prototype上.
import Vue from 'vue';import Hello from './hello.js';Vue.prototype.$hello = Hello;
vue组件中就可以this.$hello('hello world')
如果子组件想修改prop中数据:
加载渲染过程
父beforeCreate -> 父created -> 父beforeMount-> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
子组件更新过程
父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated
父组件更新过程
父beforeUpdate -> 父updated
销毁过程
父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed
自定义指令提供了几个钩子函数: bind:指令第一次绑定到元素时调用 inserted:被绑定元素插入父节点时调用 update:所在组件的 VNode 更新时调用
bind
inserted
update
使用slot后可以在子组件内显示插入的新标签
compiler(整个生命周期 [kəmˈpaɪlər]) 钩子 https://webpack.docschina.org/api/compiler-hooks/ compilation(编译 [ˌkɑːmpɪˈleɪʃn]) 钩子
compiler对象包含了Webpack 环境所有的的配置信息。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。当在 webpack 环境中应用一个插件时,插件将收到此 compiler 对象的引用。可以使用它来访问 webpack 的主环境。
compiler
compilation对象包含了当前的模块资源、编译生成资源、变化的文件等。当运行webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。 compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。
compilation
compiler代表了整个webpack从启动到关闭的生命周期,而compilation 只是代表了一次新的编译过程
webpack
生命周期
编译过程
Webpack 的编译流程是一个串行的过程,从启动到结束会依次执行以下流程:
Compiler
run
entry
Loader
Chunk
1.构建打点:构建过程中,每一个Loader 和 Plugin 的执行时长,在编译 JS、CSS 的 Loader 以及对这两类代码执行压缩操作的 Plugin上消耗时长 。一款工具:speed-measure-webpack-plugin
Plugin
2.缓存:大部分 Loader 都提供了cache 配置项。cache-loader ,将 loader 的编译结果写入硬盘缓存
cache
cache-loader
3.多核编译,happypack项目接入多核编译,理解为happypack 将编译工作灌满所有线程
happypack
4.抽离,webpack-dll-plugin 将这些静态依赖从每一次的构建逻辑中抽离出去,静态依赖单独打包,Externals将不需要打包的静态资源从构建逻辑中剔除出去,使用CDN 引用
webpack-dll-plugin
Externals
CDN 引用
5.tree-shaking,虽然依赖了某个模块,但其实只使用其中的某些功能。通过 tree-shaking,将没有使用的模块剔除,来达到删除无用代码的目的。
tree-shaking
路由懒加载:改为用import引用,以函数的形式动态引入,可以把各自的路由文件分别打包,只有在解析给定的路由时,才会下载路由组件;
element-ui按需加载:引用实际上用到的组件 ;
element-ui
组件重复打包:CommonsChunkPlugin配置来拆包,把使用2次及以上的包抽离出来,放进公共依赖文件,首页也有复用的组件,也会下载这个公共依赖文件;
CommonsChunkPlugin
gzip: 拆完包之后,再用gzip做一下压缩,关闭sourcemap。
gzip
UglifyJsPlugin: 生产环境,压缩混淆代码,移除console代码
CDN部署静态资源:静态请求打在nginx时,将获取静态资源的地址进行重定向CDN内容分发网络
移动端首屏加载可以使用骨架屏,自定义loading,首页单独做服务端渲染。
如何进行前端性能优化(21种优化+7种定位方式)
热更新流程总结:
启动本地server,让浏览器可以请求本地的静态资源
server
页面首次打开后,服务端与客户端通过 websocket建立通信渠道,把下一次的 hash 返回前端
客户端获取到hash,这个hash将作为下一次请求服务端 hot-update.js 和 hot-update.json的hash
修改页面代码后,Webpack 监听到文件修改后,开始编译,编译完成后,发送 build 消息给客户端
客户端获取到hash,成功后客户端构造hot-update.js script链接,然后插入主文档
hot-update.js 插入成功后,执行hotAPI 的 createRecord 和 reload方法,获取到 Vue 组件的 render方法,重新 render 组件, 继而实现 UI 无刷新更新。
loader 它就是一个转换器,将A文件进行编译形成B文件,
plugin ,它就是一个扩展器,来操作的是文件,针对是loader结束后,webpack打包的整个过程,它并不直接操作文件,会监听webpack打包过程中的某些节点(run, build-module, program)
Babel 能把ES6/ES7的代码转化成指定浏览器能支持的代码。
css-loader 的作用是把 css文件进行转码 style-loader: 使用
css-loader
style-loader
2021.7 货拉拉、虾皮、有赞。。。高级前端岗面试整理
JS相关
JS原型及原型链
JS继承的几种方式
详解
原型继承
构造函数继承
在子类构造函数内部使用
call或apply
来调用父类构造函数,复制父类的实例属性给子类。组合继承
组合继承就是将原型链继承与构造函数继承组合在一起。
寄生组合继承
class继承
在
class
中继承主要是依靠两个东西:extends
super
Event Loop 事件循环
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务; 异步任务指的是,不进入主线程、而进入任务队列(
task queue
)的任务,只有等主线程任务执行完毕,任务队列开始通知主线程,请求执行任务,该任务才会进入主线程执行。当某个宏任务执行完后,会查看是否有微任务队列。 如果有,先执行微任务队列中的所有任务; 如果没有,在执行环境栈中会读取宏任务队列中排在最前的任务; 执行宏任务的过程中,遇到微任务,依次加入微任务队列。 栈空后,再次读取微任务队列里的任务,依次类推。
await阻塞 后面的代码执行,因此跳出async函数执行下一个微任务
Promise 与 Async/Await 区别
async/await是基于Promise实现的,看起来更像同步代码,
使用
aync/await
的话,catch能处理JSON.parse
错误:promise怎么实现链式调用跟返回不同的状态
实现链式调用:使用
.then()
或者.catch()
方法之后会返回一个promise
对象,可以继续用.then()
方法调用,再次调用所获取的参数是上个then
方法return
的内容promise的三种状态是
fulfilled
(已成功)/pengding
(进行中)/rejected
(已拒绝)状态只能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但发生改变便不可二次修改;
Promise 中使用
resolve
和reject
两个函数来更改状态;then 方法内部做的事情就是状态判断:
函数柯里化
柯里化(Currying)
是把接收多个参数的原函数变换成接受一个单一参数(原来函数的第一个参数的函数)并返回一个新的函数,新的函数能够接受余下的参数,并返回和原函数相同的结果。JS对象深克隆
解决循环引用问题,我们需要一个存储容器存放当前对象和拷贝对象的对应关系(适合用key-value的数据结构进行存储,也就是map),当进行拷贝当前对象的时候,我们先查找存储容器是否已经拷贝过当前对象,如果已经拷贝过,那么直接把返回,没有的话则是继续拷贝。
JS模块化
nodeJS
里面的模块是基于commonJS
规范实现的,原理是文件的读写,导出文件要使用exports
、module.exports
,引入文件用require
。 每个文件就是一个模块;每个文件里面的代码会用默认写在一个闭包函数里面AMD
规范则是非同步加载模块,允许指定回调函数,AMD
是RequireJS
在推广过程中对模块定义的规范化产出。AMD
推崇依赖前置,CMD
推崇依赖就近。对于依赖的模块AMD是提前执行,CMD是延迟执行。在
ES6
中,我们可以使用import
关键字引入模块,通过exprot
关键字导出模块,但是由于ES6目前无法在浏览器中执行,所以,我们只能通过babel
将不被支持的import
编译为当前受到广泛支持的require
。CommonJs 和 ES6 模块化的区别:
前端模块化:CommonJS,AMD,CMD,ES6
import 和 require 导入的区别
import 的ES6 标准模块; require 是 AMD规范引入方式;
import是编译时调用,所以必须放在文件开头;是解构过程 require是运行时调用,所以require理论上可以运用在代码的任何地方;是赋值过程。其实require的结果就是对象、数字、字符串、函数等,再把require的结果赋值给某个变量
异步加载JS方式
Set、Map、WeakSet、WeakMap
Set对象可以存储任何类型的数据。 值是唯一的,没有重复的值。
Map对象保存键值对,任意值都可以成为它的键或值。
WeakSet 结构与 Set 类似,也是不重复的值的集合 . WeakMap 对象是一组键值对的集合
不同:
WeakSet
的成员只能是对象,而不能是其他类型的值。 WeakSet 不可遍历。WeakMap
只接受对象作为键名(null
除外),不接受其他类型的值作为键名。WeakMap
的键名所指向的对象,不计入垃圾回收机制。call、apply
call( this,a,b,c )
在第一个参数之后的,后续所有参数就是传入该函数的值。apply( this,[a,b,c] )
只有两个参数,第一个是对象,第二个是数组,这个数组就是该函数的参数。共同之处:都可以用来代替另一个对象调用一个方法,将一个函数的对象上下文从初始的上下文改变为由thisObj指定的新对象。
所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次 所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。
addEventListener的第三个参数干嘛的,为true时捕获,false时冒泡
Object.prototype.toString.call()
判断对象类型词法作用域与作用域链
作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。
暂时性死区:在代码块内,使用 let 和 const 命令声明变量之前,该变量都是不可用的,语法上被称为暂时性死区。
JavaScript 采用词法作用域(lexical scoping),也就是静态作用域。
函数的作用域在函数定义的时候就决定了。
当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
new关键字做了4件事:
不应该使用箭头函数一些情况:
this/arguments
时,由于箭头函数本身不具有this/arguments
,因此它们取决于外部上下文this
即对象本身。判断数组的四种方法
Array.isArray() 判断
instanceof 判断: 检验构造函数的prototype属性是否出现在对象的原型链中,返回一个布尔值。
let a = []; a instanceof Array; //true
constructor判断: 实例的构造函数属性constructor指向构造函数
let a = [1,3,4]; a.constructor === Array;//true
Object.prototype.toString.call() 判断
let a = [1,2,3]; Object.prototype.toString.call(a) === '[object Array]';//true
TS有什么优势
静态输入:静态类型化是一种功能,可以在开发人员编写脚本时检测错误。
大型的开发项目:使用TypeScript工具来进行重构更变的容易、快捷。
更好的协作:类型安全是在编码期间检测错误,而不是在编译项目时检测错误。
更强的生产力:干净的 ECMAScript 6 代码,自动完成和动态输入等因素有助于提高开发人员的工作效率。
interface 和 type的区别
interface 只能定义对象类型。type声明可以声明任何类型。
interface 能够声明合并,两个相同接口会合并。Type声明合并会报错
type可以类型推导
框架 Vue | React
Vue3.0 新特性
双向数据绑定 Proxy
代理,可以理解为在对象之前设置一个“拦截”,当该对象被访问的时候,都必须经过这层拦截。意味着你可以在这层拦截中进行各种操作。比如你可以在这层拦截中对原对象进行处理,返回你想返回的数据结构。
ES6 原生提供 Proxy 构造函数,MDN上的解释为:Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。
新增的属性,并不需要重新添加响应式处理,因为 Proxy 是对对象的操作,只要你访问对象,就会走到 Proxy 的逻辑中。
Vue3 Composition API
Vue3.x
推出了Composition API
。setup
是组件内使用 Composition API的入口。setup
执行时机是在beforeCreate
之前执行.reactive、ref 与 toRefs、isRef
Vue3.x 可以使用reactive和ref来进行数据定义。
watchEffect 监听函数
computed可传入get和set
用于定义可更改的计算属性
使用TypeScript和JSX
setup
现在支持返回一个渲染函数,这个函数返回一个JSX
,JSX
可以直接使用声明在setup
作用域的响应式状态:Vue 跟React 对比?
相同点:
不同点: Vue 是
MVVM
框架,双向数据绑定,当ViewModel
对Model
进行更新时,通过数据绑定更新到View
。React是一个单向数据流的库,状态驱动视图。
State --> View --> New State --> New View
ui = render (data)
模板渲染方式不同。React是通过JSX来渲染模板,而Vue是通过扩展的HTML来进行模板的渲染。
组件形式不同,Vue文件里将HTML,JS,CSS组合在一起。react提供class组件和function组
Vue封装好了一些v-if,v-for,React什么都是自己实现,自由度更高
Vue 初始化过程,双向数据绑定原理
监听器
Observer
,用来劫持并监听所有属性,如果有变动的,就通知订阅者。 订阅者Watcher
,可以收到属性的变化通知并执行相应的函数,从而调用对应update更新视图。v-model
指令,它能轻松实现表单输入和应用状态之间的双向绑定。computed: 支持缓存,只有依赖数据结果发生改变,才会重新进行计算,不支持异步操作,如果一个属性依赖其他属性,多对一,一般用computed
watch: 数据变,直接触发相应操作,支持异步,监听数据必须
data
中声明过或者父组件传递过来的props中
的数据,当数据变化时,触发其他操作,函数有两个参数vue-router实现原理
端路由简介以及vue-router实现原理 原理核心就是 更新视图但不重新请求页面。路径之间的切换,也就是组件的切换。 vue-router实现单页面路由跳转模式:hash模式、history模式。根据设置mode参数
hash模式
:通过锚点值的改变,根据不同的值,渲染指定DOM位置的不同数据。每一次改变#
后的部分,都会在浏览器的访问历史中增加一个记录,使用”后退”按钮,就可以回到上一个位置。history模式
:利用window.history.pushState
API 来完成 URL 跳转而无须重新加载页面。vuex实现原理:
Vue.use(vuex)
会调用vuex的install方法在
beforeCreate
钩子前混入vuexInit
方法,vuexInit
方法实现了store
注入vue组件实例
,并注册了vuex
store
的引用属性$store
。Vuex
的state
状态是响应式,是借助vue
的data
是响应式,将state
存入vue实例组件的data中;Vuex
的getters
则是借助vue的计算属性computed
实现数据实时监听。nextTick 的原理以及运行机制?
nextTick的源码分析
vue进行DOM更新内部也是调用nextTick来做异步队列控制。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。
DOM至少会在当前事件循环里面的所有数据变化完成之后,再统一更新视图。而当我们自己调用nextTick的时候,它就在更新DOM的microtask(微任务队列)后追加了我们自己的回调函数,
从而确保我们的代码在DOM更新后执行,同时也避免了setTimeout可能存在的多次执行问题。 确保队列中的微任务在一次事件循环前被执行完毕。
Vue 实现一个高阶组件
高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。在不改变对象自身的前提下在程序运行期间动态的给对象添加一些额外的属性或行为。
Vue.component()、Vue.use()、this.$xxx()
Vue.component()方法注册全局组件。
Vue.use注册插件,这接收一个参数。这个参数必须具有install方法。Vue.use函数内部会调用参数的install方法。
将Hello方法挂载到Vue的prototype上.
vue组件中就可以this.$hello('hello world')
Vue父组件传递props数据,子组件修改参数
如果子组件想修改prop中数据:
Vue父子组件生命周期执行顺序
加载渲染过程
子组件更新过程
父组件更新过程
销毁过程
Vue 自定义指令
自定义指令提供了几个钩子函数:
bind
:指令第一次绑定到元素时调用inserted
:被绑定元素插入父节点时调用update
:所在组件的 VNode 更新时调用使用slot后可以在子组件内显示插入的新标签
webpack 及工程化
webpack的生命周期,及钩子
compiler(整个生命周期 [kəmˈpaɪlər]) 钩子 https://webpack.docschina.org/api/compiler-hooks/ compilation(编译 [ˌkɑːmpɪˈleɪʃn]) 钩子
compiler
对象包含了Webpack 环境所有的的配置信息。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。当在 webpack 环境中应用一个插件时,插件将收到此 compiler 对象的引用。可以使用它来访问 webpack 的主环境。compilation
对象包含了当前的模块资源、编译生成资源、变化的文件等。当运行webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。 compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。compiler
代表了整个webpack
从启动到关闭的生命周期
,而compilation
只是代表了一次新的编译过程
webpack 编译过程
Webpack 的编译流程是一个串行的过程,从启动到结束会依次执行以下流程:
Compiler
对象,加载所有配置的插件,执行对象的run
方法开始执行编译;entry
找出所有的入口文件;Loader
对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;Loader
翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;Chunk
,再把每个Chunk
转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;优化项目的webpack打包编译过程
1.构建打点:构建过程中,每一个
Loader
和Plugin
的执行时长,在编译 JS、CSS 的 Loader 以及对这两类代码执行压缩操作的 Plugin上消耗时长 。一款工具:speed-measure-webpack-plugin2.缓存:大部分 Loader 都提供了
cache
配置项。cache-loader
,将 loader 的编译结果写入硬盘缓存3.多核编译,
happypack
项目接入多核编译,理解为happypack
将编译工作灌满所有线程4.抽离,
webpack-dll-plugin
将这些静态依赖从每一次的构建逻辑中抽离出去,静态依赖单独打包,Externals
将不需要打包的静态资源从构建逻辑中剔除出去,使用CDN 引用
5.
tree-shaking
,虽然依赖了某个模块,但其实只使用其中的某些功能。通过tree-shaking
,将没有使用的模块剔除,来达到删除无用代码的目的。首屏加载优化
路由懒加载:改为用
import
引用,以函数的形式动态引入,可以把各自的路由文件分别打包,只有在解析给定的路由时,才会下载路由组件;element-ui
按需加载:引用实际上用到的组件 ;组件重复打包:
CommonsChunkPlugin
配置来拆包,把使用2次及以上的包抽离出来,放进公共依赖文件,首页也有复用的组件,也会下载这个公共依赖文件;gzip: 拆完包之后,再用
gzip
做一下压缩,关闭sourcemap。UglifyJsPlugin: 生产环境,压缩混淆代码,移除console代码
CDN部署静态资源:静态请求打在nginx时,将获取静态资源的地址进行重定向CDN内容分发网络
移动端首屏加载可以使用骨架屏,自定义loading,首页单独做服务端渲染。
如何进行前端性能优化(21种优化+7种定位方式)
webpack 热更新机制
热更新流程总结:
启动本地
server
,让浏览器可以请求本地的静态资源页面首次打开后,服务端与客户端通过 websocket建立通信渠道,把下一次的 hash 返回前端
客户端获取到hash,这个hash将作为下一次请求服务端 hot-update.js 和 hot-update.json的hash
修改页面代码后,Webpack 监听到文件修改后,开始编译,编译完成后,发送 build 消息给客户端
客户端获取到hash,成功后客户端构造hot-update.js script链接,然后插入主文档
hot-update.js 插入成功后,执行hotAPI 的 createRecord 和 reload方法,获取到 Vue 组件的 render方法,重新 render 组件, 继而实现 UI 无刷新更新。
webpack的 loader和plugin介绍,css-loader,style-loader的区别
loader 它就是一个转换器,将A文件进行编译形成B文件,
plugin ,它就是一个扩展器,来操作的是文件,针对是loader结束后,webpack打包的整个过程,它并不直接操作文件,会监听webpack打包过程中的某些节点(run, build-module, program)
Babel 能把ES6/ES7的代码转化成指定浏览器能支持的代码。
css-loader
的作用是把 css文件进行转码style-loader
: 使用