sprina

sprina 查看完整档案

北京编辑西安交通大学  |  软件工程 编辑快手  |  前端 编辑 segmentfault.com/u/sprina1997 编辑
编辑

力求用最简单的思想去理解清楚复杂概念

个人动态

sprina 回答了问题 · 1月20日

解决VUE应在那个生命周期获取后台数据?

如果在mounted中请求数据就会出现页面显示了元素但是里面是空的,页面大部分空白的情况(因为没有拿到数据),在created里面写的话呈现出来的效果视觉上比较友好一些

关注 4 回答 5

sprina 回答了问题 · 1月19日

js 往数组中遍历三个元素

你这个需求,根据相同的键(id)合并两个对象,适合直接用Object.assign()这个内置方法,具体用法可以看这里https://developer.mozilla.org...

  let arr = [
        {id:0,title:'T1'},
        {id:1,title:'T2'},
        {id:2,title:'T3'},
        {id:3,title:'T4'},
        {id:4,title:'T5'}
    ]
    let img = [
        {id:0,pic:'/01.png'},
        {id:1,pic:'/02.png'},
        {id:3,pic:'/03.png'},
        {id:4,pic:'/03.png'},
        {id:5,pic:'/03.png'}
    ]
    for(let i=0; i<arr.length; i++) {
        Object.assign(arr[i],img[i])
    }
    console.log(arr);

输出结果:
image.png

关注 4 回答 4

sprina 回答了问题 · 1月19日

解决Vue/js 先判断一个数组/对象有没有这个字段再判断有没有值

像你那样直接写如果不含该字段会报undefined的错误。要用object的hasOwnProperty()方法判断是否含有该字段。

v-if="data.hasOwnProperty('icon')&&!data.icon"    // 有icon字段但该字段值为null

关注 4 回答 4

sprina 发布了文章 · 1月15日

Vue 手写组件之实现一个可显示图片的自定义远程搜索框

背景

在实际的vue项目开发中,一般与element-ui搭配使用的比较多,往往能满足大部分的需求。
但是有时某些需求element-ui并不能满足,例如现在我们有这样一个场景:远程搜索框的下拉列表项不仅仅能显示文字内容,还要将搜索到的内容的图片显示出来

首先,我们来看一下官网上的这个组件的功能(el-autocomplete),如下图,这个是element-ui官网为我们提供的远程搜索组件,它能够在我们输入时,根据输入内容实时从远程服务器获取内容并显示在下拉框中。
element-ui提供的远程搜索组件

Element-ui列表中对应的内容显然 是搜索到的内容的value字段,是一个字符串,是无法满足我们放入图片的需求的。

image.png

这时就需要我们自己去封装一个自定义的能显示图片的远程搜索框了。

实现

原理其实很简单,主要有这么几点:

  1. 监听文本框的输入事件,并且为其添加防抖(性能上的优化,使得停止输入一段时间后再去调用接口获取内容)
  2. 使用v-for列表渲染获取到的内容项,放在输入框下方
  3. 为列表设置最大高度(否则高度会根据搜索到的内容个数无限向下延长),超过这个高度加滚动条(max-height和overflow: auto)
  4. 点击其他区域或者选中某一项时列表框隐藏
  5. 其他样式:为每项加边框,与相邻项隔离; 列表框的下方添加阴影效果; 每项鼠标移动进入时颜色置灰,鼠标移出时恢复

最终实现效果如图:
远程搜索框实现效果
代码如下:

// 封了一个从服务器远程获取搜索内容的输入框,比autocompleteuole多了在列表中显示图片的功能
<template>
  <div class="container">
    <!-- 输入框 -->
    <el-input
      placeholder="输入单词或ID"
      v-model="obj[name]"
      @input="onInput"
      @blur="isShow=false"
      clearable
      style="width: 200px"
    >
    </el-input>
    <!-- 下拉列表框 -->
    <div class="selectLIst" v-if="isShow==true">
      <div v-for="item in resultList" :key="item.id" @click="handleSelect(item)" class="item">
        <span>{{item.value}}</span>  // 文字内容
        <img :data-original="item.picUrl" style="width: 20px; height: 20px; margin-left: 5px">   // 图片
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'RemoteSearch',
  props: {
    obj: {
      type: Object
    },
    name: {
      type: String
    },
  },
  data() {
    return {
      timer: null,   // 这里放外面其实不安全,容易被篡改
      resultList: [],   // 最终要渲染的内容数组
      isShow: false    // 控制下拉列表的显示与隐藏
    }
  },
  methods: {
    onInput() {
      this.isShow = true
      // 防抖
      if(this.timer) {
        clearTimeout(this.timer)
      }
      this.timer = setTimeout(()=>{
        this.fetchContent()
      },800)
    },
    async fetchContent() {
      this.resultList = []
      let queryObj = {type:1}
      // 判断 queryString是数字还是单词
      if(!isNaN(Number(this.obj[this.name]))) {
        queryObj.id = this.obj[this.name]
      } else {
        queryObj.word = this.obj[this.name]
      }
      // 这里写你要请求的地址
      const result = await this.$ajax.post(
        `/admin/incentive/queryRewardDetail`,queryObj
      )
      if (result.data.data) {
        const res = result.data.data
        res.forEach(e => {     // 这里从返回的结果中选取需要用到的的内容放入resultList
          this.resultList.push({value: e.id+' '+e.word, picUrl: e.wordPic, id: e.id})
        });
      } else {
        this.$message.warning("请求出错!");
      }
    },
    handleSelect(item) {
      this.obj[this.name] = item.id+''   // 选中后给对应字段赋值
      this.isShow = false   // 选中某一项后隐藏下拉列表框
    }
  }
}
</script>

