- 对目前项目做的贡献
- 项目做过哪写优化
- 为什么React有componentShoudUpdate而Vue没有
- Service Worker有哪些作用
- 绝对定位、固定定位和z-index
- VDom以及key属性的作用, 为什么不建议用index或者随机数当做key
- Vue2.x组件通信有哪些方式?
- Flex 应用场景
- stick-footer
- Vue3.0
- 图片懒加载
- sessionStorage用途
- new 做了什么
- 箭头函数和普通函数的区别
- TS中常用的泛型
- ts interface 和 type的区别
- npm版本语义化控制
- Promise
- WebPack plugin 和 loader 区别
- let 和 var的区别
- 如何实现一个继承
- Object.entries() 和 for …in 的区别
- 深度克隆 解决循环引用
- Set WeakSet Map WeakMap
- 节流 防抖
- 函数 变量提升
- P8
- js sleep
- hash和history路由模式区别
- Vue的优点
- require和import的区别
- 用typescript写的代码执行的时候会更快吗?
对目前项目做的贡献
项目做过哪写优化
- table渲染
- 子组件回退刷新(diff记录子组件是否有操作,父组件是否需要刷新, 列:详情页操作后回退父组件刷新)
- https://juejin.im/post/5e649e3e5188252c06113021
- https://juejin.im/post/5d690c726fb9a06b155dd40d
- https://juejin.im/post/5e8b261ae51d4546c0382ab4
- 路由懒加载原理 https://juejin.im/post/5ed7b687518825432632981e
- 尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
- v-if和v-for不能连用
- 如果需要使用v-for给每项元素绑定事件时使用事件代理
- SPA 页面采用keep-alive缓存组件
- 在更多的情况下,使用v-if替代v-show
- key保证唯一
- 使用路由懒加载、异步组件
- 防抖、节流
- 第三方模块按需导入
- 长列表滚动到可视区域动态加载
- 图片懒加载
为什么React有componentShoudUpdate而Vue没有
1.应为React的diff是依据dom的变化,通过setData设置数据, Vue底层实现了监听数据的diff,初始化时对数据做了数据劫持.
在 React 应用中,当某个组件的状态发生变化时,它会以该组件为根,重新渲染整个组件子树。当然,这可以通过shouldComponentUpdate这个生命周期方法来进行控制purerender,但Vue将此视为默认的优化。
vue中实现数据绑定靠的是数据劫持(Object.defineProperty())+发布-订阅模式。在 Vue 应用中,组件的依赖是在渲染过程中自动追踪的,所以系统能精确知晓哪个组件需要被重渲染。你可以理解为每一个组件都已经自动获得了shouldComponentUpdate,并且没有上述的子树问题限制。
https://www.jianshu.com/p/6e124ad23c68
react 组件高效渲染: https://www.jianshu.com/p/100a55978253Service Worker有哪些作用
绝对定位、固定定位和z-index
- 一旦给元素加上
absolute或float就相当于给元素加上了display:block absolute元素覆盖正常文档流内元素(不用设z-index,自然覆盖)- 可以减少重绘和回流的开销(如
absolute+ top:-9999em,或absolute + visibility:hidden,将动画效果放到absolute元素中)VDom以及key属性的作用, 为什么不建议用index或者随机数当做key
Vue2.x组件通信有哪些方式?
- 父子组件通信
父->子props,子->父$on、$emit
获取父子组件实例$parent、$childrenRef获取实例的方式调用组件的属性或者方法Provide、inject官方不推荐使用,但是写组件库时很常用 - 兄弟组件通信
Event Bus实现跨组件通信Vue.prototype.$bus = new VueVuex 跨级组件通信
Vuex$attrs、$listenersProvide、injectFlex 应用场景
绝对居中
.box{display: flex;width: 100%;height: 500px;background: #CCCCCC;justify-content: center; //主轴居中align-items: center; //交叉轴居中}.box div{width: 50px;height: 50px;background: red;border:2px solid #007AFF;}<div class="box"><div class="div1">你大爷</div></div>
图片展示, 指定宽度,不指定数量
.box{display: flex;width: 100%;height: 500px;background: #CCCCCC;justify-content: flex-start;align-content: flex-start;/*交叉轴方向行间排列*/align-items: flex-start;flex-wrap: wrap; /*设置换行*/padding: 10px;}.box div{width: 20%;height: 50px;background: red;border:2px solid #007AFF;margin: 5px;/*间距*/box-sizing: border-box;}
stick-footer
<html lang="en"><head><meta charset="UTF-8" /><title>stick footer</title><style>html,body {height: 100%;width: 100%;margin: 0;padding: 0;}.wrapper {height: 100%;}.content {display: flex;justify-content: flex-start;align-items: flex-start;align-content: flex-start;flex-wrap: wrap;min-height: 100%;padding-bottom: 50px;}.content div {height: 200px;width: 20%;background-color: aquamarine;border: 1px solid blueviolet;box-sizing: border-box;}.footer {height: 50px;margin-top: -50px;}</style></head><body><div class="wrapper"><div class="content"><div>tas</div><div>tas</div><div>tas</div><div>tas</div><div>tas</div><div>tas</div><div>tas</div></div></div><div class="footer">footer</div></body></html>
Vue3.0
图片懒加载
// 1. 图片懒加载原理// 给每张图片添加一个data-xxx的属性用于存放图片的src,检测到图片进入视野中的时候把data-xxx的属性赋给src// Promise 延时输出function delay(time: number) {return new Promise((resolve, reject) => {setTimeout(() => {resolve()}, time);})}const getTop = (evt: any) => {return evt.offsetTop}// 图片懒加载const lazyLoad = () => {let imags = document.querySelectorAll('img')// 可是区域高度const h = window.innerHeight// 滚动区域const s = document.body.scrollTop || document.documentElement.scrollTopfor (let i = 0; i < imags.length; i++) {if (h + s < getTop(imags[i])) {setTimeout(() => {const temp = new Image()temp.src = imags[i].getAttribute('data-src') || ''temp.onload = () => {imags[i].src = temp.getAttribute('data-src') || ''}}, 2000)}}}// 每一次滚动都回去计算?window.onscroll = () => {lazyLoad()}
sessionStorage用途
敏感账号的一次性登陆,sessionId不想保存在cookie,就保存在sessionStorage。
new 做了什么
- 内部创建对象
- 将构造函数的this指向这个对象
- 执行构造函数赋值
- 返回这个对象实例
var obj = newObject(); // 创建一个空对象
obj.proto = F.prototype; // obj的proto指向构造函数的prototypevar result = F.call(obj); // 把构造函数的this指向obj,并执行构造函数把结果赋值给resultif (typeof(result) === ‘object’) {
objB = result; // 构造函数F的执行结果是引用类型,就把这个引用类型的对象返回给objB
} else {
objB = obj; // 构造函数F的执行结果是值类型,就返回obj这个空对象给objB
}
箭头函数和普通函数的区别
- 箭头函数简洁 清晰
- 箭头函数this被创建后不可改变(call, apply, bind也不行)
- 箭头函数的this指向执行上下文, 自身没有this
- 不可用作构造函数 不能被new 没有prototype
- 没有自己的arguments, 在上下文中
- 不能用作Generator函数, 不能使用yeild关键字
TS中常用的泛型
- Partial
部分的 type Partial = { [P in keyof T]?: T[P] }; - Required
必须的 type Required = { [P in keyof T]-?: T[P] }; - Mutable
可变的 type Mutable = { -readonly [P in keyof T]: T[P] }; - Readonly
只读的 type Mutable = { +readonly [P in keyof T]: T[P] }; - Omit
忽略 type Omit = Pick > - Exclude
排除 type Exclude = T extends U ? never : T; - Extract
提取 type Extract = T extends U ? T : never; - Pick
挑选 type Pick = { [P in K]: T[P] }; - Record
记录 type Record = { [P in K]: T };
ts interface 和 type的区别
- 都可以被扩展 interface extends ; type &
- interface 可以声明合并
interface User{ name: string; age: numer }
interface User{ sex: string}
-> User{name: string; age: numer; sex: string}
3. type可以给基本数据类型定义别名, 联合声明类型, 元祖等;
- type 语句中还可以使用 typeof 获取实例的 类型进行赋值
type Name = String
type a = string | number
type b = [string,number]
npm版本语义化控制
X.X.X 格式
主板本 . 向下兼容新增功能 . 补丁版本
Promise
Promise的三种状态:
:::success
pending、fulfilled、rejected(未决定,履行,拒绝),同一时间只能存在一种状态,且状态一旦改变就不能再变。promise是一个构造函数,promise对象代表一项有两种可能结果(成功或失败)的任务,它还持有多个回调,出现不同结果时分别发出相应回调。
:::
1.初始化,状态:pending
2.当调用resolve(成功),状态:pengding=>fulfilled
3.当调用reject(失败),状态:pending=>rejected
const PENDING = “pending”;//Promise会一直保持挂起状态,知道被执行或拒绝。
const FULFULLED = “fulfilled”;
const REJECTED = “rejected”;
class Promise{
constructor(exector){
let self = this;//缓存当前promise对象
self.status = PENDING;//初始状态,对promise对象调用state(状态)方法,可以查看其状态是“pending”、”resolved”、还是”rejected“
self.value = undefined;// fulfilled状态时 返回的信息
self.reason = undefined;// rejected状态时 拒绝的原因
self.onResolveCallBacks = [];// 存储resolve(成功)状态对应的onFulfilled函数
self.onRejectCallBacks = [];// 存储rejected(失败)状态对应的onRejected函数
let resolve = (value) => {//成功
if(self.status === PENDING){//如果成功则,状态由pending=>fulfilled
self.status = FULFULLED;
self.value = value;
self.onResolveCallBacks.forEach(cb=>cb(self.value));//执行发布
}
}
let reject = (reason) => {//失败
if(self.status === PENDING){//如果失败,则状态由pending=>rejected
self.status = REJECTED;
self.reason = reason;
self.onRejectCallBacks.forEach(cb=>cb(self.reason));//执行发布
}
}
try{
exector(resolve,reject)
}catch(e){
reject(e)
}
}
then(onFulfilled,onRejected){
let self=this;
if(self.status === FULFULLED){
onFulfilled(self.value);//成功值
}
if(self.status === REJECTED){
onFulfilled(self.reason);//拒绝原因
}
if(self.status === PENDING){
self.onResolveCallBacks.push(onFulfilled);//订阅发布
self.onRejectCallBacks.push(onRejected);//订阅发布
}
}
//promise的决议结果只有两种可能:完成和拒绝,附带一个可选的单个值。如果Promise完成,那么最终的值称为完成值;如果拒绝,那么最终的值称为原因。Promise只能被决议(完成或拒绝)一次。之后再次试图完成或拒绝的动作都会被忽略。
}
new Promise((resolve,reject)=>{
resolve(“挖坑妹”);
//异步处理
//处理结束后、调用resolve或reject
}).then((data)=>{
console.log(data);//“挖坑妹”
},(reason)=>{
console.log(reason);
})
3.promise的优缺点
优点:
1.Promise 分离了异步数据获取和业务逻辑,有利于代码复用。
2.可以采用链式写法
3.一旦 Promise 的值确定为fulfilled 或者 rejected 后,不可改变。
缺点:
代码冗余,语义不清。
二、为什么用Promise?
1.解决回调地狱
回调地狱:发送多个异步请求时,每个请求之间相互都有关联,会出现第一个请求成功后再做下一个请求的情况。我们这时候往往会用嵌套的方式来解决这种情况,但是这会形成”回调地狱“。如果处理的异步请求越多,那么回调嵌套的就越深。出现的问题:
1.代码逻辑顺序与执行顺序不一致,不利于阅读与维护。
2.异步操作顺序变更时,需要大规模的代码重构。
3.回调函数基本都是匿名函数,bug追踪困难。
const request = url => {
return new Promise((resolve,reject) => {
$.get(url,params => {
resolve(params)
});
});
};
request(url).then(params1 => {
return request(params1.url);
}).then(params2 => {
return request(params2.url);
}).then(params3 => {
console.log(params3);
}).catch(err => throw new Error(err));
2.解决异步
我们都知道js是单线程执行代码,导致js的很多操作都是异步执行(ajax)的,以下是解决异步的几种方式:
1.回调函数(定时器)。
2.事件监听。
3.发布/订阅。
4.Promise对象。(将执行代码和处理结果分开)
5.Generator。
6.ES7的async/await。
三、怎么用Promise?
1.then方法(异步执行)
当resolve(成功)/reject(失败)的回调函数
//onFulfilled 是用来接收promise成功的值
//onRejected 是用来接收promise失败的原因
promise.then(onFulfilled,onRejected)
2.resolve(成功)
调用onFulfilled
const promise = new Promise((resolve,reject)=>{
resolve(‘fulfilled’);//状态:pending=>fulfilled
});
promise.then(result =>{//onFulfilled调用
console.log(result);//‘fulfilled’
},result =>{//onRejected不调用
})
//注:resolve使用
Promise.resolve(‘hellow world’)相当于
//相当于
const promise = new Promise(resolve=>{
resolve(‘hellow world’);
})
3.reject(失败)
调用onRejected
const promise = new Promise((resolve,reject)=>{
reject(‘rejected’);//状态:pending=>rejected
});
promise.then(result =>{//onFulfilled不调用
},result =>{//onRejected调用
console.log(result);//‘rejected’
})
//注:reject使用
Promise.reject(‘err’)相当于
//相当于
const promise = new Promise((resolve,reject)=>{
reject(‘err’);
});
4.catch
链式写法中可以捕获前面then中发送的异常,这种写法的好处在于先执行promise操作,然后根据返回的结果(成功或失败)来调用onFulfilled(或者onRrejected)函数。
promise.then(onFulfilled).catch(onRrejected);
5.all
Promise.all接收一个promise对象数组为参数,处理并行异步操作会用到,但是需要全部为resolve才能调用。这种情况是几个任务可以并行执行
const promise1= new Promise((resolve,reject)=>{
resolve(‘promise1’);
});
const promise2= new Promise((resolve,reject)=>{
resolve(‘promise2’);
});
const promise3= new Promise((resolve,reject)=>{
resolve(‘promise3’);
});
Promise.all([promise1, promise2, promise3]).then(data => {
console.log(data);
// [‘promise1’, ‘promise2’, ‘promise3’] 结果顺序和promise实例数组顺序是一致的
}, err => {
console.log(err);
});
可以从一个promise对象派生出新的promise对象,我们可以要求代表着并行任务的两个promise对象合并成一个promise对象,由后者负责通知前面的那些任务都已完成。也可以要求代表着任务系列中首要任务的Promise对象派生出一个能代表任务系列中末任务的Promise对象,这样后者就能知道这一系列的任务是否均已完成。
6.race
Promise.race接收一个promise对象数组为参数,只要有一个promise对象进入Fulfilled或者Rejected状态的话,就会进行后面的处理。这可以解决多个异步任务的容错
function racePromise(time){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(time);
},time)
})
}
var startDate = Date.now();
Promise.race([
racePromise(5),
racePromise(50),
racePromise(500),
]).then(function(values){
console.log(values);
})
WebPack plugin 和 loader 区别
一、webpack的打包原理
- 识别入口文件
- 通过逐层识别模块依赖(Commonjs、amd或者es6的import,webpack都会对其进行分析,来获取代码的依赖)
- webpack做的就是分析代码,转换代码,编译代码,输出代码
- 最终形成打包后的代码
二、什么是loader
loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中
- 处理一个文件可以使用多个loader,loader的执行顺序和配置中的顺序是相反的,即最后一个loader最先执行,第一个loader最后执行
- 第一个执行的loader接收源文件内容作为参数,其它loader接收前一个执行的loader的返回值作为参数,最后执行的loader会返回此模块的JavaScript源码
三、什么是plugin
在webpack运行的生命周期中会广播出许多事件,plugin可以监听这些事件,在合适的时机通过webpack提供的API改变输出结果。
四、loader和plugin的区别
对于loader,它是一个转换器,将A文件进行编译形成B文件,这里操作的是文件,比如将A.scss转换为A.css,单纯的文件转换过程
plugin是一个扩展器,它丰富了webpack本身,针对是loader结束后,webpack打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听webpack打包过程中的某些节点,执行广泛的任务
let 和 var的区别
- var存在变量提升, let不存在
暂时性死区;只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。 :::warning 总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区” :::
- let 不允许重复定义
-
const 常量(内存地址不变) 值类型不可以变, 引用类型可以变
如何实现一个继承
- 如何实现一个继承 (Object.create & call)在 ES6 时代可以简单的通过 class & extends 实现继承,ES5 时代用如下方法
function A () {}
function B () {
A.call(this)
}
B.prototype = Object.create(A.prototype)
// B.prototype = new A() 不推荐
Object.entries() 和 for …in 的区别
object.entries() 返回自身可枚举属性; for …in 会返回原型链上的属性
深度克隆 解决循环引用
function deepCopy(data, hash = new WeakMap()) {if(typeof data !== 'object' || data === null){throw new TypeError('传入参数不是对象')}// 判断传入的待拷贝对象的引用是否存在于hash中if(hash.has(data)) {return hash.get(data)}let newData = {};const dataKeys = Object.keys(data);/// for(let key in data){}dataKeys.forEach(value => {const currentDataValue = data[value];// 基本数据类型的值和函数直接赋值拷贝if (typeof currentDataValue !== "object" || currentDataValue === null) {newData[value] = currentDataValue;} else if (Array.isArray(currentDataValue)) {// 实现数组的深拷贝newData[value] = [...currentDataValue];} else if (currentDataValue instanceof Set) {// 实现set数据的深拷贝newData[value] = new Set([...currentDataValue]);} else if (currentDataValue instanceof Map) {// 实现map数据的深拷贝newData[value] = new Map([...currentDataValue]);} else {// 将这个待拷贝对象的引用存于hash中hash.set(data,data)// 普通对象则递归赋值newData[value] = deepCopy(currentDataValue, hash);}});return newData;}
Set WeakSet Map WeakMap
- Set
- 成员唯一、无序且不重复
- [value, value],键值与键名是一致的(或者说只有键值,没有键名)
- 可以遍历,方法有:add、delete、has
- WeakSet
- 成员都是对象
- 成员都是弱引用,可以被垃圾回收机制回收,可以用来保存DOM节点,不容易造成内存泄漏
- 不能遍历,方法有add、delete、has
- Map
- 本质上是键值对的集合,类似集合
- 可以遍历,方法很多可以跟各种数据格式转换
- WeakMap
- 只接受对象作为键名(null除外),不接受其他类型的值作为键名
- 键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的
- 不能遍历,方法有get、set、has、delete
节流 防抖
防抖(连续的事件,停止触发后延迟触发)
- 搜索框搜索输入。只需用户最后一次输入完,再发送请求
- 手机号、邮箱验证输入检测
窗口大小Resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。
function debounce(fn, delay){let timer = nullreturn function(){timer && clearTimeout(timer)timer = setTimeout(()=>{fn && fn.apply(this, arguments)}, delay )}}
节流(间隔一段时间执行一次回调)
滚动加载,加载更多或滚到底部监听
- 谷歌搜索框,搜索联想功能
- 高频点击提交,表单重复提交
function throttle(fn, delay){let timer = nullreturn function(){if(timer){ return }timer = setTimeout(()=>{fn && fn.apply(this, arguments)timer = null}, delay)}}
函数 变量提升
var b = 13function b(){b = 5;console.log(b);}b()
P8
1.简单做个自我介绍开始打断了我,嗯。百度的经历你不用介绍了,我在lBS6年,你的老大我很熟。
2.我也来自我介绍下,我是阿里支付宝大前端负责人,我们的团队分工很多,你看你喜欢哪一个类型的。
3.聊一下近一年大前端发展趋势,未来走向,如何关注的,然后请总结下端内前端的核心痛点,你怎么解决。
4.刚听你说了很多,有性能有工程有基建。有几个核心的词我们分别聊一下吧。
5.你说了JIT那内部DSL和外部DSL的本质区别是什么。你设计的话该考虑些什么,比如语法噪音?
6.嗯 那内部DSL的解释器和编译器该如何设计呢?你对那种语言的相对了解呢。
7.刚刚我们聊到C、LISP等,这里你能说下函数式编程的理解么?他是个数学问题么?
8.听你说了很多,包括你聊到了SKI,那我们在往深了说C与LISP最最底层的核心本质区别是什么呢?
9.刚刚你还说了 基于web ai。low code、node code 你能大概说下人工智能的权重、层、 逻辑回归、欠拟合与过拟合等核心的概念么。
10.嗯,感觉你对AI确实了解的不深入还,那我们回归一个现实问题吧。假设给你时间去学习Tensorflow.js,完成组内今年的任务是根据同学们传递给你输入TSX代码,你如何通过AI知道他是一个什么样的组件?并生成代码 图片好说我们用CNN,代码呢?你用AST就可以么?
11.嗯,你的思想还是对的。AI我们到这,对了你有系统学习过AI么?
12.那AI吹得很响 最本质的目标是什么?那你觉得Tensorflow.js相对于Tensorflow最大坑是什么?
13.好。我们回到端内,因为我们是支付宝。你觉得端内前端工程师的前途是什么?如果交给你来管理这个团队,你能为这个团队做什么。端内H5都有哪些技术形态,最迫切的问题是什么?
14.为什么选择了创业?你有对外的产品么?这平时时间是怎么分配的。看到了你用了ServerLess。如果让你从头开发个对外的 ServerLess产品你如何做呢。
15.最后你认为什么是大前端呢,到底什么才是技术深度,一个前端工程师的终极职业规划什么样。你来支付宝你希望做什么?
16.我们的面试周期会非常漫长 应该会有6-8面。你能接受么?
js sleep
function sleep1(delay) {var start = Date.now();while (Date.now() < start + delay);}
const sleep = (time) => new Promise((resolve) => setTimeout(resolve, time))
hash和history路由模式区别
hash和history的区别
触发hashChange事件 浏览器前进后退改变 URL、<a>标签改变 URL、window.location改变URL。
