JavaScript | 青训营笔记

67 阅读18分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天

JavaScript

作用

  1. 增强页面动态效果 (如:下拉菜单、图片轮播、信息滚动等)

  2. 实现页面与用户之间的实时、动态交互(如:用户注册、登陆验证等)

类型

  1. 嵌入式

    把 JS 代码写在标签<script type="text/javascript"></script>之间。

  2. 外联式(外部式)

    把 JS 代码写一个单独的外部文件中,文件以'.js'为扩展名。

    <script src="文件名.js"></script>

js代码放置的位置

  • 浏览器解释HTML时是按自上而下的顺序的

  • 放在<head>部分

    最常用的方式是在页面中head部分放置<script></script>元素,浏览器解析head部分就会执行这个代码,然后才解析页面的其余部分。

  • 放在<body>部分

    JavaScript代码在网页读取到该语句的时候就会执行,所以前面的 script 就先被执行。

    进行页面显示初始化的 JS 必须放在head里面,因为初始化都要求提前进行(设置 CSS 等)。

    如果是通过事件调用执行的 function ,那么对位置没什么要求的。

基础知识

代码格式

语句;

注释

  1. 单行注释: //注释内容

  2. 多行注释: /*注释内容*/

变量

  1. 概念

    从字面上看,变量是可变的量;从编程角度讲,变量是用于存储某种/某些数值的存储器。

  2. 声明变量

    var 变量名;

  3. 命名规则

    变量必须使用字母、下划线_或者美元符$开始。

    可以使用任意多个英文字母、数字、下划线_或者美元符$组成。

    不能使用JavaScript关键词与JavaScript保留字。

    注意:JS区分大小写

  4. 赋值

    先声明再赋值

    var 变量名; 变量名 = 值;

    var 变量名 = 值;

  5. 数据类型

(1)基本数据类型

​ 数字Number,字符串String,布尔Boolean,对空Null,未定义Undefined,Symbol

(2)引用数据类型

​ 对象Object,数组Array,函数Function

  1. 检查数据类型typeof

    typeof 变量名:返回变量的数据类型

    typeof Null === 'Object'

  2. 变量存储(栈内存 & 堆内存)

    js变量都是保存在栈内存中的

    基本数据类型(变量名+值)直接存储在栈内存中,值与值之间是独立的,修改一个变量不会影响其他变量

    引用数据类型是保存在堆内存中的对象,每new一个新对象,就会在堆内存中开辟一个新空间来存放对象,而变量保存的是对象的地址(引用),变量名及其对象地址保存在栈内存中。若两个变量保存的是同一个对象引用,则其中一个变量修改属性使,另一个变量也会收到影响。

    你不知道的js堆内存和栈内存 - 知乎
  3. 深拷贝 & 浅拷贝

    深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。

    浅拷贝:复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。

img

深拷贝:将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

img