<style lang="scss" scoped>
.container {
  position: absolute;
  .selectLIst {
    position: relative;
    z-index: 10;
    background-color: #FFFFFF;
    box-shadow: 0 4px 3px #C5C5C5;
    max-height: 200px;
    overflow: auto;
    .item {
      height: 30px;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    .item:hover {
      background-color: #EFEFEF;
    }
  }
  ul {
    list-style: none   // 隐藏无序列表前面的小圆点
  }
}
</style>

直接在需要的页面引入并使用:

// item: { word: '' }
// 假如你要将input框与item的word属性绑定,则
<RemoteSearch :obj="item" name='word'/>
查看原文

赞 8 收藏 4 评论 0

sprina 收藏了文章 · 1月15日

2021 年前端趋势预测

简介: 2021 哪些前端技术值得关注?来听听狼叔的分享。

知乎上,有人提问《2021前端会有什么新的变化?》

狼叔的回答二天超过6.1万+阅读量,目前444个赞同,2个专业徽章,整体上看,这篇回答大家还是相当认可的。

诚如很多大V所讲,确实是2020年,前端圈带来具有突破意义的内容或框架不多,很多人也不会再有2013年到2017间日日新的框架大战局面,也不会有Node全栈之争,也不会因为React-Native、Weex、Flutter这类跨端而欣喜若狂。

我能看到的是今天前端已趋于稳定,在深水区探索,比如蚂蚁金服的x6,在图形可视化方面做的就是非常好,比如淘宝的midway-faas,在Serverless领域确实有它独特的定位。比如语雀,钉钉文档,在线Excel等等,也都不是可以轻松可以搞定的。

我很开心的看到,混乱之后,大家都能在深水区里进行探索。2019年阿里经济体前端委员会四大技术方向:第一搭建服务,第二是 Serverless,第三是智能化,第四是 IDE。2020年阿里经济体前端委员的突破方向是互动技术、跨端技术、智能化。而中后台、数据可视化、Node.js(Serverless)、工程体系(安全生产)都变成了基础技术方向。这大概是能够代表前端技术走向的。

我个人也走过类似的路,2017年加入阿里,将PHP替换为Node.js,随后搞了开源项目egg-react-ssr,然后在2019年加入前端委员会,负责Serverless-side render方向。在2020年,转岗到淘系前端,负责前端智能化相关开发。我其实是非常看好Serverless的,Serverless这种稳步推进的必然是前端新基建,未来玩2到5年问题不大。对我而言,前端智能化的诱惑更大,能够站到产研链路是思考问题,这才是我梦寐以求的机会。

我之前的想法是搞一次Node Party讲讲这些2021年前端趋势预测。在线直播,不知道是否有人感兴趣。先把我的这些思考写出来,希望能够对大家判断2021年前端趋势有所帮助。

http import 会大行其道

其实就是Deno创造的方式,Deno被评为2020最佳开源贡献也是实至名归的。

import cheerio from "https://dev.jspm.io/npm:cheerio/index.js";

把cjs转esm都交给CDN类的服务来做更合适。事实上,pika.dev/skypack.dev/http://jspm.io 都已经做了这件事儿。

Node.js马上跟上,相关PR早已在路上,此项必火。

参见文章《2021再看Deno(关于CDN for JavaScript modules的思考)》 https://mp.weixin.qq.com/s/Ez...

逻辑编排,更加面向开发者

已收到很多imove类似项目。解决逻辑可变和不直观的问题。

以函数为粒度,继而通过运用配置类的操作,将逻辑可视化,配置化。用法极为简单。参见 https://github.com/imgcook/imove

智能UI精细化

首先服务端搜索瓶颈已经到了天花板,端智能和端UI的探索,一定是增量上提升业务指标的。)

  • 参见文章《智能UI:面向未来的UI开发技术》
  • 参见文章《CBU智能UI落地最佳实践》

智能化 PRD 2 Code(P2C)

站在产研链路审视研发效率问题。站在D2C(设计稿转代码)之上,引入PD产品经理标注方式提升出码,进一步做到无人工,真正智能化。(招人做此项目)

下面2020年D2前端大会上ppt分享的一页,PD标注业务含义讲的还是比较清楚的。

参见文章《前端智能化实践— P2C 从需求文档生成代码 | D2 分享视频+文章》

不会 Python,前端也能搞机器学习

基于pipline思路抽象的AI基础框架,让AI落地更简单。

参见文章《前端机器学习的利器,更快的 Pipcook 1.2》

一些我关注的开源项目

midway-hooks

最好用最潮的Serverless同构框架,没有之一。

imove

逻辑编排工具,开发是有快感的。基于阿里开源的x6和formrender,简单易用。

前面讲过过,这里就不在赘述。

ykfe/ssr

基于Serverless的端渲染方案。支持多个Faas环境。同时支持csr和ssr无缝降级的方案。基于之前成熟的egg-react-ssr,去掉Eggjs,改成midway-faas,天然一套支持跨运营商。

airpack

阿里内部的支持http import和cjs转esm的高效构建工具,据说已经在筹备开源工作了。看到一个性能压测,airpack大约是webpack5的20倍左右。

不确定的是 react server component

总结

2020年各大厂应该都在困惑,老项目为提升业务指标发愁,新项目在为研发提效发愁。很多既得利益者,吃着所剩不多的红利,一方面担心被替换,舍不得放弃,一方面又不敢做改变。我的观点是服务端算法(包括搜索推荐)今天已经触及了天花板,再提升一个点都会比之前更难。传统前端基建也面临一样的问题,比如node,搭建,ui框架,对于下一代升级想法,大概也是缺少想法和目标的。

创新是需要勇气的,眼界不够的人不能做到,能力不足的人不能做到。前端和AI结合的跨技术融合项目是存在非常大的机遇和挑战的。甄子以一人之力,扛起前端智能化大旗是非常不容易的。目前imgcook在设计稿转代码领域已经取得阶段性结果,但我们还有如下探索。

  • 智能UI,目前已经能够看到增量的点。头条也做了类似的事儿。
  • P2C,围绕业务标注,实现产研提效,已验证。
  • Pipcook会进一步简化ai开发,只要有数据就能训练模型,真的是有手就行。
  • Design+,设计资产管理。
  • 像airpack、http import、imove、midway-hooks、ykfe/ssr这些其实都会成为前端新基建。

总结一下,笔者认为前端智能化是2021年最有前途的方向。

