前端工程师自检清单(三)

四、数据结构和算法

JavaScript编码能力

  • 多种方式实现数组去重、扁平化、对比优缺点

数组去重

1.遍历数组:新建一个数组,遍历需去重的数组,当值不再新数组时indexOf === -1 就加入新数组

2.排序后相邻去除法:给传入的数组排序,排序后相同的值会相邻,然后遍历排序后数组时,新数组只加入不与前一值重复的值。

3.优化遍历数组法:双层循环

4.ES6中的Set数据结构

数组扁平化

1.ES6中的flat(),可带参数,表示拉平层数,默认1,未知Infinity

2.循环数组+递归调用

3.apply+some:

function steamroller2(arr){
  while(arr.some(item=> Array.isArray(item))){
    arr=[].concat.apply([],arr)
  }
  return arr
}
console.log(steamroller2(arr))

 4.reduce方法

function steamroller3(arr){
  return arr.reduce((prev,next)=>{
    return prev.concat(Array.isArray(next)?steamroller3(next):next)
  },[])
}
console.log(steamroller3(arr))

5.es6展开运算符 :

function steamroller4(arr){
  while(arr.some(item=> Array.isArray(item))){
    arr=[].concat(...arr)
  }
  return arr
}

  • 多种方式实现深拷贝、对比优缺点

1.递归实现:

function deep(obj) {
  //判断拷贝的要进行深拷贝的是数组还是对象,是数组的话进行数组拷贝,对象的话进行对象拷贝
  var objClone = Array.isArray(obj) ? [] : {};
  //进行深拷贝的不能为空,并且是对象或者是
  if (obj && typeof obj === "object") {
    for (key in obj) {
      if (obj.hasOwnProperty(key)) {
        if (obj[key] && typeof obj[key] === "object") {
          objClone[key] = deep(obj[key]);
        } else {
          objClone[key] = obj[key];
        }
      }
    }
  }
  return objClone;
}

2. 通过JSON对象实现:无法实现对对象中方法的深拷贝

//通过js的内置对象JSON来进行数组对象的深拷贝
function deepClone2(obj) {
  var _obj = JSON.stringify(obj),
    objClone = JSON.parse(_obj);
  return objClone;
}

3.lodash函数库实现:  lodash.cloneDeep()

4.Jquery的extend()方法

注意:Object.assign(),slice(),concat()方法进行拷贝只能实现一级属性的拷贝成功,二级以下仍脱离不了,算不上真正的深拷贝


  • 手写函数柯里化工具,并理解其应用场景和优势

柯里化,是把接受多个参数的函数变换成接受一个单一参数的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

优势:1.参数复用   2.提前确认  3.延迟运行

通用封装方法:

// 初步封装
var currying = function(fn) {
    // args获取第一个方法内的全部参数
    var args = Array.prototype.slice.call(arguments, 1);
    return function() {
        // 将后面方法里的全部参数和args进行合并
        var newArgs = args.concat(Array.prototype.slice.call(arguments));
        // 把合并后的参数通过apply作为fn的参数并执行
        return fn.apply(this, newArgs);
    }
}



// 支持多参数传递
function progressCurrying(fn, args) {

    var _this = this
    var len = fn.length;
    var args = args || [];

    return function() {
        var _args = Array.prototype.slice.call(arguments);
        Array.prototype.push.apply(args, _args);

        // 如果参数个数小于最初的fn.length,则递归调用,继续收集参数
        if (_args.length < len) {
            return progressCurrying.call(_this, fn, _args);
        }

        // 参数收集完毕,则执行fn
        return fn.apply(this, _args);
    }
}

 详解JS函数柯里化


  • 手写防抖和节流工具函数,并理解其内部原理和应用场景

防抖:触发事件后再n秒内函数只执行一次,如果再n秒内又触发了事件,则重新计算函数执行时间。

应用场景:
1. 给按钮加防抖防止表单多次提交;
2. 输入栏连续输入进行AJAX验证,防抖减少请求次数;
3. 判断scroll是否滑到底部;