对象

  1. 对象的概念

    JS中的所有事物都是对象。

    Object是js的一种数据类型,称为对象,是一组数据和功能(函数)的集合,使用键值对来保存。

    每个对象带有属性和方法。

  2. 对象的分类

    内置对象:由ES标准中定义的对象,在任何ES的实现中都可以使用(如 MathString

    宿主对象:由JS运行环境提供的对象,目前主要指由浏览器提供的对象(如 BOMDOM

    自定义对象:开发者自己创建的对象

  3. 创建对象

    (1)使用构造函数:对象名 = new Object();

    (2)字面量法:对象名 = { 属性1:值1, 属性2:值2, ...};

    (3)工厂模式:

    // 工厂模式创建对象
    function creatObject(a,b,...) {
    var obj = new Object();
    obj.属性1 = a;
    obj.属性2 = b;
    ...
    obj.方法名 = function(){   }
    return obj;
    }
    对象名 = creatObject(a,b,...);
    // 使用该方法创建的对象类别无法区分,可结合构造函数来解决该问题
    
  4. 对象的属性

反映该对象某些特定的性质的。如:字符串的长度、图像的长宽等。

添加/修改属性:对象.属性名 = 属性值;

删除属性:delete 对象.属性名;

  1. 对象的方法

能够在对象上执行的动作(函数)。如:表单的“提交”(Submit),时间的“获取”(getYear)等。

对象名 = {..., 方法名: function(){ 函数体 } , ...}

  1. 访问对象方法的语法

    对象名.方法();

  2. 垃圾回收

    将不需要再使用的对象赋值为null,JS会自动进行回收。

函数

函数也是一个对象。函数中可以封装一些功能,在需要时进行调用。

  • 创建函数

    // 方法一:函数声明式
    function 函数名(形参)
    {
        函数体;
        return 返回值; //有返回值时
    }
    
    // 方法二:匿名函数式
    函数名 = function(){ 函数体 };
    
    // 方法三:Function构造函数(不推荐)
    // 会导致解析两次代码(第一次是解析常规ECMAScript代码,第二次是解析传入构造函数中的字符串),性能低
    函数名 = new Function( "形参1", "形参2", ... , "函数体" );
    
  • 调用函数

    函数名(实参);

  • 作用域链(变量作用的范围)

    1. 全局作用域

      在函数外部声明的变量,称为全局变量

      全局变量的作用域是全局的:网页的所有脚本和函数都能够访问

      全局作用域在页面打开时创建,在页面关闭时销毁

      在全局作用域中有一个全局对象window,它代表浏览器的一个窗口

      在全局作用域中创建的变量会作为window对象的属性,创建的函数会作为window对象的方法

    2. 局部作用域(函数作用域)

      在函数内部声明的变量,称为局部变量

      局部变量的作用域是局部的:只能在该函数内部访问

      局部作用域在函数调用时创建,在函数执行完毕时销毁

      每调用一个函数都会创建一个新的函数作用域,它们之间是独立的

      在局部作用域中可以访问到全局变量(可以使用 window.变量名 访问),在全局作用域中不能访问到局部变量

      当局部作用域访问一个变量时,先在自身作用域中寻找,若有就直接使用;否则向上一级作用域寻找,直到找到全局作用域

      在局部作用域有变量/函数提升的特性

      在函数中,不使用var声明的变量会成为全局变量

      函数中的形参相当于在函数作用域中声明了变量

  • 变量/函数预解析(变量/函数提升)

    1. 变量预解析(变量提升)

      使用var关键字声明的变量,会在所有代码执行前先被声明(不会提前赋值)。但如果不是var声明的变量,则不会被提前声明。

    2. 函数预解析(函数提升)

      使用函数声明式创建的函数 function 函数名(){ },会在所有代码执行前被创建(不提前调用)。但使用匿名函数创建的函数不会被提前创建,则不能在声明前调用。

  • 上下文对象 this

    1. 解析器在每次调用函数时,会向函数内部传递进两个隐含的参数:thisarguments

      this指向的是一个对象,称为函数执行的上下文对象。

      在调用函数时,传递的实参都会在arguments中保存。

    2. 在绝大多数情况下,函数的调用方式决定了this指向的对象

      以函数形式调用:this指向window

      以对象的方法形式调用:this指向调用方法的对象

  • 构造函数(类)

    1. 构造函数与普通函数的区别:构造函数在调用时使用new关键字

    2. 构造函数的执行流程:

    (1)创建一个新的对象(实例对象)

    (2)将实例对象设置为构造函数中的this

    (3)逐行执行函数中的代码

    (4)将实例对象作为函数的返回值返回

    1. 语法

      // 创建 构造函数(类)
      function 构造函数名(形参) {
      函数体
      // 函数中的this指向调用该构造函数的实例对象
      }
      // 调用 构造函数
      实例对象名 = new 构造函数名(实参);
      
    2. 使用instanceof检查一个对象是否是一个类的实例

      返回值为布尔值

      实例对象名 instanceof 类名(构造函数名)

      任何对象 instanceof Object === true

  • 原型对象prototype

    1. 创建的每一个函数时,解析器都会向函数中添加一个属性prototype,这个属性的值指向函数的原型对象。

    2. 如果函数作为普通函数调用prototype没有任何作用;当函数以构造函数的形式调用时,它所创建的实例对象中都会有一个隐含的属性(使用__proto__访问该属性),指向该构造函数的原型对象。

    3. 原型对象就相当于一个公共的区域,所有同一个类的实例对象都可以访问到这个原型对象。因此,创建构造函数时,可以将对象中共有的属性和方法,统一设置到原型对象中,就可以使每个对象都具有这些属性和方法:

      构造函数名.prototype.属性名/方法名 = ...

    4. 原型对象也是对象,因此它也有原型对象。

    JS原型、原型链以及继承_gzzzzzzzz的博客-CSDN博客_原型链以及继承

    1. 当访问对象的一个属性或方法时,会先在对象自身中寻找,如果有则直接使用,如果没有则去原型对象中寻找,如果还没有就去原型的原型中寻找,直到找到Object对象的原型对象。

    2. 判断对象中是否有某个属性/方法

      "属性/方法名" in 对象名:若对象或原型函数中有该属性/方法,则返回true

      对象名.hasOwnProperty("属性/方法名"):若对象自身中有该属性/方法,则返回true

  • 闭包

    1. 闭包的条件

      函数嵌套函数

      内部函数使用外部函数的变量

      调用外部函数

    2. 闭包到底是什么

      通俗的理解:函数内部嵌套的函数

      浏览器查看后理解:内部函数的作用域中,包含引用外部函数变量的一个对象

    3. 闭包的作用

      延长了局部变量的生命周期

      在外部控制局部变量

    4. 闭包的缺点

      局部变量会长期驻留在内存中,可能会造成内存泄漏(IE9以下)

    5. 解决闭包带来的缺点

      减少使用闭包;及时释放。

数组

  1. 概念

    ​ 数组是一个值的集合,每个值都有一个索引号,从0开始,每个索引都有一个相应的值。

  2. 创建数组

    var 数组名 = new Array(数组长度);

    ​ 创建的新数组是空数组,没有值,输出时显示为undefined

    ​ 虽然创建数组时,指定了长度,但实际上数组都是变长的,也就是说即使指定了长度,仍然可以将元素存储在规定长度以外。

  3. 数组赋值

    数组名[数组长度] = 数据;

    var 数组名 = new Array(所有数据);

    var 数组名 = [所有数据];

  4. 添加元素数组名[下一个未用的索引] = 数据;

  5. 使用数组元素数组名[索引]

  6. 数组长度数组名.length

  7. arguments

    在调用函数时,浏览器每次都会传递进两个隐含的参数:函数的上下文对象this、封装实参的对象arguments。

    在调用函数时,传递的实参都会在arguments中保存。

    arguments是一个类数组对象,可以通过索引来操作数据,也可以获取长度。

    arguments有一个属性叫做callee,这个属性指向当前正在执行的函数对象。

    function 函数名() {
    	arguments.callee == 函数名;
    }
    

判断语句

  1. if 语句

    if(条件)
    { 条件成立时执行的代码 }
    else
    { 条件不成立时执行的代码 }
    
  2. 三目运算条件?真语句:假语句

  3. switch语句

    switch(变量名){
        case1:
            条件成立时执行的代码
            break; //退出switch循环
        case2:
            条件成立时执行的代码
            break;
        case3:
            条件成立时执行的代码
            break;
        default:
            条件均不成立时执行的代码
            break;
    }
    

循环语句

  1. while语句

    while(条件表达式) {
        循环体;
    }
    
  2. do while语句

    do {
        循环体;
    }while (条件表达式);
    
  3. for语句

    for(条件表达式) {
        循环体;
    }
    
  4. for...of...语句

    for...of语句在可迭代对象(包括 ArrayMapSetStringTypedArrayarguments对象等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。

    for (变量名 of 可迭代对象) {
        //在每次迭代中,将不同属性的值分配给变量。
        //statements
    }
    
  5. for...in...语句(仅迭代自身的属性)

    for...in语句以任意顺序迭代一个对象的非Symbol类型的可枚举属性,包括继承的可枚举属性。常用于检查属性值。

    for (变量名 in 可迭代对象) {
        //在每次迭代中,将不同属性的值分配给变量。
        //statements
    }
    
  6. 跳出循环

    • break语句:终止整个循环
    • continue语句:阻止某次循环的执行

String字符串对象

  1. 定义字符串对象

    var 字符串对象名 = '字符串内容';

  2. 访问字符串对象的属性length

    字符串对象名.length

  3. 访问字符串对象的方法

    toUpperCase():将字符串小写字母转换为大写

    字符串对象名.toUpperCase()

    charAt():返回指定位置的字符。返回的字符是长度为1的字符串。

    字符串对象名.charAt(index)

    indexOf():返回指定的字符串首次出现的位置

    字符串对象名.indexOf(字符串值, 字符串中开始检索的位置)

    如果省略字符串中开始检索的位置参数,则默认从头开始检索。

    split():将字符串分割为字符串数组,并返回此数组。

    字符串对象名.split(分割的位置,分割的次数)

    substring():提取字符串中介于两个指定下标之间的字符。

    字符串对象名.substring(开始位置,结束位置)

    如果省略结束位置的参数,则默认为结尾。

    substr():提取指定数目的字符

    字符串对象名.substr(开始位置,提取字符串的长度)

Array数组对象

  1. 数组定义的方法

    定义一个空数组:var 数组名= new Array();

    定义时指定有n个空元素的数组:var 数组名 =new Array(n);

    定义数组的时候,直接初始化数据:var 数组名 = [元素1, 元素2, 元素3, ...];

  2. 数组元素使用

    数组名[下标] = 值;

  3. length属性

    用法:数组对象名.length

    返回:数组的长度,即数组里有多少个元素

  4. 数组方法

    • 增加

      push():把元素依次添加到数组的末尾,并返回添加元素后的数组长度

      数组对象名.push(新元素1, 新元素2, ...)

      unshift():把元素依次添加到数组的前面,并返回添加元素后的数组长度

      数组对象名.unshift(新元素1, 新元素2, ...)

    • 删除

      pop():删除数组最后一个元素,并返回删除的元素

      数组对象名.pop()

      shift():删除数组第一个元素,并返回删除的元素

      数组对象名.shift()

      splice():删除指定数量的元素、替换指定元素、在指定位置添加元素

      数组对象名.splice(开始删除位置的索引, 删除的数量, 添加的元素1, 添加的元素2, ...)

      delete 数组名[要删除的元素的索引]

      注意】delete这种方式数组长度不变,此时删除的元素变为undefined了,但是也有好处原来数组的索引也保持不变,此时要遍历数组元素可以才用。

    • 排序

      reverse():颠倒数组元素顺序

      数组对象名.reverse()

      sort():使数组中的元素按照一定的顺序排列

      数组对象名.sort(函数名)

    • 拼接

      concat():连接两个或多个数组

      数组对象名.concat(要连接的第一个数组, 第一个数组, ..., 第N个数组)

      join():指定分隔符连接数组元素

      数组对象名.join(分隔符)

      如果省略分隔符的参数,则默认使用逗号作为分隔符

    • 选择

      slice():选定数组中的元素

      数组对象名.slice(开始位置, 结束位置)

    • filter测试是否满足条件

      filter(callbackFn, thisArg)
      

      callbackFn:用来测试数组中每个元素的回调函数。返回true表示该元素通过测试,保留该元素;false则不保留。它接受以下三个参数: element:数组中当前正在处理的元素 index:正在处理的元素在数组中的索引 array:调用了filter()的数组本身

      thisArg:可选,执行callbackFn时用于this的值

      返回值:一个新的、由通过测试的元素组成的数组。如果没有任何数组元素通过测试,则返回空数组。

JavaScript编码原则

各司其责

  1. HTML 负责内容、CSS 负责样式、JS 负责交互行为。

  2. 避免由 JS 直接操作样式,可以通过修改元素的 className 来更改样式。

  3. 纯展示类交互寻求零 JS 方案。

    例如:实现点击按钮切换样式的功能,可以使用以下方式。

    <body>
    	<input id="modeCheckBox" type="checkbox ">
        <div class="content">
            <header>
        		<label id="modeBtn" for="modeCheckBox"></label>
    		    <h1>标题</h1>
            </header>
            <main>
                正文...
            </main>
    	</div>
    </body>
    
    .content {
        transition: background-color 1s, color 1s;
    }
    #modeCheckBox{
        display: none;
    }
    #modeCheckBox:checked + .content {
        background-color: black;
        color: white;
        transition: all 1s;
    }
    

组件封装

组件是指Web页面上抽出来一个个包含模版(HTML)、样式(CSS)和功能(JS)的单元。在进行组件封装时,将HTML解耦进行模板化,将JavaScript解耦进行插件化,将通用的组件模型抽象出来形成组件框架,增加组件的复用性灵活性扩展性

组件设计的原则:封装性、正确性、扩展性、复用性

实现组件的步骤:结构设计、展示效果、行为设计

三次重构:插件化、模板化、抽象化(组件框架)

轮播图案例

<div id="my-slider" class="slider-list"></div>
#my-slider{
  position: relative;
  width: 790px;
  height: 340px;
}