很多人都以为前端智能化对ai和算法要求极高,其实这个看法是片面的。在前端智能化团队里有3种事儿可以做:1)业务,2)工程,3)算法。其中工程和业务是不需要算法的,对于新人也是会给缓冲期的,可以先做擅长的事儿,同时跟着团队向ai算法方面学习。

我经历过的阶段:

  • 熟悉imgcook,这是d2c领域。覆盖了 2020 年双 11 会场 90%+ 的模块开发,出码可用率达到 79.26%,且需求吞吐量提升 1.5~2 倍,给前端研发带来实质性的提效。因为我是双十一PM,这个我是相当知道。
  • 接手P2C,建立pd标注体系。这个过程是很困难的,但也是很有成长的。其实,更多的我是一个产品的角色。站在D2C的肩膀上,站在用户视角(PD),保证项目方向不歪。对于pd能做什么,该怎么做,如何快速落地业务,我是有很多思考和成长的。
  • 搞定API,以前都是先选数据源然后确定字段,这是很麻烦的,PD是无法接受的,如果api有100个,每个api有10个字段,pd就疯了。我们的做法是先选字段,然后再确定具体接口,这种逆向思维,在这种项目里是非常适用的。
  • 继续深化C端解决方案,站在淘系业务和技术都很成熟的前提下,提升业务数据,又能兼顾技术创新,大概不会有比这还胆大包天且令人激动的目标了。

这个团队是一个复合型团队。除了有卓风,妙净等老阿里前端大佬外,还有算法、设计、AI底层、UI等各个方面专家。

这是一个很潮、包容、技术范的前端智能化团队。欢迎感兴趣的同学一起交流前端智能化。

作者:狼叔
原文链接
本文为阿里云原创内容,未经允许不得转载

查看原文

sprina 关注了用户 · 1月15日

阿里云云栖号 @yunqishequ_5aa899aad5395

阿里云官网内容平台!汇聚阿里云优质内容(入门、文档、案例、最佳实践、直播等)!如需转载或内容类合作,邮件yqgroup@service.aliyun.com 秒级回复!

关注 10649

sprina 赞了文章 · 1月15日

2021 年前端趋势预测

简介: 2021 哪些前端技术值得关注?来听听狼叔的分享。

知乎上,有人提问《2021前端会有什么新的变化?》

狼叔的回答二天超过6.1万+阅读量,目前444个赞同,2个专业徽章,整体上看,这篇回答大家还是相当认可的。

诚如很多大V所讲,确实是2020年,前端圈带来具有突破意义的内容或框架不多,很多人也不会再有2013年到2017间日日新的框架大战局面,也不会有Node全栈之争,也不会因为React-Native、Weex、Flutter这类跨端而欣喜若狂。

我能看到的是今天前端已趋于稳定,在深水区探索,比如蚂蚁金服的x6,在图形可视化方面做的就是非常好,比如淘宝的midway-faas,在Serverless领域确实有它独特的定位。比如语雀,钉钉文档,在线Excel等等,也都不是可以轻松可以搞定的。

我很开心的看到,混乱之后,大家都能在深水区里进行探索。2019年阿里经济体前端委员会四大技术方向:第一搭建服务,第二是 Serverless,第三是智能化,第四是 IDE。2020年阿里经济体前端委员的突破方向是互动技术、跨端技术、智能化。而中后台、数据可视化、Node.js(Serverless)、工程体系(安全生产)都变成了基础技术方向。这大概是能够代表前端技术走向的。

我个人也走过类似的路,2017年加入阿里,将PHP替换为Node.js,随后搞了开源项目egg-react-ssr,然后在2019年加入前端委员会,负责Serverless-side render方向。在2020年,转岗到淘系前端,负责前端智能化相关开发。我其实是非常看好Serverless的,Serverless这种稳步推进的必然是前端新基建,未来玩2到5年问题不大。对我而言,前端智能化的诱惑更大,能够站到产研链路是思考问题,这才是我梦寐以求的机会。

我之前的想法是搞一次Node Party讲讲这些2021年前端趋势预测。在线直播,不知道是否有人感兴趣。先把我的这些思考写出来,希望能够对大家判断2021年前端趋势有所帮助。

http import 会大行其道

其实就是Deno创造的方式,Deno被评为2020最佳开源贡献也是实至名归的。

import cheerio from "https://dev.jspm.io/npm:cheerio/index.js";

把cjs转esm都交给CDN类的服务来做更合适。事实上,pika.dev/skypack.dev/http://jspm.io 都已经做了这件事儿。

Node.js马上跟上,相关PR早已在路上,此项必火。

参见文章《2021再看Deno(关于CDN for JavaScript modules的思考)》 https://mp.weixin.qq.com/s/Ez...

逻辑编排,更加面向开发者

已收到很多imove类似项目。解决逻辑可变和不直观的问题。

以函数为粒度,继而通过运用配置类的操作,将逻辑可视化,配置化。用法极为简单。参见 https://github.com/imgcook/imove

智能UI精细化

首先服务端搜索瓶颈已经到了天花板,端智能和端UI的探索,一定是增量上提升业务指标的。)

  • 参见文章《智能UI:面向未来的UI开发技术》
  • 参见文章《CBU智能UI落地最佳实践》

智能化 PRD 2 Code(P2C)

站在产研链路审视研发效率问题。站在D2C(设计稿转代码)之上,引入PD产品经理标注方式提升出码,进一步做到无人工,真正智能化。(招人做此项目)

下面2020年D2前端大会上ppt分享的一页,PD标注业务含义讲的还是比较清楚的。

参见文章《前端智能化实践— P2C 从需求文档生成代码 | D2 分享视频+文章》

不会 Python,前端也能搞机器学习

基于pipline思路抽象的AI基础框架,让AI落地更简单。

参见文章《前端机器学习的利器,更快的 Pipcook 1.2》

一些我关注的开源项目

midway-hooks

最好用最潮的Serverless同构框架,没有之一。

imove

逻辑编排工具,开发是有快感的。基于阿里开源的x6和formrender,简单易用。

前面讲过过,这里就不在赘述。

ykfe/ssr

