JS部分
1.数组去重
- 方法1
let newarr = function (arr){
if(!Array.isArray (arr)) return false
let newArr = []
for(let i=0;i<arr.length;i++){
if(newArr.indexOf(arr[i]) == -1){
newArr.push(arr[i])
}
}
return newArr
}
- 方法2
let newArr = function(arr){
if( !Array.isArray(arr)) return false
return Array.from( new Set(arr))
}
2.react实现一个防抖的模糊搜索输入框
防抖:debounce 当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。 节流:throttle 当持续触发事件时,保证一定时间段内只调用一次事件处理函数
- 防抖
function debounce (func,wait){ var timeout = null return function(){ if(timeout){ clearTimeout(timeout) } timeout = setTimeout(func,wait) } } - 节流
定时器方法如下 还可以用时间戳实现
function throttle (func,wait){ var timeout = null return function (){ var context = this; var args = arguments; if(timeout) return timeout = setTimeout(()=>{ func.apply(context.args) timeout = null },wait) } } - 组件实现:
注意:绑定的应该是防抖函数执行的结果,不要用表达式,会重复执行导致起不到防抖效果知道起到延时效果import React ,{Component} from 'react' export default class SearchInput extends Component{ constructor(props,contex){ super(props,contex); this.state={ value:"" } this.newsearch = this.debounce(this.search,1000) } debounce => (func, wait) { let timeout; return function () { let context = this; let args = arguments; if (timeout) clearTimeout(timeout); timeout = setTimeout(() => { func.apply(context, args) }, wait); } } onChange = (e)=>{ this.setState({ value:e.target.value }, this.newsearch) } search =()=>{ console.log(this.state.value) } render (){ return <div> <input value = {this.state.value} onChange={this.onChange} ></input> </div> } }
3.手动封装一个请求函数,可以设置最大请求次数,请求成功则不再请求,请求失败则继续请求直到超过最大次数
import axios from 'axios'
export const maxAxios = (config,num =3)=>{
let {url,method="GET",successCallback,failCallback} = config
axios({
method:method,
url:url,
})
.then(function (response) {
successCallback(response)
})
.catch(err => {
if (num <= 0) return errorCallback(err);
return maxAxios(config, --num);
});
}
4.forEach,map和filter的区别
forEach、map、filter、find、some、every、reduce等Array的Api
在回调函数中都可以修改引用对象类型的item子元素
| Api | 调用格式 | 作用 | 是否修改原数组 | 备注 |
|---|---|---|---|---|
| forEach | arr.forEach( function ( item, index, arr){ “js 语句” }) | 遍历数组全部元素,利用回调函数对数组进行操作 | 不修改 | 且无法break中途跳出循环,不可控。不支持return操作输出,return只用于控制循环是否跳出当前循环 |
| map | arr.map( function ( item, index, arr){ return 值}) | 和 forEach 类似,不过他可以对数组的每一项进行操作,并返回一个新的数组 | 返回一个新的数组,不修改原数组 | 输出的是 return什么就输出什么 新数组 |
| filter | arr.filter( function ( item, index, arr){ return 条件 }) | 按照我们的条件筛选数组,把 |
创建新数组,不修改原数组 | |
| find | arr.find(function(item,index){return item>2&&item< 5;}) | 当遍历循环到判断到一个为true则跳出循环,输出当前数组元素,不再循环 | 不创建新数组,不修改原数组 | 与findIndex相同 |
| some | arr.some( function ( item, index, arr){ return 条件}) | 执行完成返回一个布尔值,当所有返回值return有一个为真(true),返回true,全为假(false)返回假(false) | 不创建新数组,不会改变原数组 | |
| every | arr.some( function ( item, index, arr){ return 条件}) | 遍历一个数组,如果数组的全部元素都为 true ,则返回 true ,否则返回 false | 不创建新数组,不会改变原数组 | |
| reduce | arr.reduce(function(pre,next,index){ return},initialValue) | 叠加,可以利用前遍历操作的结果到下一次遍历使用,重复叠加使用下去 | 创建新数组,不改变原数组 | pre第一次为数组第一项,之后为上一操作的结果,next为当前项,index(next项的序列) |
5.实现一个函数判断数据类型
let getType = (item)=>{
//Number、String、Object(Array、Function)、Boolean、Null、Undefined、Symbol
if(item === null)
return 'null'
if(item === undefined){
return 'undefined'
}
let type1 = typeof item;
if(type1 =='string' || type1 =="number" || type1=="boolean" || type1 == 'symbol'){
return type1
}else{
return 'object'
}
}
原答案
function getType(obj) {
if (obj === null) return String(obj);
if (typeof obj ==='object'){
let toStringfuc = Object.prototype.toString;
return toStringfuc.call(obj).replace('[object ', '').replace(']', '').toLowerCase()
}else{
return typeof obj;
}
}
备注:
Object构造函数本身没有toString方法。依照原型链关系,Object构造函数的上游原型链是Function.prototype。所以,你调用Object.toString.call(param)本质上是调用Function.prototype.toString.call(param),这里需要的参数类型是函数,你传了对象,所以会报错。
6. delete 数组的 item,数组的 length 是否会 -1
不会,会变成稀疏数组
7.给出 ['1', '3', '10'].map(parseInt) 执行结果
- map(function(item,index,arr){})
- parseInt(string, radix),radix介于 2 ~ 36 之间。如果省略该参数或其值为 0,则数字将以 10 为基础来解析。如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN。
- 执行步骤 1.parseInt(1,0),parseInt(2,1),parseInt(10,2)
- 结果
[1,NaN,2]
8.如何进行性能优化?
一、编码优化
- 数据读取
- 通过作用域链 / 原型链 读取变量或方法时,需要更多的耗时,且越长越慢;
- 对象嵌套越深,读取值也越慢;
- 尽量在局部作用域中进行 变量缓存;避免嵌套过深的数据结构,数据扁平化 有利于数据的读取和维护
- 循环
- 代码的性能问题会再循环中被指数倍放大;
- 尽可能 减少循环次数;减少遍历的数据量;完成目的后马上结束循环; 避免在循环中执行大量的运算,避免重复计算,相同的执行结果应该使用缓存; js 中使用 倒序循环 会略微提升性能;尽量避免使用 for-in 循环,因为它会枚举原型对象,耗时大于普通循环;
- 条件流程性能: Map / Object > switch > if-else
- 减少 cookie 体积:能有效减少每次请求的体积和响应时间;
- dom 优化:
- 减少访问 dom 的次数.如需多次,将 dom 缓存于变量中
- 减少重绘与回流:多次操作合并为一次;减少对计算属性(offsetTop, getComputedStyle)的访问(会重排)
- 使用事件委托,避免大量的事件绑定;
- css 优化:
- 层级扁平,避免过于多层级的选择器嵌套;
- 特定的选择器 好过一层一层查找: .xxx-child-text{} 优于 .xxx .child .text{}
- 减少使用通配符与属性选择器;
- 减少不必要的多余属性;
- 使用 动画属性 实现动画,动画时脱离文档流,开启硬件加速,优先使用 css 动画;
- 使用 替代原生 @import;
- Html优化
- 减少dom数量,避免不必要的节点或者嵌套
- 避免空标签(
),减少不必要的请求
- 图片提前指定宽高或者脱离文档流,减少因为图片加载引起的页面回流
- 语意化标签,有利于SEO和浏览器解析
- 减少table布局
二、 页面基础优化
- 引入位置 css在引入,js在引入-》影响首屏效果
- 减少请求 http 1.0 -1.1
- 减少文件体积
- 删除多余代码:tree-shaking UglifyJs code-spliting
- 混淆压缩代码 开启gzip
- 动态 polyfill,只针对不支持的浏览器引入 polyfill;
- 图片优化-》合适质量,合适尺寸、合适格式、小图片合成 雪碧图,低于 5K 的图片可以转换成 base64 内嵌、合适场景下,使用 iconfont 或者 svg
- 使用缓存
- 浏览器缓存
- CDN缓存
- 服务器缓存
- 数据缓存
三、 首屏渲染优化
- css/js分割,减少首屏依赖文件大小
- 非关键文件异步加载或懒加载
- 使用
dns-prefetch/preconnect/prefetch/preload等 - 谨慎选择web字体
- 合理使用Localstorage/server-worker等缓存
- 服务端渲染
- 利用骨架屏或者loading
9.函数式编程
- 编程范式: 面向过程、事件驱动(发布-订阅)、面对对象、函数式
- 编程理念
-
纯函数
let getPurefunc = (func)=>{ if(func不修改传入参数 && func不依赖、不修改任何函数外部的数据 && func完全可控,参数一样,返回值一定一样 && func引用透明){ return true } }引用透明:函数的运算结果替换函数表达式不会改变函数的含义
优势:完全独立,与外部解耦;高度可复用,在任意上下文、时间线上有固定的结果;可测试性强。
纯函数是函数式编程的基础,可以使程序变得灵活,高度可拓展,可维护;
-
函数复合
-
- 扁平化嵌套 f(g(k(x))) => xxx(f,g,k)(x)
const pipe = (...fs) => x => fs.reduce((v, f) => f(v), x) -
- 结果传递
-
好处
-
- 隐藏中间参数,不需要临时变量
-
- 只需要关注每个纯函数即可
-
- 可复用性强
-
- 可拓展性强,维护成本低
-
数据不可变性:函数式编程的核心理念之一
-
- 不修改原对象,需要改变时返回一个全新的对象
-
- 目的:保持数据的稳定性
-
避免不同函数之间的 状态共享,数据的传递使用复制或全新对象,遵守数据不可变原则;
-
避免函数内部修改外部状态
-
避免在单元函数内部执行一些副作用如:日志输出、读写文件、网络请求
- 高阶函数:以函数作为参数,返回一个新的增强的函数的一类函数
- 优点
- 使用纯函数,副作用小,没有耦合,复用性高
- 能专注于数据流动和处理,提供稳定性和健壮性
- 可维护可扩展性好
- 单元测试方便
- 总结
- 提倡 纯函数 函数复合 数据不可变
- 追求更细的粒度 拆分成一组极小的单元函数
10.执行上下文
当代码运行时,会产生一个对应的执行环境,会变量提升,代码从上往下开始执行,就叫执行上下文 运行环境分三种:全局环境、函数环境、eval函数 包含三部分:变量对象(存储着该执行上下文中的所有 变量和函数声明)、作用域链、this指向
作用域其实可理解为该上下文中声明的 变量和声明的作用范围。可分为 块级作用域 和 函数作用域 特点:
- 单线程,在主线程上运行
- 同步执行,从上往下顺序执行
- 全局上下文只有一个,浏览器关闭时会弹出栈
- 函数每调用一次,都会产出一个新的函数执行上下文,当函数调用完成时,这个上下文环境以及其中数据都会被消除
11. 100*100的 canvas 占多少内存?
没有思路 参考思路: 100*100个像素点 每个像素点用RGBA四位表示。。。
12.实现数组的filter方法
filter 用法:arr.filter(function(item,index,arr){})
Array.prototype.myfilter = function(func) {
let arr = this;
let resultarr =[]
for(let i=0;i<arr.length;i++){
let item = arr[i];
let result = func.call(this,item,i,arr)
if(result){
resultarr.push(arr[i])
}
}
return resultarr
}
正确答案
Array.prototype.filter = function(fn,context){
if(typeof fn != 'function'){
throw new TypeError(`${fn} is not a function`)
}
let arr = this;
let reuslt = []
for(var i = 0;i < arr.length; i++){
let temp= fn.call(context,arr[i],i,arr);
if(temp){
result.push(arr[i]);
}
}
return result
}
filter完整用法 array.filter(function(currentValue,index,arr), context)
13.实现数组的flat方法
var newArray = arr.flat([depth]); 手写实现
Array.prototype.myflat = function(deepth =1 ){
let arr = this;
let resultarr =[]
resultarr = arr.reduce((pre,next)=>{
return pre.concat(next)
},[])
return deepth>1? resultarr.myflat(--deepth):resultarr
}
14.数组乱序
思考:
- 排序 大小随机。
arr.sort(()=>{Math.random()-0.5})
- 洗牌算法:
// 著名的Fisher–Yates shuffle 洗牌算法.每次随机取一个放到最尾处
function shuffle(arr){
let m = arr.length;
while(m > 1){
let index = parseInt(Math.random() * m--);
[arr[index],arr[m]] = [arr[m],arr[index]];
}
return arr;
}
15. 实现一个模版引擎
let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {
name: '姓名',
age: 18
}
render(template, data); // 我是姓名,年龄18,性别undefined
function render(template, data) {
return template.replace(new RegExp('{{(.*?)}}', 'g'), (match, key) => data[key.trim()]);
}
16. 实现一个最基本的数据绑定
题意:
var obj = {
key_1: 1,
key_2: 2
}
function func(key) {
console.log(key + ' 的值发生改变:' + this[key]);
}
//实现一个方法,可以给 obj 所有的属性添加动态绑定事件,当属性值发生变化时会触发事
testfunc(obj,func)
obj.key_1 = 2; // 此时自动输出 "key_1 的值发生改变:2"
obj.key_2 = 1; // 此时自动输出 "key_2 的值发生改变:1"
通过使用Object.defineProperty(obj,key,desc)直接在一个对象上定义一个新属性,或者修改一个已经存在的属性。 let testfunc = (obj,func)=>{ for(let k in obj){ Object.defineProperty(obj,k,{ set:function(newValue){ if(this.value !=newValue){ this.value = newValue func.call(obj,k) } }, get:function(){ return this.value } }) } }
17.转化为驼峰命名 【get-element-by-id】
let f=(str)=>{
//正则替换
let s = str.replace(/-\w/g,(x)=>{
return x.slice(1).toUpperCase()
})
return s
}
//驼峰改回-
let f=(str)=>{
let s = str.replace(/[A-Z]/g,(x)=>{
return '-'+x.toLowerCase()
})
return s
}
//笨方法
let func = (str)=>{
let s = str.split('-')
let result=''
for(let i = 0;i<s.length;i++){
let item =s[i]
if(i!=0){
item =item.replace(item[0], item[0].toUpperCase())
}
result += item
}
return result
}
18. 解析 URL Params 为对象
let getParams= (url) =>{
let s =url.indexOf('?')==-1? url: url.split('?')[1];
let arr = s.split('&');
let obj={}
for(let i=0;i<arr.length;i++){
let item = arr[i];
obj[item.split('=')[0]] = item.split('=')[1]
}
return obj
}
19.vue 组件之间的传值通信
三种传值类型:父传子、子传父、兄弟组件之间的通讯 父组件可以使用props向子组件传递数据。子组件通过$emit触发事件,回调给父组件。中央事件总线Bus
20.Proxy 相比于 defineProperty 的优势
Object.defineProperty() 的问题主要有三个:
不能监听数组的变化 必须遍历对象的每个属性 必须深层遍历嵌套的对象
Proxy 在 ES2015 规范中被正式加入,它有以下几个特点:
针对对象:针对整个对象,而不是对象的某个属性,所以也就不需要对 keys 进行遍历。这解决了上述 Object.defineProperty() 第二个问题 支持数组:Proxy 不需要对数组的方法进行重载,省去了众多 hack,减少代码量等于减少了维护成本,而且标准的就是最好的。
除了上述两点之外,Proxy 还拥有以下优势:
Proxy 的第二个参数可以有 13 种拦截方法,这比起 Object.defineProperty() 要更加丰富 Proxy 作为新标准受到浏览器厂商的重点关注和性能优化,相比之下 Object.defineProperty() 是一个已有的老方法。