.slider-list ul{
  list-style-type:none;
  position: relative;
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
}

.slider-list__item,
.slider-list__item--selected{
  position: absolute;
  transition: opacity 1s;
  opacity: 0;
  text-align: center;
}

.slider-list__item--selected{
  transition: opacity 1s;
  opacity: 1;
}

.slide-list__control{
  position: relative;
  display: table;
  background-color: rgba(255, 255, 255, 0.5);
  padding: 5px;
  border-radius: 12px;
  bottom: 30px;
  margin: auto;
}

.slide-list__next,
.slide-list__previous{
  display: inline-block;
  position: absolute;
  top: 50%;
  margin-top: -25px;
  width: 30px;
  height:50px;
  text-align: center;
  font-size: 24px;
  line-height: 50px;
  overflow: hidden;
  border: none;
  background: transparent;
  color: white;
  background: rgba(0,0,0,0.2);
  cursor: pointer;
  opacity: 0;
  transition: opacity .5s;
}

.slide-list__previous {
  left: 0;
}

.slide-list__next {
  right: 0;
}

#my-slider:hover .slide-list__previous {
  opacity: 1;
}


#my-slider:hover .slide-list__next {
  opacity: 1;
}

.slide-list__previous:after {
  content: '<';
}

.slide-list__next:after {
  content: '>';
}