基于Serverless的端渲染方案。支持多个Faas环境。同时支持csr和ssr无缝降级的方案。基于之前成熟的egg-react-ssr,去掉Eggjs,改成midway-faas,天然一套支持跨运营商。

airpack

阿里内部的支持http import和cjs转esm的高效构建工具,据说已经在筹备开源工作了。看到一个性能压测,airpack大约是webpack5的20倍左右。

不确定的是 react server component

总结

2020年各大厂应该都在困惑,老项目为提升业务指标发愁,新项目在为研发提效发愁。很多既得利益者,吃着所剩不多的红利,一方面担心被替换,舍不得放弃,一方面又不敢做改变。我的观点是服务端算法(包括搜索推荐)今天已经触及了天花板,再提升一个点都会比之前更难。传统前端基建也面临一样的问题,比如node,搭建,ui框架,对于下一代升级想法,大概也是缺少想法和目标的。

创新是需要勇气的,眼界不够的人不能做到,能力不足的人不能做到。前端和AI结合的跨技术融合项目是存在非常大的机遇和挑战的。甄子以一人之力,扛起前端智能化大旗是非常不容易的。目前imgcook在设计稿转代码领域已经取得阶段性结果,但我们还有如下探索。

  • 智能UI,目前已经能够看到增量的点。头条也做了类似的事儿。
  • P2C,围绕业务标注,实现产研提效,已验证。
  • Pipcook会进一步简化ai开发,只要有数据就能训练模型,真的是有手就行。
  • Design+,设计资产管理。
  • 像airpack、http import、imove、midway-hooks、ykfe/ssr这些其实都会成为前端新基建。

总结一下,笔者认为前端智能化是2021年最有前途的方向。

很多人都以为前端智能化对ai和算法要求极高,其实这个看法是片面的。在前端智能化团队里有3种事儿可以做:1)业务,2)工程,3)算法。其中工程和业务是不需要算法的,对于新人也是会给缓冲期的,可以先做擅长的事儿,同时跟着团队向ai算法方面学习。

我经历过的阶段:

  • 熟悉imgcook,这是d2c领域。覆盖了 2020 年双 11 会场 90%+ 的模块开发,出码可用率达到 79.26%,且需求吞吐量提升 1.5~2 倍,给前端研发带来实质性的提效。因为我是双十一PM,这个我是相当知道。
  • 接手P2C,建立pd标注体系。这个过程是很困难的,但也是很有成长的。其实,更多的我是一个产品的角色。站在D2C的肩膀上,站在用户视角(PD),保证项目方向不歪。对于pd能做什么,该怎么做,如何快速落地业务,我是有很多思考和成长的。
  • 搞定API,以前都是先选数据源然后确定字段,这是很麻烦的,PD是无法接受的,如果api有100个,每个api有10个字段,pd就疯了。我们的做法是先选字段,然后再确定具体接口,这种逆向思维,在这种项目里是非常适用的。
  • 继续深化C端解决方案,站在淘系业务和技术都很成熟的前提下,提升业务数据,又能兼顾技术创新,大概不会有比这还胆大包天且令人激动的目标了。

这个团队是一个复合型团队。除了有卓风,妙净等老阿里前端大佬外,还有算法、设计、AI底层、UI等各个方面专家。

这是一个很潮、包容、技术范的前端智能化团队。欢迎感兴趣的同学一起交流前端智能化。

作者:狼叔
原文链接
本文为阿里云原创内容,未经允许不得转载

查看原文

赞 15 收藏 9 评论 0

sprina 回答了问题 · 1月15日

vue报错 但是找不到错在哪里 有可以具体看到错在哪一行的方法吗。

可以的,展开后在右边有一些链接,找到你写的那个页面(.vue)文件点一下就能打开这个文件显示错误的地方了

关注 3 回答 2

sprina 发布了文章 · 1月9日

前端面试js高频手写大全

介绍

在前端面试中,手撕代码显然是不可避免的,并且占很大的一部分比重。

一般来说,如果代码写的好,即使理论知识答得不够清楚,也能有大概率通过面试。并且其实很多手写往往背后就考察了你对相关理论的认识。

编程题主要分为这几种类型:

* 算法题
* 涉及js原理的题以及ajax请求
* 业务场景题: 实现一个具有某种功能的组件
* 其他(进阶,对计算机综合知识的考察,考的相对较少):实现订阅发布者模式;分别用面向对象编程,面向过程编程,函数式编程实现把大象放进冰箱等等

其中前两种类型所占比重最大。
算法题建议养成每天刷一道leetcode的习惯,重点刷数据结构(栈,链表,队列,树),动态规划,DFS,BFS

本文主要涵盖了第二种类型的各种重点手写。

建议优先掌握

  • instanceof (考察对原型链的理解)
  • new (对创建对象实例过程的理解)
  • call&apply&bind (对this指向的理解)
  • 手写promise (对异步的理解)
  • 手写原生ajax (对ajax原理和http请求方式的理解,重点是get和post请求的实现)
  • 其他:数组,字符串的api的实现,难度相对较低。只要了解数组,字符串的常用方法的用法,现场就能写出来个大概。(ps:笔者认为数组的reduce方法比较难,这块有余力可以单独看一些,即使面试没让你实现reduce,写其他题时用上它也是很加分的)

话不多说,直接开始

1. 手写instanceof

instanceof作用:

判断一个实例是否是其父类或者祖先类型的实例。

instanceof在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype查找失败,返回 false

 let myInstanceof = (target,origin) => {
     while(target) {
         if(target.__proto__===origin.prototype) {
            return true
         }
         target = target.__proto__
     }
     return false
 }
 let a = [1,2,3]
 console.log(myInstanceof(a,Array));  // true
 console.log(myInstanceof(a,Object));  // true

2. 实现数组的map方法

数组的map() 方法会返回一个新的数组,这个新数组中的每个元素对应原数组中的对应位置元素调用一次提供的函数后的返回值。

用法:

const a = [1, 2, 3, 4];
const b = array1.map(x => x * 2);
console.log(b);   // Array [2, 4, 6, 8]

原生实现:

 Array.prototype.myMap = function(fn, thisValue) {
     let res = []
     thisValue = thisValue||[]
     let arr = this
     for(let i in arr) {
        res.push(fn(arr[i]))
     }
     return res
 }

