前端基础知识学习(1)

251 阅读10分钟

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 当持续触发事件时,保证一定时间段内只调用一次事件处理函数

  1. 防抖
    function debounce (func,wait){
        var timeout = null
        return function(){
            if(timeout){
                clearTimeout(timeout)
            }
            timeout = setTimeout(func,wait)
        }
    }
    
  2. 节流 定时器方法如下 还可以用时间戳实现
    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)
        }
    }
    
  3. 组件实现:
    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 条件 }) 按照我们的条件筛选数组,把\color{red}{原数组中满足条件的筛选出来},并返回一个新的数组 创建新数组,不修改原数组 \color{red}{会根据下标从原数组中取元素}
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;
     }
 }

备注:\color{red}{Object.prototype.toString与Object.toString区别}

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.如何进行性能优化?

一、编码优化

  1. 数据读取
  • 通过作用域链 / 原型链 读取变量或方法时,需要更多的耗时,且越长越慢;
  • 对象嵌套越深,读取值也越慢;
  • 尽量在局部作用域中进行 变量缓存;避免嵌套过深的数据结构,数据扁平化 有利于数据的读取和维护
  1. 循环
  • 代码的性能问题会再循环中被指数倍放大;
  • 尽可能 减少循环次数;减少遍历的数据量;完成目的后马上结束循环; 避免在循环中执行大量的运算,避免重复计算,相同的执行结果应该使用缓存; js 中使用 倒序循环 会略微提升性能;尽量避免使用 for-in 循环,因为它会枚举原型对象,耗时大于普通循环;
  1. 条件流程性能: Map / Object > switch > if-else
  2. 减少 cookie 体积:能有效减少每次请求的体积和响应时间;
  3. dom 优化:
  • 减少访问 dom 的次数.如需多次,将 dom 缓存于变量中
  • 减少重绘与回流:多次操作合并为一次;减少对计算属性(offsetTop, getComputedStyle)的访问(会重排)
  • 使用事件委托,避免大量的事件绑定;
  1. css 优化:
  • 层级扁平,避免过于多层级的选择器嵌套;
  • 特定的选择器 好过一层一层查找: .xxx-child-text{} 优于 .xxx .child .text{}
  • 减少使用通配符与属性选择器;
  • 减少不必要的多余属性;
  • 使用 动画属性 实现动画,动画时脱离文档流,开启硬件加速,优先使用 css 动画;
  • 使用 替代原生 @import;
  1. Html优化
  • 减少dom数量,避免不必要的节点或者嵌套
  • 避免空标签(),减少不必要的请求
  • 图片提前指定宽高或者脱离文档流,减少因为图片加载引起的页面回流
  • 语意化标签,有利于SEO和浏览器解析
  • 减少table布局

二、 页面基础优化

  1. 引入位置 css在引入,js在引入-》影响首屏效果
  2. 减少请求 http 1.0 -1.1
  3. 减少文件体积
  • 删除多余代码:tree-shaking UglifyJs code-spliting
  • 混淆压缩代码 开启gzip
  • 动态 polyfill,只针对不支持的浏览器引入 polyfill;
  1. 图片优化-》合适质量,合适尺寸、合适格式、小图片合成 雪碧图,低于 5K 的图片可以转换成 base64 内嵌、合适场景下,使用 iconfont 或者 svg
  2. 使用缓存
  • 浏览器缓存
  • CDN缓存
  • 服务器缓存
  • 数据缓存

三、 首屏渲染优化

  1. css/js分割,减少首屏依赖文件大小
  2. 非关键文件异步加载或懒加载
  3. 使用dns-prefetch/preconnect/prefetch/preload
  4. 谨慎选择web字体
  5. 合理使用Localstorage/server-worker等缓存
  6. 服务端渲染
  7. 利用骨架屏或者loading

9.函数式编程

  1. 编程范式: 面向过程、事件驱动(发布-订阅)、面对对象、函数式
  2. 编程理念
  • 纯函数

    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)
    
    • 结果传递
  • 好处

    • 隐藏中间参数,不需要临时变量
    • 只需要关注每个纯函数即可
    • 可复用性强
    • 可拓展性强,维护成本低
  • 数据不可变性:函数式编程的核心理念之一

    • 不修改原对象,需要改变时返回一个全新的对象
    • 目的:保持数据的稳定性
  • 避免不同函数之间的 状态共享,数据的传递使用复制或全新对象,遵守数据不可变原则;

  • 避免函数内部修改外部状态

  • 避免在单元函数内部执行一些副作用如:日志输出、读写文件、网络请求

  1. 高阶函数:以函数作为参数,返回一个新的增强的函数的一类函数
  2. 优点
  • 使用纯函数,副作用小,没有耦合,复用性高
  • 能专注于数据流动和处理,提供稳定性和健壮性
  • 可维护可扩展性好
  • 单元测试方便
  1. 总结
  • 提倡 纯函数 函数复合 数据不可变
  • 追求更细的粒度 拆分成一组极小的单元函数

10.执行上下文

当代码运行时,会产生一个对应的执行环境,会变量提升,代码从上往下开始执行,就叫执行上下文 运行环境分三种:全局环境、函数环境、eval函数 包含三部分:变量对象(存储着该执行上下文中的所有 变量和函数声明)、作用域链、this指向

作用域其实可理解为该上下文中声明的 变量和声明的作用范围。可分为 块级作用域 和 函数作用域 特点:

  1. 单线程,在主线程上运行
  2. 同步执行,从上往下顺序执行
  3. 全局上下文只有一个,浏览器关闭时会弹出栈
  4. 函数每调用一次,都会产出一个新的函数执行上下文,当函数调用完成时,这个上下文环境以及其中数据都会被消除

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.数组乱序

思考:

  1. 排序 大小随机。
 arr.sort(()=>{Math.random()-0.5})
  1. 洗牌算法:
// 著名的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() 是一个已有的老方法。