适合多次事件一次响应的情况。

/**
 * @desc 函数防抖
 * @param func 函数
 * @param wait 延迟执行毫秒数
 */
function debounce(func,wait) {
    let timeout;
    return function () {
        let context = this;
        let args = arguments;

        if (timeout) clearTimeout(timeout);

        let callNow = !timeout;
        timeout = setTimeout(() => {
            timeout = null;
        }, wait)

        if (callNow) func.apply(context, args)
    }
}

节流:指连续触发事件但是再n秒内只执行一次函数。

适用场景:游戏中的刷新率;DOM元素拖拽,Cancas画笔功能

总体来说,适合大量事件按时间做平均分配触发

function throttle(fn, gapTime) {
  let _lastTime = null;
  return function () {
    let _nowTime = + new Date()
    if (_nowTime - _lastTime > gapTime || !_lastTime) {
      fn();
      _lastTime = _nowTime
    }
  }
}

  • 实现一个sleep函数

sleep函数:让线程休眠等到值定时间再重新唤起

//        第一种实现方法
        function sleep(numberMillis) {
            var now = new Date();
            var exitTime = now.getTime() + numberMillis;
            while (true) {
                now = new Date();
                if (now.getTime() > exitTime){
                    break;
               }
            }
        }
//        第二种实现方法
        function sleep(numberMillis) {
            var start = new Date().getTime();
            while (true) {
                if (new Date().getTime() - start > numberMillis) {
                    break;
                }
            }
        }

//   方法三
function sleep(ms, callback) {
    setTimeout(callback, ms);
}

//   方法四
function sleep(ms) {
    return new Promise(
        function(resolve, reject){
            setTimeout(resolve, ms);
        } 
    )
}

手动实现前端轮子

  • 手动实现call、apply、bind

实现call

将目标函数的this指向传入的第一个对象,参数不定长,且立即执行

function.prototype.mycall = function(obj){
    var args = Array.prototype.slice.apply(arguments, [1]);
    obj.fn = this;      // 在obj上添加fn属性,值是this
    obj.fn(...args);       // 在obj上调用函数,那函数的this值就是obj
    delete obj.fn;         //  伤处obj的fn属性,去除影响
}

 使用eval方法,回对传入的字符串,当作JS代码进行解析执行