3. reduce实现数组的map方法

利用数组内置的reduce方法实现map方法,考察对reduce原理的掌握

Array.prototype.myMap = function(fn,thisValue){
     var res = [];
     thisValue = thisValue||[];
     this.reduce(function(pre,cur,index,arr){
         return res.push(fn.call(thisValue,cur,index,arr));
     },[]);
     return res;
}
​
var arr = [2,3,1,5];
arr.myMap(function(item,index,arr){
 console.log(item,index,arr);
})

4. 手写数组的reduce方法

reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终为一个值,是ES5中新增的又一个数组逐项处理方法

参数:

  • callback(一个在数组中每一项上调用的函数,接受四个函数:)

    • previousValue(上一次调用回调函数时的返回值,或者初始值)
    • currentValue(当前正在处理的数组元素)
    • currentIndex(当前正在处理的数组元素下标)
    • array(调用reduce()方法的数组)
  • initialValue(可选的初始值。作为第一次调用回调函数时传给previousValue的值)
 function reduce(arr, cb, initialValue){
     var num = initValue == undefined? num = arr[0]: initValue;
     var i = initValue == undefined? 1: 0
     for (i; i< arr.length; i++){
        num = cb(num,arr[i],i)
     }
     return num
 }
 
 function fn(result, currentValue, index){
     return result + currentValue
 }
 
 var arr = [2,3,4,5]
 var b = reduce(arr, fn,10) 
 var c = reduce(arr, fn)
 console.log(b)   // 24

5. 数组扁平化

数组扁平化就是把多维数组转化成一维数组

1. es6提供的新方法 flat(depth)

let a = [1,[2,3]]; 
a.flat(); // [1,2,3] 
a.flat(1); //[1,2,3]

其实还有一种更简单的办法,无需知道数组的维度,直接将目标数组变成1维数组。 depth的值设置为Infinity。

let a = [1,[2,3,[4,[5]]]]; 
a.flat(Infinity); // [1,2,3,4,5]  a是4维数组

2. 利用cancat

function flatten(arr) {
     var res = [];
     for (let i = 0, length = arr.length; i < length; i++) {
     if (Array.isArray(arr[i])) {
     res = res.concat(flatten(arr[i])); //concat 并不会改变原数组
     //res.push(...flatten(arr[i])); //或者用扩展运算符 
     } else {
         res.push(arr[i]);
       }
     }
     return res;
 }
 let arr1 = [1, 2,[3,1],[2,3,4,[2,3,4]]]
flatten(arr1); //[1, 2, 3, 1, 2, 3, 4, 2, 3, 4]

6. 函数柯里化

柯里化的定义:接收一部分参数,返回一个函数接收剩余参数,接收足够参数后,执行原函数。

当柯里化函数接收到足够参数后,就会执行原函数,如何去确定何时达到足够的参数呢?

有两种思路:

  1. 通过函数的 length 属性,获取函数的形参个数,形参的个数就是所需的参数个数
  2. 在调用柯里化工具函数时,手动指定所需的参数个数

将这两点结合一下,实现一个简单 curry 函数:


/**
 * 将函数柯里化
 * @param fn    待柯里化的原函数
 * @param len   所需的参数个数,默认为原函数的形参个数
 */
function curry(fn,len = fn.length) {
 return _curry.call(this,fn,len)
}
​
/**
 * 中转函数
 * @param fn    待柯里化的原函数
 * @param len   所需的参数个数
 * @param args  已接收的参数列表
 */
function _curry(fn,len,...args) {
    return function (...params) {
         let _args = [...args,...params];
         if(_args.length >= len){
             return fn.apply(this,_args);
         }else{
          return _curry.call(this,fn,len,..._args)
         }
    }
}

我们来验证一下:

let _fn = curry(function(a,b,c,d,e){
 console.log(a,b,c,d,e)
});
​
_fn(1,2,3,4,5);     // print: 1,2,3,4,5
_fn(1)(2)(3,4,5);   // print: 1,2,3,4,5
_fn(1,2)(3,4)(5);   // print: 1,2,3,4,5
_fn(1)(2)(3)(4)(5); // print: 1,2,3,4,5

我们常用的工具库 lodash 也提供了 curry 方法,并且增加了非常好玩的 placeholder 功能,通过占位符的方式来改变传入参数的顺序。

比如说,我们传入一个占位符,本次调用传递的参数略过占位符, 占位符所在的位置由下次调用的参数来填充,比如这样:

直接看一下官网的例子:

img

接下来我们来思考,如何实现占位符的功能。

对于 lodash 的 curry 函数来说,curry 函数挂载在 lodash 对象上,所以将 lodash 对象当做默认占位符来使用。

而我们的自己实现的 curry 函数,本身并没有挂载在任何对象上,所以将 curry 函数当做默认占位符

使用占位符,目的是改变参数传递的顺序,所以在 curry 函数实现中,每次需要记录是否使用了占位符,并且记录占位符所代表的参数位置。

直接上代码:


/**
 * @param  fn           待柯里化的函数
 * @param  length       需要的参数个数,默认为函数的形参个数
 * @param  holder       占位符,默认当前柯里化函数
 * @return {Function}   柯里化后的函数
 */
function curry(fn,length = fn.length,holder = curry){
 return _curry.call(this,fn,length,holder,[],[])
}
/**
 * 中转函数
 * @param fn            柯里化的原函数
 * @param length        原函数需要的参数个数
 * @param holder        接收的占位符
 * @param args          已接收的参数列表
 * @param holders       已接收的占位符位置列表
 * @return {Function}   继续柯里化的函数 或 最终结果
 */