.slide-list__control-buttons,
.slide-list__control-buttons--selected{
  display: inline-block;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  margin: 0 5px;
  background-color: white;
  cursor: pointer;
}

.slide-list__control-buttons--selected {
  background-color: red;
}
class Component{
  constructor(id, opts = {name, data:[]}){
    this.container = document.getElementById(id);
    this.options = opts;
    this.container.innerHTML = this.render(opts.data);
  }
  registerPlugins(...plugins){
    plugins.forEach(plugin => {
      const pluginContainer = document.createElement('div');
      pluginContainer.className = `.${name}__plugin`;
      pluginContainer.innerHTML = plugin.render(this.options.data);
      this.container.appendChild(pluginContainer);
      
      plugin.action(this);
    });
  }
  render(data) {
    /* abstract */
    return ''
  }
}

class Slider extends Component{
  constructor(id, opts = {name: 'slider-list', data:[], cycle: 3000}){
    super(id, opts);
    this.items = this.container.querySelectorAll('.slider-list__item, .slider-list__item--selected');
    this.cycle = opts.cycle || 3000;
    this.slideTo(0);
  }
  render(data){
    const content = data.map(image => `
      <li class="slider-list__item">
        <img src="${image}"/>
      </li>    
    `.trim());
    
    return `<ul>${content.join('')}</ul>`;
  }
  getSelectedItem(){
    const selected = this.container.querySelector('.slider-list__item--selected');
    return selected
  }
  getSelectedItemIndex(){
    return Array.from(this.items).indexOf(this.getSelectedItem());
  }
  slideTo(idx){
    const selected = this.getSelectedItem();
    if(selected){ 
      selected.className = 'slider-list__item';
    }
    const item = this.items[idx];
    if(item){
      item.className = 'slider-list__item--selected';
    }

    const detail = {index: idx}
    const event = new CustomEvent('slide', {bubbles:true, detail})
    this.container.dispatchEvent(event)
  }
  slideNext(){
    const currentIdx = this.getSelectedItemIndex();
    const nextIdx = (currentIdx + 1) % this.items.length;
    this.slideTo(nextIdx);
  }
  slidePrevious(){
    const currentIdx = this.getSelectedItemIndex();
    const previousIdx = (this.items.length + currentIdx - 1) % this.items.length;
    this.slideTo(previousIdx);  
  }
  addEventListener(type, handler){
    this.container.addEventListener(type, handler);
  }
  start(){
    this.stop();
    this._timer = setInterval(()=>this.slideNext(), this.cycle);
  }
  stop(){
    clearInterval(this._timer);
  }
}