function.prototype.mycall = function(obj){
        obj = obj||window;
         var args = [];
        for(var i = 1 ; i < arguments.length; i++) {
                  args.push('arguments[' + i + ']');
                    // 不能直接push值,因为会导致参数为数组时eval调用回将其转换成字符串
         }
 
         obj.fn = this;
         eval('obj.fn('+args+'));
         delete  obj.fn;
}

实现apply

apply和call区别只有一个,apply第二个参数为数组

function.prototype.myApply = function(obj, arr) {
    obj.fn = this;
    if (!arr) {
        obj.fn();
    }else{
        var args = []
        for(let i = 0;i < arr.length;i++) {
            args.push('arr[' + i + ']')
        }
        eval('obj.fn('+args+')');
    }
    delete obj.fn;
}

实现bind

返回一个被调用函数具有相同函数体的新函数,且之歌新函数也能使用new操作符。

Function.prototype.myFind = function(obj){
    if(obj === null || obj === undefined){
        obj = window;
    } else {
        obj = Object(obj);
    }
    let _this = this;
    let argArr = [];
    let arg1 = [];
    for(let i = 1 ; i<arguments.length ; i++){  
        arg1.push( arguments[i] );
        argArr.push( 'arg1[' + (i - 1)  + ']' ) ;
    }
    // 下面可用apply
    return function(){
        let val ;
        for(let i = 0 ; i<arguments.length ; i++){
            argArr.push( 'arguments[' + i + ']' ) ;
        }
        obj._fn_ = _this;
        console.log(argArr);
        val = eval( 'obj._fn_(' + argArr + ')' ) ;
        delete obj._fn_;
        return val
    };
}

  • 手动实现符合Promise/A+规范的Promise、手动实现async await

Promise内部维护着三种状态,即pending,resolved和rejected。初始状态是pending,状态可以有pending--->relolved,或者pending--->rejected.不能从resolve转换为rejected 或者从rejected转换成resolved.
即 只要Promise由pending状态转换为其他状态后,状态就不可变更。

Promise/A+ 规范的实现


  • 手写一个EventEmitter实现事件发布、订阅

基于发布订阅模式写一个eventEmitter


  • 可以手动实现两种实现双向绑定的方案

JavaScript实现双向绑定的三种方式


  • 手写JSON.stringify、JSON.parse

原生js实现JSON.parse()和JSON.stringify() 

JSON.parse 三种实现方式


  • 手写一个模板引擎,并能解释其中原理

 正则匹配并替换字符串中 {{}} 中的值

function template(str, data) {
  var reg = /{{([a-zA-Z0-9_$][a-zA-Z0-9\.]+)}}/g;  
  return str.replace(reg, function(raw, key, offset, string) {
    var paths = data,
        ary = key.split('.');

    while(ary.length > 0) {
      paths = paths[ary.shift()];
    }

    return paths || raw;   
  });
}

 手写JavaScript模板引擎


  • 手写懒加载、下拉刷新、上拉加载、预加载等效果

懒加载的目的是作为服务器前端的优化,减少请求数或延迟请求数

实现方式:
1. 纯粹的延迟加载,使用setTimeOut进行加载延迟
2. 条件加载,符合条件或触发事件再开始异步加载
3. 可视区加载,监控滚动条来实现

        var num = document.getElementsByTagName('img').length;
        var img = document.getElementsByTagName("img");
        // 存储图片加载到的位置,避免每次都从第一张图片开始遍历
        var n = 0;
        // 页面载入完毕加载可视区域内的图片
        lazyLoad();                                
        window.onscroll = lazyLoad;
        // 监听页面滚动事件
        function lazyLoad() {
            // 可见区域高度
            var seeHeight = document.documentElement.clientHeight;
            // 滚动条距离顶部高度
            var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            for (var i = n; i < num; i++) {
                // 图片距离上方的距离小于可视区高度加滚动条距上方高度
                if (img[i].offsetTop < seeHeight + scrollTop) {
                    if (img[i].getAttribute("src") == "default.jpg") {
                        img[i].src = img[i].getAttribute("data-src");
                    }
                    n = i + 1;
                }
            }
        }

预加载是牺牲服务器前端性能,换取更好的用户体验

实现方式:
1.用CSS和JS实现预加载
2.仅使用JS实现预加载
3.使用Ajax实现预加载

function preLoadImg(url, callback) { 
  var img = new Image(); //创建一个Image对象,实现图片的预下载 
  img.src = url; 
if (img.complete) { // 如果图片已经存在于浏览器缓存,直接调用回调函数 
  callback.call(img); 
  return; // 直接返回,不用再处理onload事件 
} 
  img.onload = function () { //图片下载完毕时异步调用callback函数。 
    callback.call(img);//将回调函数的this替换为Image对象 
  }; 
}; 

 原生js实现简单的下拉刷新功能


数据结构

  • 理解常见数据结构的特点,以及他们在不同场景下使用的优缺点

数据结构是以某种形式将数据组织在一起的集合,它不仅存储数据,还支持访问和处理数据的操作。

常见的数据结构:

1.线性表(数组):
数组是基础,为数组封装好一个List构造函数,增加长度、插入、删除、索引等工具结构。
数组的索引下标需要在js语言内部转换为js对象的属性名,因此效率打了折扣

2.栈:具有后进先出的特点,是一种高效的列表,只对栈顶的数据进行添加和删除

3.队列:具有先进先出的特点,是只能在队首取出或删除元素,在队尾插入元素的列表。

4.链表:链表是由一组节点组成的集合。

5.字典及散列(object):散列也叫做散列表,在散列表上插入、删除和取用数据非常快,但对查找操作来说效率低下。

6.集合(set):是一种包含不同元素的数据结构。特点是无序且各不相同。

7.树:树是非线性,分层存储的数据结构,可用来存储文件系统或有序列表。

JavaScript常用数据结构


  • 理解数组、字符串的存储原理,并熟练应用他们解决问题

数组是一个连续的内存分配,但在JS中不是连续分配的,类似哈希映射的方式存在的。

深究 JavaScript 数组 —— 演进&性能

字符串是存储在中的


  • 理解二叉树、栈、队列、哈希表的基本结构和特点

二叉树(Binary Tree)是一种树形结构,它的特点是每个节点最多只有两个分支节点,一棵二叉树通常由根节点,分支节点,叶子节点组成。而每个分支节点也常常被称作为一棵子树。js数据结构-二叉树(二叉搜索树)

哈希表也称为散列表。是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。


  • 了解图、堆的基本结构和使用场景

图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V(vertex)是图G中顶点的集合,E(edge)是图G中边的集合。

数据结构之图的基本概念


算法

  • 可计算一个算法的时间复杂度和空间复杂度,可估计业务逻辑代码的耗时和内存消耗

算法是指用来操作数据、解决程序问题的一组方法。

算法的时间复杂度和空间复杂度-总结


  • 至少理解五种排序算法的实现原理、应用场景、优缺点,可快速说出时间、空间复杂度

 排序图解:js排序算法实现


  • 了解递归和循环的优缺点、应用场景、并可在开发中熟练应用

递归

函数内部调用这个函数本身。代码简洁,但时间空间消耗大。会有重复计算,栈溢出的问题。

循环

通过设置初始值和终止条件,在一个范围内重复运算。代码可读性差,但效率高。


  • 可应用回溯算法、贪心算法、分治算法、动态规划等解决复杂问题

回溯算法:是一种选优搜索法,又称试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法。而满足回溯条件的某个状态的点称为“回溯点”。

贪心算法:对问题进行求解时,在每一步选择中采取最好或最优的选择,从而希望能够导致结果是最好或最优的算法。

分治算法:就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。

动态规划:可以把原始问题分解为相关联的子解问题,并通过求取和保存子问题的解,获得原问题的解。找到问题的状态描述和状态转移方程。


  • 前端处理海量数据的算法方案

前端如何处理十万级别的大量数据


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MCU(Microcontroller Unit,微控制器单元)的自检是通过系统自检功能来实现的。系统自检功能主要是通过程序设计,对MCU的各个部分进行检测,以确保MCU的正常工作状态。 首先,系统自检功能会对MCU的外部连接进行检测,包括电源连接、时钟信号连接、外设接口的正常连接等。检测外部连接的目的是确保MCU能够正常接收外部信号,并且外设能够正确地与MCU进行通信。 其次,系统自检功能会对MCU内部的各个功能模块进行检测,包括存储器、计算单元、定时器、串口等。通过检测各个功能模块的工作状态,系统自检功能可以确定是否存在硬件故障或者连接错误。 系统自检功能还会对MCU的时钟频率进行测试,以确保MCU的时钟频率与设计要求一致。时钟频率对MCU的工作速度至关重要,因此准确的时钟频率是保证MCU正常工作的前提条件。 最后,系统自检功能还会检测MCU的供电电压是否正常,以确保MCU能够在稳定的电压下正常运行。若供电电压存在波动或者不稳定的情况,系统自检功能会发出警报或者自动切换到备用电源,保证MCU的正常运行。 综上所述,系统自检功能通过程序设计和内部检测来对MCU进行自检,以确保其正常工作状态。通过外部连接、内部功能模块、时钟频率和供电电压等方面的检测,系统自检功能能够快速发现并排除一些常见的硬件故障,保证MCU的稳定运行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值