function _curry(fn,length,holder,args,holders){
 return function(..._args){
 //将参数复制一份,避免多次操作同一函数导致参数混乱
 let params = args.slice();
 //将占位符位置列表复制一份,新增加的占位符增加至此
 let _holders = holders.slice();
 //循环入参,追加参数 或 替换占位符
 _args.forEach((arg,i)=>{
 //真实参数 之前存在占位符 将占位符替换为真实参数
 if (arg !== holder && holders.length) {
     let index = holders.shift();
     _holders.splice(_holders.indexOf(index),1);
     params[index] = arg;
 }
 //真实参数 之前不存在占位符 将参数追加到参数列表中
 else if(arg !== holder && !holders.length){
     params.push(arg);
 }
 //传入的是占位符,之前不存在占位符 记录占位符的位置
 else if(arg === holder && !holders.length){
     params.push(arg);
     _holders.push(params.length - 1);
 }
 //传入的是占位符,之前存在占位符 删除原占位符位置
 else if(arg === holder && holders.length){
    holders.shift();
 }
 });
 // params 中前 length 条记录中不包含占位符,执行函数
 if(params.length >= length && params.slice(0,length).every(i=>i!==holder)){
 return fn.apply(this,params);
 }else{
 return _curry.call(this,fn,length,holder,params,_holders)
 }
 }
}

验证一下:;

let fn = function(a, b, c, d, e) {
 console.log([a, b, c, d, e]);
}
​
let _ = {}; // 定义占位符
let _fn = curry(fn,5,_);  // 将函数柯里化,指定所需的参数个数,指定所需的占位符
​
_fn(1, 2, 3, 4, 5);                 // print: 1,2,3,4,5
_fn(_, 2, 3, 4, 5)(1);              // print: 1,2,3,4,5
_fn(1, _, 3, 4, 5)(2);              // print: 1,2,3,4,5
_fn(1, _, 3)(_, 4,_)(2)(5);         // print: 1,2,3,4,5
_fn(1, _, _, 4)(_, 3)(2)(5);        // print: 1,2,3,4,5
_fn(_, 2)(_, _, 4)(1)(3)(5);        // print: 1,2,3,4,5

至此,我们已经完整实现了一个 curry 函数~~

7. 实现深拷贝

浅拷贝和深拷贝的区别:

浅拷贝:只拷贝一层,更深层的对象级别的只拷贝引用

深拷贝:拷贝多层,每一级别的数据都会拷贝。这样更改拷贝值就不影响另外的对象

ES6浅拷贝方法:Object.assign(target,...sources)


let obj={
 id:1,
 name:'Tom',
 msg:{
 age:18
 }
}
let o={}
//实现深拷贝  递归    可以用于生命游戏那个题对二维数组的拷贝,
//但比较麻烦,因为已知元素都是值,直接复制就行,无需判断
function deepCopy(newObj,oldObj){
     for(var k in oldObj){
         let item=oldObj[k]
         //判断是数组?对象?简单类型?
         if(item instanceof Array){
             newObj[k]=[]
             deepCopy(newObj[k],item)
         }else if(item instanceof Object){
             newObj[k]={}
             deepCopy(newObj[k],item)
         }else{  //简单数据类型,直接赋值
             newObj[k]=item
         }
     }
}

8. 手写call, apply, bind

手写call

Function.prototype.myCall=function(context=window){  // 函数的方法,所以写在Fuction原型对象上
 if(typeof this !=="function"){   // 这里if其实没必要,会自动抛出错误
    throw new Error("不是函数")
 }
 const obj=context||window   //这里可用ES6方法,为参数添加默认值,js严格模式全局作用域this为undefined
 obj.fn=this      //this为调用的上下文,this此处为函数,将这个函数作为obj的方法
 const arg=[...arguments].slice(1)   //第一个为obj所以删除,伪数组转为数组
 res=obj.fn(...arg)
 delete obj.fn   // 不删除会导致context属性越来越多
 return res
}
//用法:f.call(obj,arg1)
function f(a,b){
 console.log(a+b)
 console.log(this.name)
}
let obj={
 name:1
}
f.myCall(obj,1,2) //否则this指向window

obj.greet.call({name: 'Spike'}) //打出来的是 Spike

手写apply(arguments[this, [参数1,参数2.....] ])

Function.prototype.myApply=function(context){  // 箭头函数从不具有参数对象!!!!!这里不能写成箭头函数
 let obj=context||window
 obj.fn=this
 const arg=arguments[1]||[]    //若有参数,得到的是数组
 let res=obj.fn(...arg)
 delete obj.fn
 return res
} 
function f(a,b){
 console.log(a,b)
 console.log(this.name)
}
let obj={
 name:'张三'
}
f.myApply(obj,[1,2])  //arguments[1]

手写bind

this.value = 2
var foo = {
 value: 1
};
var bar = function(name, age, school){
 console.log(name) // 'An'
 console.log(age) // 22
 console.log(school) // '家里蹲大学'
}
var result = bar.bind(foo, 'An') //预置了部分参数'An'
result(22, '家里蹲大学') //这个参数会和预置的参数合并到一起放入bar中

简单版本

Function.prototype.bind = function(context, ...outerArgs) {
 var fn = this;
 return function(...innerArgs) {   //返回了一个函数,...rest为实际调用时传入的参数
 return fn.apply(context,[...outerArgs, ...innerArgs]);  //返回改变了this的函数,
 //参数合并
 }
}

new失败的原因:

例:

// 声明一个上下文
let thovino = {
 name: 'thovino'
}
​
// 声明一个构造函数
let eat = function (food) {
 this.food = food
 console.log(`${this.name} eat ${this.food}`)
}
eat.prototype.sayFuncName = function () {
 console.log('func name : eat')
}
​
// bind一下
let thovinoEat = eat.bind(thovino)
let instance = new thovinoEat('orange')  //实际上orange放到了thovino里面
console.log('instance:', instance) // {}

生成的实例是个空对象

new操作符执行时,我们的thovinoEat函数可以看作是这样:

function thovinoEat (...innerArgs) {
 eat.call(thovino, ...outerArgs, ...innerArgs)
}

在new操作符进行到第三步的操作thovinoEat.call(obj, ...args)时,这里的obj是new操作符自己创建的那个简单空对象{},但它其实并没有替换掉thovinoEat函数内部的那个上下文对象thovino。这已经超出了call的能力范围,因为这个时候要替换的已经不是thovinoEat函数内部的this指向,而应该是thovino对象。