const pluginController = {
  render(images){
    return `
      <div class="slide-list__control">
        ${images.map((image, i) => `
            <span class="slide-list__control-buttons${i===0?'--selected':''}"></span>
         `).join('')}
      </div>    
    `.trim();
  },
  action(slider){
    let controller = slider.container.querySelector('.slide-list__control');
    
    if(controller){
      let buttons = controller.querySelectorAll('.slide-list__control-buttons, .slide-list__control-buttons--selected');
      controller.addEventListener('mouseover', evt=>{
        var idx = Array.from(buttons).indexOf(evt.target);
        if(idx >= 0){
          slider.slideTo(idx);
          slider.stop();
        }
      });

      controller.addEventListener('mouseout', evt=>{
        slider.start();
      });

      slider.addEventListener('slide', evt => {
        const idx = evt.detail.index;
        let selected = controller.querySelector('.slide-list__control-buttons--selected');
        if(selected) selected.className = 'slide-list__control-buttons';
        buttons[idx].className = 'slide-list__control-buttons--selected';
      });
    }    
  }
};

const pluginPrevious = {
  render(){
    return `<a class="slide-list__previous"></a>`;
  },
  action(slider){
    let previous = slider.container.querySelector('.slide-list__previous');
    if(previous){
      previous.addEventListener('click', evt => {
        slider.stop();
        slider.slidePrevious();
        slider.start();
        evt.preventDefault();
      });
    }  
  }
};

