1. 前端如何进行性能优化?
前端进行性能优化的方案很多,这里只列举部分。
在实际应用中不要贪多,想着都用上,要对网站的主要用户群体进行针对性优化。
- 降低请求量
- 合并资源,减少 http 请求数量。
- lazyLoad,如图片懒加载。分批加载,每次只加载一部分。
- 使用字体图标或 CSS 绘制,来代替部分图片。
- 加快请求速度
- 预解析 DNS
- 使用 HTTP2.0
- 并行加载
- CDN 分发
- webP,对图片进行压缩,减少图片体积。
- minify/gzip 压缩,对 css、js 等文件进行压缩(去除空格、回车等),减少文件体积
- 补充知识:
- webP 的优势体现在它具有更优的图像数据压缩算法,能带来更小的图片体积,而且图像质量几乎无差异。同时具备了无损和有损的压缩模式、Alpha 透明以及动画的特性,在 JPEG 和 PNG 上的转化效果都相当优秀。
- Minify 把 CSS 和 JS 压缩和削减(Minify:去掉空格回车符等),以及把多个 CSS,JS 文件整合到一个文件里。
- 缓存
- HTTP 协议缓存请求
- 离线缓存 manifest
- 本地缓存 localStorage
- 补充知识:
- GET 请求可以缓存,POST 请求不能缓存。GET 请求后退/刷新无害,POST 后退/刷新则会致使重新提交数据
- 渲染
- JS 优化,如防抖、节流、事件委托、减少重排重绘等。
- CSS 优化,如提取公共样式减少代码量、减少选择器嵌套层数等。
- 服务器端渲染
- 使用 Web Workers
- CSS 写在文件头部,JS 写在文件底部。
- 补充知识:
- 客户端渲染: 获取 HTML 文件,根据需要下载 JavaScript 文件,运行文件,生成 DOM,再渲染。
- 服务端渲染:服务端返回 HTML 文件,客户端只需解析 HTML,使首屏渲染快,SEO(搜索引擎优化) 好。
2. 描述事件委托的原理并说说它的好处?
- 事件冒泡 和 DOM 遍历
- 好处
- 可以大量节省内存占用,减少事件注册。(比如 ul 上代理所有 li 的 click 事件就很不错)
- 可以实现当新增子对象时,无需再对其进行事件绑定 (对于动态内容部分尤为合适)
- 缺点
- 事件委托基于冒泡, 对于不冒泡的事件不支持
- 如果把所有事件都用事件代理,可能会出现事件误判。(即本不该被触发的事件被绑定上了事件)
3. es5 实现继承的方式有哪些?
-
原型链继承 (子类的原型等于父类的实例)
- 缺点:
- 子类无法向父类的构造函数传参. new Sub( name )父类也接收不到
- 子类修改父类原型中引用类型的值时,原型的属性发生改变,导致之后的实例的值都发生改变
- 缺点:
-
借用构造函数继承 (通过 call 、apply 将父类构造函数的方法引入子类)
- 优点
- 解决了子类实例向父类传参的缺点
- 解决了子类实例修改父类原型属性或者方法的问题
- 可以继承多个构造函数属性(call 多个)
- 缺点
- 无法继承父类原型的方法和属性
- 每个新实例都有父类构造函数的副本,无法复用,臃肿。
- 优点
-
组合继承 (j 原型链继承 + 构造函数继承 )
- 优点
- 可传参 + 继承了父类的原型
- 缺点
- 两次调用父类的构造函数(耗内存)
// 父类 function Super(name) { this.name = name; } Super.prototype = { name: "我要走走看看", age: 18, hobby: { a: "篮球" }, }; // 子类 function Sub(name) { Super.call(this, name); // 1: 调用第一次 } Sub.prototype = new Super(); // 2: 调用第二次
- 优点
-
原型式继承
- 缺点
- 无法复用,原型都是手动添加上去的
function Super(name) { this.name = name } Super.prototype.sayName = function() { console.log(this.name) } const a = new Super('姓名') // 第一种 function inherit(o) { function F() {} F.prototype = o return new F() } const b1 = inherit(a) // 第二种 普通用法 // const b1 = Object.create(a) // Object.create 增加额外的属性 const b1 = Object.create(a, { age: { value: 15, writable: true, }, address: { value: '上海', writable: true, }, }) console.log(b.name) // 姓名
- 缺点
-
寄生组合式继承(常用)
- 优点
- 既可以传参又实现了原型的复用
function Super(name) { this.name = name this.age = 21 } Super.prototype = Object.assign({ sayName: () => { console.log(this.name) }, address: '上海', hobby: ['唱歌'], }) function Sub(name) { Super.call(this, name) // 继承父类构造函数的属性 } Sub.prototype = Object.create(Super.prototype) // 继承父类的原型 const sub1 = new Sub('姓名1') console.log(this.name) // 姓名1
- 优点
4. 实现深浅拷贝的方式都有哪些?如何判断是深拷贝还是浅拷贝?
- 方法
-
jQ对象上的extend方法
var obj = {name:1, girl:{name: '花花'}} var newObj = {} // 在引入jq后 // true为深拷贝;反之false为浅拷贝 => 目标对象 => 源对象 $.extend(true, newObj, obj)
-
JSON对象的parse和stringift
-
递归遍历
-
- 判断
- 改变拷贝后的新数据,如果源数据发生变化则是浅拷贝,反之则是深拷贝
5. js 如何检测数据的类型?这些方法都有什么局限性?
- typeof
- 只能检测除null的基本数据类型,检测引用数据类型除function以外,都是object
- instanceof
- 可以准确地判断复杂引用数据类型(原型链也会检测)
- 基本数据类型检测补全(undefined、null、symbol)
- constructor
- 适合检测引用数据类型(不会检测原型链)
- Object.prototype.toString.cal()
- 适用于所有类型(除对象可以不改变this指向 => 可以不加.call)
6. 原型、构造函数、实例对象之间有什么关系?原型指的是什么?原型的作用有哪些?
- 关系
- 任何一个对象都有constructor属性,实例化对象的constructor属性指向构造函数
- 原型也是对象,也有constructor属性,构造函数的原型的constructor属性指向构造函数
- 实例对象的__proto__指向于它构造函数的原型
- 原型是什么
- 任何一个函数都有prototype属性,它本身是一个对象,称之为原型、
- 原型作用
- 存放所有实例对象需要数据共享(属性或方法),
- 节省了内存空间,
- 避免了全局污染
7. 描述 Vue 响应式是怎么实现的?
- vue.js是采用数据劫持与发布者、订阅者的模式的方式,通过Object.defineProperty()来劫持各个属性的getter、setter,在数据变动时发布消息给订阅者,触发相应的监听回调来渲染视图
8. 不建议用 index 做 key 的原因是什么?key 的作用有哪些?
- 原因
- 用 index 作为 key 时,在对数据进行,逆序添加,逆序删除等破坏顺序的操作时,会产生没必要的真实 DOM更新,从而导致效率低
- 作用
- key 是虚拟 DOM 对象的唯一标识
- 在 diff 算法中 key 起着极其重要的作用。
9. 如何解决 vue 为 data 中的对象 新增属性无法更新视图?无法更新视图的原因是什么?
- 解决
- 对象添加少量的新属性,可以直接采用Vue.set()
- 对象添加大量的新属性,则通过Object.assign()创建新对象
- 强制刷新时,可采取$forceUpdate() (不建议)
- nextTick()
- 原因
- 组件初始化时,对data中的obj进行递归遍历,对obj的每一个属性进行劫持,添加set,get方法,新增对象属性没有通过Obgect.defineProperty设置为响应式,所以不会渲染视图
10. vue 路由里的$router和$route 的区别是什么?
- $router
- $router是全局 router 的实例, 包含了一些路由的跳转方法 push, replace等
- $route
- $route是当前激活路由的信息对象。每个对象都是局部的,可以获取当前路由的 path, name, params, query 等属性
11. watch 和 computed 的区别?
- watch没有缓存, computed有缓存
- watch可以监听异步, computed无法监听异步数据变化
- watch监听的数据发生变化时,立即触发回调函数; computed只有依赖的数据变化时才会重新调用getter
12. 手写一个函数防抖,并分别解释防抖和节流。
-
防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。
-
节流是指规定一个单位时间内,只能有第一次触发事件的回调函数执行
// 防抖 function fangdou(fn,time){ let timer=null; return function(){ let context=this; let args=arguments; //如果此时定时器已经存在了,则取消之前的定时器重新计时 if(!timer){ clearTimeout(timer); timer=null; } //设置定时器,使事件间隔指定事件后触发 timer=setTimeout(()=>{ fn.apply(context,args); },time); }; } // 节流 function jieliu(fn,time){ let timer=null; return function(){ let context=this; let args=arguments; if(!timer){ timer=setTimeout(()=>{ fn.apply(context,args); timer=null; },time); } } }
13. 箭头函数与普通函数的区别?
- 外形不同:箭头函数使用箭头定义,普通函数中没有。
- 命名不同:箭头函数全是匿名函数,普通函数可以有匿名函数,也可以有具名函数
- 用于构造函数:箭头函数不可用于构造函数,普通函数可以用于构造函数,以此创建对象实例。
- this不同:箭头函数的this指向于它父级的this,在普通函数中,this 总是指向调用它的对象,如果用作构造函数,它指向创建的对象实例。
- arguments 对象:箭头函数没有arguments 对象,每一个普通函数调用后都具有一个
14. js 闭包是什么?有什么优缺点?
- 闭包
- 闭包就是内层函数可以访问外层函数内部数据的函数
- 优点
- 延长了变量的生命周期、缓存数据、避免全局污染
- 缺点
- 增加常驻内存, 容易造成内存泄漏