换句话说,我们希望的是new操作符将eat内的this指向操作符自己创建的那个空对象。但是实际上指向了thovinonew操作符的第三步动作并没有成功

可new可继承版本

Function.prototype.bind = function (context, ...outerArgs) {
 let that = this;
​
function res (...innerArgs) {
     if (this instanceof res) {
         // new操作符执行时
         // 这里的this在new操作符第三步操作时,会指向new自身创建的那个简单空对象{}
         that.call(this, ...outerArgs, ...innerArgs)
     } else {
         // 普通bind
         that.call(context, ...outerArgs, ...innerArgs)
     }
     }
     res.prototype = this.prototype //!!!
     return res
}

9. 手动实现new

new的过程文字描述:

  1. 创建一个空对象 obj;
  2. 将空对象的隐式原型(proto)指向构造函数的prototype。
  3. 使用 call 改变 this 的指向
  4. 如果无返回值或者返回一个非对象值,则将 obj 返回作为新对象;如果返回值是一个新对象的话那么直接直接返回该对象。
function Person(name,age){
 this.name=name
 this.age=age
}
Person.prototype.sayHi=function(){
 console.log('Hi!我是'+this.name)
}
let p1=new Person('张三',18)
​
////手动实现new
function create(){
 let obj={}
 //获取构造函数
 let fn=[].shift.call(arguments)  //将arguments对象提出来转化为数组,arguments并不是数组而是对象    !!!这种方法删除了arguments数组的第一个元素,!!这里的空数组里面填不填元素都没关系,不影响arguments的结果      或者let arg = [].slice.call(arguments,1)
 obj.__proto__=fn.prototype
 let res=fn.apply(obj,arguments)    //改变this指向,为实例添加方法和属性
 //确保返回的是一个对象(万一fn不是构造函数)
 return typeof res==='object'?res:obj
}
​
let p2=create(Person,'李四',19)
p2.sayHi()

细节:

[].shift.call(arguments)  也可写成:
 let arg=[...arguments]
 let fn=arg.shift()  //使得arguments能调用数组方法,第一个参数为构造函数
 obj.__proto__=fn.prototype
 //改变this指向,为实例添加方法和属性
 let res=fn.apply(obj,arg)

10. 手写promise(常见promise.all, promise.race)

// Promise/A+ 规范规定的三种状态
const STATUS = {
 PENDING: 'pending',
 FULFILLED: 'fulfilled',
 REJECTED: 'rejected'
}
​
class MyPromise {
 // 构造函数接收一个执行回调
 constructor(executor) {
     this._status = STATUS.PENDING // Promise初始状态
     this._value = undefined // then回调的值
     this._resolveQueue = [] // resolve时触发的成功队列
     this._rejectQueue = [] // reject时触发的失败队列
    ​
 // 使用箭头函数固定this(resolve函数在executor中触发,不然找不到this)
 const resolve = value => {
     const run = () => {
         // Promise/A+ 规范规定的Promise状态只能从pending触发,变成fulfilled
         if (this._status === STATUS.PENDING) {
             this._status = STATUS.FULFILLED // 更改状态
             this._value = value // 储存当前值,用于then回调
            ​
             // 执行resolve回调
             while (this._resolveQueue.length) {
                 const callback = this._resolveQueue.shift()
                 callback(value)
             }
         }
     }
     //把resolve执行回调的操作封装成一个函数,放进setTimeout里,以实现promise异步调用的特性(规范上是微任务,这里是宏任务)
     setTimeout(run)
 }
​
 // 同 resolve
 const reject = value => {
     const run = () => {
         if (this._status === STATUS.PENDING) {
         this._status = STATUS.REJECTED
         this._value = value
        ​
         while (this._rejectQueue.length) {
             const callback = this._rejectQueue.shift()
             callback(value)
         }
     }
 }
     setTimeout(run)
 }

     // new Promise()时立即执行executor,并传入resolve和reject
     executor(resolve, reject)
 }
​
 // then方法,接收一个成功的回调和一个失败的回调
 function then(onFulfilled, onRejected) {
  // 根据规范,如果then的参数不是function,则忽略它, 让值继续往下传递,链式调用继续往下执行
  typeof onFulfilled !== 'function' ? onFulfilled = value => value : null
  typeof onRejected !== 'function' ? onRejected = error => error : null

  // then 返回一个新的promise
  return new MyPromise((resolve, reject) => {
    const resolveFn = value => {
      try {
        const x = onFulfilled(value)
        // 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
        x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
      } catch (error) {
        reject(error)
      }
    }
  }
}
​
  const rejectFn = error => {
      try {
        const x = onRejected(error)
        x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
      } catch (error) {
        reject(error)
      }
    }

    switch (this._status) {
      case STATUS.PENDING:
        this._resolveQueue.push(resolveFn)
        this._rejectQueue.push(rejectFn)
        break;
      case STATUS.FULFILLED:
        resolveFn(this._value)
        break;
      case STATUS.REJECTED:
        rejectFn(this._value)
        break;
    }
 })
 }
 catch (rejectFn) {
  return this.then(undefined, rejectFn)
}
// promise.finally方法
finally(callback) {
  return this.then(value => MyPromise.resolve(callback()).then(() => value), error => {
    MyPromise.resolve(callback()).then(() => error)
  })
}

 // 静态resolve方法
 static resolve(value) {
      return value instanceof MyPromise ? value : new MyPromise(resolve => resolve(value))
  }

 // 静态reject方法
 static reject(error) {
      return new MyPromise((resolve, reject) => reject(error))
    }

 // 静态all方法
 static all(promiseArr) {
      let count = 0
      let result = []
      return new MyPromise((resolve, reject) =>       {
        if (!promiseArr.length) {
          return resolve(result)
        }
        promiseArr.forEach((p, i) => {
          MyPromise.resolve(p).then(value => {
            count++
            result[i] = value
            if (count === promiseArr.length) {
              resolve(result)
            }
          }, error => {
            reject(error)
          })
        })
      })
    }

 // 静态race方法
 static race(promiseArr) {
      return new MyPromise((resolve, reject) => {
        promiseArr.forEach(p => {
          MyPromise.resolve(p).then(value => {
            resolve(value)
          }, error => {
            reject(error)
          })
        })
      })
    }
}