const pluginNext = {
  render(){
    return `<a class="slide-list__next"></a>`;
  },
  action(slider){
    let previous = slider.container.querySelector('.slide-list__next');
    if(previous){
      previous.addEventListener('click', evt => {
        slider.stop();
        slider.slideNext();
        slider.start();
        evt.preventDefault();
      });
    }  
  }
};

const slider = new Slider('my-slider', {name: 'slide-list', data: ['https://p5.ssl.qhimg.com/t0119c74624763dd070.png',
     'https://p4.ssl.qhimg.com/t01adbe3351db853eb3.jpg',
     'https://p2.ssl.qhimg.com/t01645cd5ba0c3b60cb.jpg',
     'https://p4.ssl.qhimg.com/t01331ac159b58f5478.jpg'], cycle:3000});

slider.registerPlugins(pluginController, pluginPrevious, pluginNext);
slider.start();

过程抽象

  • 过程抽象:用来处理局部细节控制的一些方法、函数式编程思想的基础应用

  • 纯函数:相同的输入会导致相同的输出、不产生副作用、不依赖外部状态,要尽量使用纯函数来提高代码的可维护性。

  • 高阶函数HOF :以函数作为参数、以函数作为返回值、常用于作为函数装饰器,使用高阶函数可减少非纯函数的使用率。

    function HOF(fn) {
        return function(...args) {
      	return fn.apply(this, args);
        }
    }
    
    1. Once

      为了能够让“只执行一次”的需求(一次性的异步交互)覆盖不同的事件处理,我们可以将这个需求剥离出来。这个过程我们称为过程抽象

      function once(fn) {
         return function(...args) {
            if(fn) {
                const ret = fn.apply(this, args);
              fn = null;
                return ret;
            }
         }
      }
      
    2. Throttle

      函数防抖节流:限制一个函数在一定时间内只能执行一次。

      “非立即执行版” 函数防抖:触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。

      function throttle(fn, time = 500) {
        let timer; //刚开始没有设置timer,因此timer初始值为null
        return function(...args) {
          if(timer == null) { 
            fn.apply(this, args); //调用函数fn
            timer = setTimeout(() => {
              timer = null;
            }, time);
          }
        }
      }
      
    3. Debounce

      “立即执行版”函数防抖:触发事件后函数不会立即执行,而是在 n 秒后执行。

      function debounce(fn, dur) {
        dur = dur || 100;
        var timer;
        return function() {
          clearTimeout(timer);
          timer = setTimeout(() => {
            fn.apply(this, arguments);
          }, dur);
        }
      }
      
    4. Consumer

      延时调用函数。

      function consumer(fn, time) {
        let tasks = [], timer;
        return function(...args) {
          tasks.push(fn.bind(this, ...args));
          if(timer == null){
            timer = setInterval(() => {
              tasks.shift().call(this);
              if(tasks.length <= 0) {
                clearInterval(timer);
                timer = null;
              }
            }, time);
          }
        }
      }
      
    5. Iterative

      迭代操作可迭代数据中的多个元素。

      const isIterable = obj => obj != null 
        && typeof obj[Symbol.iterator] === 'function'; //判断数据是否可迭代
      
      function iterative(fn) {
        return function(subject, ...rest) {
          if(isIterable(subject)) {
            const ret = [];
            for(let obj of subject) { //迭代
              ret.push(fn.apply(this, [obj, ...rest]));
            }
            return ret;
          }
          return fn.apply(this, [subject, ...rest]);
        }
      }
      

代码优化

写代码最应该关注的是代码的使用场景。