11. 手写原生AJAX

步骤

  1. 创建 XMLHttpRequest 实例
  2. 发出 HTTP 请求
  3. 服务器返回 XML 格式的字符串
  4. JS 解析 XML,并更新局部页面

    不过随着历史进程的推进,XML 已经被淘汰,取而代之的是 JSON

了解了属性和方法之后,根据 AJAX 的步骤,手写最简单的 GET 请求。

version 1.0:

myButton.addEventListener('click', function () {
  ajax()
})

function ajax() {
  let xhr = new XMLHttpRequest() //实例化,以调用方法
  xhr.open('get', 'https://www.google.com')  //参数2,url。参数三:异步
  xhr.onreadystatechange = () => {  //每当 readyState 属性改变时,就会调用该函数。
    if (xhr.readyState === 4) {  //XMLHttpRequest 代理当前所处状态。
      if (xhr.status >= 200 && xhr.status < 300) {  //200-300请求成功
        let string = request.responseText
        //JSON.parse() 方法用来解析JSON字符串,构造由字符串描述的JavaScript值或对象
        let object = JSON.parse(string)
      }
    }
  }
  request.send() //用于实际发出 HTTP 请求。不带参数为GET请求
}

promise实现

function ajax(url) {
  const p = new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest()
    xhr.open('get', url)
    xhr.onreadystatechange = () => {
      if (xhr.readyState == 4) {
        if (xhr.status >= 200 && xhr.status <= 300) {
          resolve(JSON.parse(xhr.responseText))
        } else {
          reject('请求出错')
        }
      }
    }
    xhr.send()  //发送hppt请求
  })
  return p
}
let url = '/data.json'
ajax(url).then(res => console.log(res))
  .catch(reason => console.log(reason))

12. 手写节流防抖函数

函数节流与函数防抖都是为了限制函数的执行频次,是一种性能优化的方案,比如应用于window对象的resize、scroll事件,拖拽时的mousemove事件,文字输入、自动完成的keyup事件。

节流:连续触发事件但是在 n 秒中只执行一次函数

例:(连续不断动都需要调用时用,设一时间间隔),像dom的拖拽,如果用消抖的话,就会出现卡顿的感觉,因为只在停止的时候执行了一次,这个时候就应该用节流,在一定时间内多次执行,会流畅很多。

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

例:(连续不断触发时不调用,触发完后过一段时间调用),像仿百度搜索,就应该用防抖,当我连续不断输入时,不会发送请求;当我一段时间内不输入了,才会发送一次请求;如果小于这段时间继续输入的话,时间会重新计算,也不会发送请求。

防抖的实现:

function debounce(fn, delay) {
     if(typeof fn!=='function') {
        throw new TypeError('fn不是函数')
     }
     let timer; // 维护一个 timer
     return function () {
         var _this = this; // 取debounce执行作用域的this(原函数挂载到的对象)
         var args = arguments;
         if (timer) {
            clearTimeout(timer);
         }
         timer = setTimeout(function () {
            fn.apply(_this, args); // 用apply指向调用debounce的对象,相当于_this.fn(args);
         }, delay);
     };
}

// 调用​
input1.addEventListener('keyup', debounce(() => {
 console.log(input1.value)
}), 600)

节流的实现:

function throttle(fn, delay) {
  let timer;
  return function () {
    var _this = this;
    var args = arguments;
    if (timer) {
      return;
    }
    timer = setTimeout(function () {
      fn.apply(_this, args); // 这里args接收的是外边返回的函数的参数,不能用arguments
      // fn.apply(_this, arguments); 需要注意:Chrome 14 以及 Internet Explorer 9 仍然不接受类数组对象。如果传入类数组对象,它们会抛出异常。
      timer = null; // 在delay后执行完fn之后清空timer,此时timer为假,throttle触发可以进入计时器
    }, delay)
  }
}

div1.addEventListener('drag', throttle((e) => {
  console.log(e.offsetX, e.offsetY)
}, 100))

13. 手写Promise加载图片

function getData(url) {
  return new Promise((resolve, reject) => {
    $.ajax({
      url,
      success(data) {
        resolve(data)
      },
      error(err) {
        reject(err)
      }
    })
  })
}
const url1 = './data1.json'
const url2 = './data2.json'
const url3 = './data3.json'
getData(url1).then(data1 => {
  console.log(data1)
  return getData(url2)
}).then(data2 => {
  console.log(data2)
  return getData(url3)
}).then(data3 =>
  console.log(data3)
).catch(err =>
  console.error(err)
)

14. 函数实现一秒钟输出一个数

for(let i=0;i<=10;i++){   //用var打印的都是11
 setTimeout(()=>{
    console.log(i);
 },1000*i)
}

15. 创建10个标签,点击的时候弹出来对应的序号?

var a
for(let i=0;i<10;i++){
 a=document.createElement('a')
 a.innerHTML=i+'<br>'
 a.addEventListener('click',function(e){
     console.log(this)  //this为当前点击的<a>
     e.preventDefault()  //如果调用这个方法,默认事件行为将不再触发。
     //例如,在执行这个方法后,如果点击一个链接(a标签),浏览器不会跳转到新的 URL 去了。我们可以用 event.isDefaultPrevented() 来确定这个方法是否(在那个事件对象上)被调用过了。
     alert(i)
 })
 const d=document.querySelector('div')
 d.appendChild(a)  //append向一个已存在的元素追加该元素。
}

参考:

数组扁平化 https://juejin.im/post/5c971ee16fb9a070ce31b64e#heading-3

函数柯里化 https://juejin.im/post/6844903882208837645

节流防抖 https://www.jianshu.com/p/c8b...

查看原文

赞 44 收藏 38 评论 2

sprina 关注了用户 · 2020-12-31

九是我呀 @jiushiwoya_5fc6f64f51980

希望每写一篇优质文章,工资就涨100元。

关注 1

认证与成就

  • 获得 80 次点赞
  • 获得 5 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 5 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2020-06-27
个人主页被 2.3k 人浏览