谈谈我们知识体系(js篇)

扶醉桌前 提交于 2020-08-05 07:50:09

前言

我是小白,励志要做技术男神的帅逼,目前住在南京,做了快3年前端工程师,会继续写笔记的。

js

string对象的一些常用方法

  • 普通方法
    • charAt() 方法返回字符串中指定位置的字符。
    • split // 通过把字符串分割成子字符串来把一个 String 对象分割成一个字符串数组。
    • slice // 提取字符串中的一部分,并返回这个新的字符串,获取索引号为1,2,3的字符串,即[1, 4)
    • substr(start, length) // 方法返回字符串中从指定位置开始到指定长度的子字符串
    • substring(start, end) // 返回字符串两个索引之间(或到字符串末尾)的子串。
    • trim() // 删除一个字符串两端的空白字符
    • toLowerCase() // 将调用该方法的字符串值转为小写形式,并返回。
    • toUpperCase() // 将字符串转换成大写并返回。
  • 正则方法
    • search // 执行一个查找,看该字符串对象与一个正则表达式是否匹配。
    • replace // 被用来在正则表达式和字符串直接比较,然后用新的子串来替换被匹配的子串。
    • match // 当字符串匹配到正则表达式(regular expression)时,match() 方法会提取匹配项。

Array对象的一些常用方法

  • 不改变原数组的方法
    • concat() // 连接两个或多个数组
    • join() // 把数组中的所有元素放入一个字符串中
    • slice(start, end) // 返回一个新的数组,包含从 start 到 end (不包括该元素)的 arrayObject 中的元素
    • toString() // 把一个逻辑值转换为字符串,并返回结果
  • 改变原数组
    • pop() // 用于删除并返回数组的最后一个元素
    • push() // 想数组的末尾添加一个或多个元素
    • shift() // 把数组的第一个元素从其中删除
    • unshift() // 向数组的开头添加一个或多个元素
    • splice() // 向数组添加项目或者从数组中删除项目
    • reverse() // 用于颠倒数组中的元素的顺序
    • sort() // 排序
  • 其他
    • forEach 无法break,可以用try/catch中throw new Error来停止
    • map: 遍历数组,返回回调返回值组成的新数组
    • filter: 过滤
    • some: 有一项返回true,则整体为true
    • every: 有一项返回false,则整体为false
    • indexOf / lastIndexOf(value, fromIndex): 查找数组项,返回对应的下标
    • reduce / reduceRight(fn(prev, cur), defaultPrev): 两两执行,prev 为上次化简函数的return值,cur 为当前值(从第二项开始)
  • 数组乱序:
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arr.sort(function () {
    return Math.random() - 0.5;
});
复制代码
  • 数组拆解: flat: [1,[2,3]] --> [1, 2, 3]
Array.prototype.flat = function() {
    return this.toString().split(',').map(item => +item )
}
复制代码

for in for of forEach map的区别

  • for…in
    • 遍历数组时不一定会按照数组的索引顺序。
for(let pro in arr){
    console.log(pro+':' + arr[pro])
}
//0:123
//1:abc
复制代码
  • for…of
    • 语句在可迭代对象(Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,为每个不同属性的值执行语句。
let iterable = new Map([["a", 1], ["b", 2], ["c", 3]]);

for (let [key,value] of iterable) {
  console.log(key+':'+value);
}
//a:1
//b:2
//c:3
复制代码
  • forEach
    • forEach方法对数组/Map/Set中的每个元素执行一次提供的函数。该函数接受三个参数:
      • 正在处理的当前元素,对于Map元素,代表其值;
      • 正在处理的当前元素的索引,对于Map元素,代表其键,对于Set而言,索引与值一样。
      • forEach()方法正在操作的数组对象。
let arr = [1,2,3,4]
arr.forEach(function(value,index,currentArr){
    currentArr[index]=value + 1
})
console.log(arr)//[ 2, 3, 4, 5 ]
复制代码
  • map
    • map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。该函数接受的三个参数为:
      • 当前元素
      • 当前索引
      • 当前被调用的数组
var numbers = [1, 4, 9];
var roots = numbers.map(Math.sqrt);
// roots的值为[1, 2, 3], numbers的值仍为[1, 4, 9]
复制代码

深浅拷贝

  • 浅拷贝: 以赋值的形式拷贝引用对象,仍指向同一个地址,修改时原对象也会受到影响
    • Object.assign
    • 展开运算符(...)
  • 深拷贝: 完全拷贝一个新对象,修改时原对象不再受到任何影响
    • JSON.parse(JSON.stringify(obj)): 性能最快
      • 具有循环引用的对象时,报错
      • 当值为函数、undefined、或symbol时,无法拷贝
    • 递归进行逐一赋值
//使用递归的方式实现数组、对象的深拷贝
function deepClone1(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] = deepClone1(obj[key]);
        } else {
          objClone[key] = obj[key];
        }
      }
    }
  }
  return objClone;
}
//通过js的内置对象JSON来进行数组对象的深拷贝
function deepClone2(obj) {
  var _obj = JSON.stringify(obj),
    objClone = JSON.parse(_obj);
  return objClone;
}

复制代码

运算符优先级

最上面的优先级最高:

// 这里运用到了 堆栈 及 运算符优先级知识点
var a = {n:1};
var b = a;
a.x = a = {n:2};
=============================
问:
console.log(a.x)= undefined
console.log(b.x)= {n: 2}
复制代码

原型 / 构造函数 / 实例 / 原型链(Object.prototype.__proto__)

  • 原型(prototype):一个简单的对象,用于实现对象的 属性继承。可以简单的理解成对象的爹。在 Firefox 和 Chrome中,每个JavaScript对象中都包含一个__proto__(非标准)的属性指向它爹(该对象的原型),可obj.__proto__进行访问。

  • 构造函数: 可以通过new来 新建一个对象 的函数。

  • 实例: 通过构造函数和new创建出来的对象,便是实例。 实例通过__proto__指向原型,通过constructor指向构造函数。

  • 原型链是由原型对象组成,每个对象都有 __proto__属性,指向了创建该对象的构造函数的原型,__proto__将对象连接起来组成了原型链。是一个用来实现继承和共享属性的有限的对象链。

const instance = new Object()
则此时, 实例为instance, 构造函数为Object,构造函数拥有一个prototype的属性指向原型,因此原型为:
// 原型
const prototype = Object.prototype

实例.__proto__ === 原型

原型.constructor === 构造函数

构造函数.prototype === 原型

// 这条线其实是是基于原型进行获取的,可以理解成一条基于原型的映射线
// 例如: 
// const o = new Object()
// o.constructor === Object   --> true
// o.__proto__ = null;
// o.constructor === Object   --> false
实例.constructor === 构造函数
复制代码

执行上下文 / 变量对象 / 作用域 / 作用域链 / 变量提升

这个知识点我在浅谈页面从url到页面加载完成(第二篇笔记)中有写过可以只看这四点知识 看完后 可以 看一下 小标题中的 上述结论 我在里面有详细用代码的方式解释(包含变量提升知识点)

闭包

闭包函数:声明在一个函数中的函数,叫做闭包函数。(注意: 这里就涉及到 垃圾回收机制 及 执行上下文,也可以看看浅谈页面从url到页面加载完成(第二篇笔记)里有这两点知识)

[[scope]]属性: 指向父级变量对象(AO)和作用域链,也就是包含了父级的[[scope]] 和 变量对象(AO)

闭包属于一种特殊的作用域,称为 静态作用域。它的定义可以理解为: 父函数被销毁 的情况下,返回出的子函数的[[scope]]中仍然保留着父级的单变量对象和作用域链,因此可以继续访问到父级的变量对象,这样的函数称为闭包。

  • 闭包会产生一个很经典的问题:

    • 多个子函数的[[scope]]都是同时指向父级,是完全共享的。因此当父级的变量对象被修改时,所有子函数都受到影响。
  • 解决:

    • 变量可以通过 函数参数的形式 传入,避免使用默认的[[scope]]向上查找
    • 使用setTimeout包裹,通过第三个参数传入
    • 使用 块级作用域,让变量成为自己上下文的属性,避免共享
  • 特点

    • 让外部访问函数内部变量成为可能;
    • 局部变量会常驻在内存中;
    • 可以避免使用全局变量,防止全局变量污染;
    • 会造成内存泄漏(有一块内存空间被长期占用,而不被释放)
  • 闭包:内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后。

  • 闭包的创建:

    • 闭包就是可以创建一个独立的环境,每个闭包里面的环境都是独立的,互不干扰。闭包会发生内存泄漏,每次外部函数执行的时 候,外部函数的引用地址不同,都会重新创建一个新的地址。但凡是当前活动对象中有被内部子集引用的数据,那么这个时候,这个数据不删除,保留一根指针给内部活动对象
// 这就是闭包函数(闭包)
function funA(){
    function funB (){
    }
}

<!--例子-->
function funA(){
  var a = 10;  // funA的活动对象之中;
  return function(){   //匿名函数的活动对象;
        alert(a);
  }
}
var b = funA();
b();  //10

<!--例子-->
function outerFn(){
  var i = 0; 
  function innerFn(){
      i++;
      console.log(i);
  }
  return innerFn;
}
var inner = outerFn();  //每次外部函数执行的时候,都会开辟一块内存空间,外部函数的地址不同,都会重新创建一个新的地址
inner();
inner();
inner();
var inner2 = outerFn();
inner2();
inner2();
inner2(); // 答案: 1 2 3 1 2 3
<!--例子-->
var i = 0;
function outerFn(){
  function innnerFn(){
       i++;
       console.log(i);
  }
  return innnerFn;
}
var inner1 = outerFn();
var inner2 = outerFn();
inner1();
inner2();
inner1();
inner2(); // 答案: 1 2 3 4
复制代码

script 引入方式

  • html 静态<script>引入
  • js 动态插入<script>
  • <script defer>:延迟加载,元素解析完成后执行
  • <script async>:异步加载,但执行时会阻塞元素渲染

DOM事件

  • 事件级别:
  1. DOM 0级: 写法:el.οnclick=function(){}

当希望为同一个元素/标签绑定多个同类型事件的时候(如给上面的这个btn元素绑定3个点击事件),是不被允许的。DOM0事件绑定,给元素的事件行为绑定方法,这些方法都是在当前元素事件行为的冒泡阶段(或者目标阶段)执行的。

  1. 由于DOM 1级中没有事件的相关内容,所以没有DOM 1级事件
  2. DOM 2级 写法:el.addEventListener(event-name, callback, useCapture)

event-name: 事件名称,可以是标准的DOM事件

callback: 回调函数,当事件触发时,函数会被注入一个参数为当前的事件对象 event

useCapture: 默认是false,代表事件句柄在冒泡阶段执行

  1. DOM 3级 写法和DOM2级一致 只是在DOM 2级事件的基础上添加了更多的事件类型

UI事件,当用户与页面上的元素交互时触发,如:load、scroll

焦点事件,当元素获得或失去焦点时触发,如:blur、focus

鼠标事件,当用户通过鼠标在页面执行操作时触发如:dblclick、mouseup

滚轮事件,当使用鼠标滚轮或类似设备时触发,如:mousewheel

文本事件,当在文档中输入文本时触发,如:textInput

键盘事件,当用户通过键盘在页面上执行操作时触发,如:keydown、keypress

合成事件,当为IME(输入法编辑器)输入字符时触发,如:compositionstart

变动事件,当底层DOM结构发生变化时触发,如:DOMsubtreeModified

同时DOM3级事件也允许使用者自定义一些事件。

事件冒泡 / 事件捕获 / DOM事件流

  • 事件冒泡

IE的事件流叫做事件冒泡(event bubbling),即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。所有现代浏览器都支持事件冒泡,并且会将事件一直冒泡到window对象。

图片来源

  • 事件捕获

事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于在事件到达预定目标之前就捕获它。IE9+、Safari、Chrome、Opera和Firefox支持,且从window开始捕获(尽管DOM2级事件规范要求从document)。由于老版本浏览器不支持,所以很少有人使用事件捕获。

  • DOM事件流:

事件流是从页面接收事件的顺序,DOM2级事件规定事件流包括三个阶段:

- 事件捕获阶段:用意在于事件达到目标之前捕获它,在事件捕获阶段事件流模型:document→html→body→div(事件从window对象自上而下向目标节点传播的阶段)
- 处于目标阶段2:实际的目标到事件(真正的目标节点正在处理事件的阶段)
- 事件冒泡阶段:由最具体的元素接收到事件,然后向上传播到较为不具体的节点。事件流模型:div →body→ html→ document(事件从目标节点自下而上向window对象传播的阶段)
复制代码

图片来源

  • 事件委托(事件代理)

事件委托顾名思义:将事件委托给另外的元素。其实就是利用DOM的事件冒泡原理,将事件绑定到目标元素的父节点。

  • 优点:

    1. 减少内存消耗,提高性能(不需要为每一个子元素绑定事件)
    2. 动态绑定事件

如果要为大量的元素节点绑定事件,完美可以用事件委托完美解决,直接将事件绑定在这些元素的父节点上,只需要绑定一次,就可以在所有子节点触发事件。

事件冒泡的过程也需要耗时,越靠近顶层,事件的”事件传播链”越长,也就越耗时。如果DOM嵌套结构很深,事件冒泡通过大量祖先元素会导致性能损失。基于这点 看下个知识点 就可以优化这个

  • data-*
    • 属性用于存储页面或应用程序的私有自定义数据。
    • 可以用于 事件委托的优化

JS运行机制

JS为啥是单线程

js作为浏览器脚本语言,其主要用途是与用户互动,以及操作DOM。这就决定了它只能是单线程,否则会带来很复杂的同步问题。(假设JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?)

JS同步任务和异步任务

JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。如果前一个任务耗时很长,后一个任务就不得不一直等着。JavaScript语言的设计者意识到这个问题,将所有任务分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)

  • 同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
  • 异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行
任务队列(消息队列)

任务队列中存着的是异步任务,这些异步任务一定要等到执行栈清空后才会执行。

异步任务,会先到事件列表中注册函数。如果事件列表中的事件触发了,会将这个函数移入到任务队列中(DOM操作对应DOM事件,资源加载操作对应加载事件,定时器操作可以看做对应一个“时间到了”的事件)

宏任务与微任务

  • macro-task(宏任务):包括整体代码script,setTimeout,setInterval, setImmediate, I/O, UI rendering
  • micro-task(微任务):Promise,process.nextTick,MutationObserver
  • 微任务意义:

减少更新时的渲染次数 因为根据HTML标准,会在宏任务执行结束之后,在下一个宏任务开始执行之前,UI都会重新渲染。如果在microtask中就完成数据更新,当 macro-task结束就可以得到最新的UI了。如果新建一个 macro-task来做数据更新的话,那么渲染会执行两次

Event Loop(事件循环)

  1. 整体的script(作为第一个宏任务)开始执行的时候,会把所有代码分为两部分:“同步任务”、“异步任务”;
  2. 同步任务会直接进入主线程依次执行;
  3. 异步任务会再分为宏任务和微任务;
  4. 宏任务进入到Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到Event Queue中;
  5. 微任务也会进入到另一个Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到Event Queue中;
  6. 当主线程内的任务执行完毕,主线程为空时,会检查微任务的Event Queue,如果有任务,就全部执行,如果没有就执行下一个宏任务;
  7. 上述过程会不断重复,这就是Event Loop事件循环;
一图总结(事件循环、执行栈、任务队列、宏任务、微任务)

经典面试题
console.log(1);

setTimeout(()=>{
    console.log(2);   
    new Promise((resolve,reject)=>{
    console.log(3);
    resolve()
}).then(res=>{
    console.log(4); 
})
})

new Promise((resolve,reject)=>{
    resolve()
}).then(res=>{
    console.log(5); 
}).then(res=>{
    console.log(6);
    
})

new Promise((resolve,reject)=>{
    console.log(7);
    resolve()
}).then(res=>{
    console.log(8); 
}).then(res=>{
    console.log(9);
    
})

setTimeout(()=>{
    console.log(10);   
    new Promise((resolve,reject)=>{
    console.log(11);
    resolve()
}).then(res=>{
    console.log(12); 
})
})

console.log(13);
复制代码

结果:

Event对象使用

  1. 阻止默认行为:event. preventDefault() 什么是默认事件呢?例如表单一点击提交按钮(submit)跳转页面、a标签默认页面跳转或是锚点定位等
  2. 阻止冒泡:event.stopPropagation()
    • 方法阻止事件冒泡到父元素,阻止任何父事件处理程序被执行
    • stopImmediatePropagation 既能阻止事件向父元素冒泡,也能阻止元素同事件类型的其它监听器被触发
  • event.currentTarget始终是监听事件者
  • event.target是事件的真正发出者

call()与apply()

改变this指向,意义: 实现(多重)继承

可以理解为 用 xxx的一个方法 替换 xxx的一个方法。

  • call():第一个参数是this值没有变化,变化的是其余参数都直接传递给函数。在使用call()方法时,传递给函数的参数必须逐个列举出来。

  • apply():传递给函数的是参数数组

1.
function add(a,b)  
{  
    alert(a+b);  
}  
function sub(a,b)  
{  
    alert(a-b);  
}  
  
add.call(sub,3,1)
// 用 add 来替换 sub,add.call(sub,3,1) == add(3,1) ,所以运行结果为:alert(4);
2.
function Animal(name){      
    this.name = name;      
    this.showName = function(){      
        alert(this.name);      
    }      
}      
    
function Cat(name){    
    Animal.call(this, name);    
}      
    
var cat = new Cat("Black Cat");     
cat.showName();  
// Animal.call(this) 的意思就是使用 Animal对象代替this对象,那么 Cat中不就有Animal的所有属性和方法了吗,Cat对象就能够直接调用Animal的方法以及属性了. 

复制代码

手写EventEmitter(发布订阅模式--简单版)

    // 手写发布订阅模式 EventEmitter
    class EventEmitter {
        constructor() {
          this.events = {};
        }
        // 实现订阅
        on(type, callBack) {
          if (!this.events) this.events = Object.create(null);

          if (!this.events[type]) {
            this.events[type] = [callBack];
          } else {
            this.events[type].push(callBack);
          }
        }
        // 删除订阅
        off(type, callBack) {
          if (!this.events[type]) return;
          this.events[type] = this.events[type].filter(item => {
            return item !== callBack;
          });
        }
        // 只执行一次订阅事件
        once(type, callBack) {
          function fn() {
            callBack();
            this.off(type, fn);
          }
          this.on(type, fn);
        }
        // 触发事件
        emit(type, ...rest) {
          this.events[type] &&
            this.events[type].forEach(fn => fn.apply(this, rest));
        }
    }
    // 使用如下
    const event = new EventEmitter();

    const handle = (...rest) => {
        console.log(rest);
    };

    event.on("click", handle);
    
    event.emit("click", 1, 2, 3, 4);
    
    event.off("click", handle);
    
    event.emit("click", 1, 2);
    
    event.once("dbClick", () => {
    console.log(123456);
    });
    event.emit("dbClick");
    event.emit("dbClick");
复制代码

js数据类型

这个知识点我在浅谈页面从url到页面加载完成(第二篇笔记)中有写过可以只看这点知识

new运算符的执行过程

  • 新生成一个对象
  • 链接到原型: obj.proto = Con.prototype
  • 绑定this: apply
  • 返回新对象(如果构造函数有自己retrun时,则返回该值)
function _new(func) {
    // 第一步 创建新对象
    let obj= {}; 
    // 第二步 空对象的_proto_指向了构造函数的prototype成员对象
    obj.__proto__ = func.prototype;//
    // 一二步合并就相当于 let obj=Object.create(func.prototype)

    // 第三步 使用apply调用构造器函数,属性和方法被添加到 this 引用的对象中
    let result = func.apply(obj);
    if (result && (typeof (result) == "object" || typeof (result) == "function")) {
    // 如果构造函数执行的结果返回的是一个对象,那么返回这个对象
        return result;
    }
    // 如果构造函数返回的不是一个对象,返回创建的新对象
    return obj;
}
复制代码

创建对象方式:

// 1.对象字面量
let a={name:'xxx'}

// 2.构造函数
function Person(name){
    this.name=name
}
let b=new Person('xxx')

// 3.Object.create(proto, [propertiesObject])
// Object.create()方法创建的对象时,属性是在原型下面的
let c=Object.create({name:'xxx'})
复制代码

instanceof原理

能在实例的 原型对象链中找到该构造函数的prototype属性所指向的 原型对象,就返回true。即:

// __proto__: 代表原型对象链
instance.[__proto__...] === instance.constructor.prototype

// return true
复制代码

代码的复用

当你发现任何代码开始写第二遍时,就要开始考虑如何复用。一般有以下的方式:

  • 函数封装
  • 继承
  • 复制extend
  • 混入mixin
  • 借用apply/call

继承

在 JS 中,继承通常指的便是 原型链继承,也就是通过指定原型,并可以通过原型链继承原型上的属性或者方法。

  • 最优化: 圣杯模式
var inherit = (function(c,p){
	var F = function(){};
	return function(c,p){
		F.prototype = p.prototype;
		c.prototype = new F();
		c.uber = p.prototype;
		c.prototype.constructor = c;
	}
})();
复制代码
  • 使用 ES6 的语法糖 class / extends 使用extends表明继承自哪个父类,并且在子类构造函数中必须调用super
class Son extends Father {
  constructor(name) {
    super(name);
    this.name = name || "son";
  }
}
复制代码

类型判断

判断 Target 的类型,单单用 typeof 并无法完全满足,这其实并不是 bug,本质原因是 JS 的万物皆对象的理论。因此要真正完美判断时,我们需要区分对待:

  • 基本类型(null): 使用 String(null)
  • 基本类型(string / number / boolean / undefined) + function: 直接使用 typeof即可
  • 其余引用类型(Array / Date / RegExp Error): 调用toString后根据[object XXX]进行判断
<!--很稳健的类型判断封装 -->
let class2type = {}
'Array Date RegExp Object Error'.split(' ').forEach(e => class2type[ '[object ' + e + ']' ] = e.toLowerCase()) 

function type(obj) {
    if (obj == null) return String(obj)
    return typeof obj === 'object' ? class2type[ Object.prototype.toString.call(obj) ] || 'object' : typeof obj
}
复制代码

模块化

模块化开发在现代开发中已是必不可少的一部分,它大大提高了项目的可维护、可拓展和可协作性。通常,我们 在浏览器中使用 ES6 的模块化支持,在 Node 中使用 commonjs 的模块化支持

  • 分类:

    • es6: import / export
    • commonjs: require / module.exports / exports
    • amd: require / defined
  • require与import的区别

    • require支持动态导入,import不支持,正在提案 (babel 下可支持)
    • require是 同步 导入,import属于 异步 导入
    • require是值拷贝,导出值变化不会影响导入值;import指向内存地址,导入值会随导出值而变化

防抖与节流

防抖与节流函数是一种最常用的 高频触发优化方式,能对性能有较大的帮助。

  • 防抖 (debounce):将多次高频操作优化为只在最后一次执行,通常使用的场景是:用户输入,只需再输入完成后做一次输入校验即可。
function debounce(fn, wait, immediate) {
    let timer = null

    return function() {
        let args = arguments
        let context = this

        if (immediate && !timer) {
            fn.apply(context, args)
        }

        if (timer) clearTimeout(timer)
        timer = setTimeout(() => {
            fn.apply(context, args)
        }, wait)
    }
}
复制代码
  • 节流(throttle):每隔一段时间后执行一次,也就是降低频率,将高频操作优化成低频操作,通常使用场景: 滚动条事件 或者 resize 事件,通常每隔 100~500 ms执行一次即可。
function throttle(fn, wait, immediate) {
    let timer = null
    let callNow = immediate
    
    return function() {
        let context = this,
            args = arguments

        if (callNow) {
            fn.apply(context, args)
            callNow = false
        }

        if (!timer) {
            timer = setTimeout(() => {
                fn.apply(context, args)
                timer = null
            }, wait)
        }
    }
}
复制代码

函数执行改变this

由于 JS 的设计原理:在函数中,可以引用运行环境中的变量。因此就需要一个机制来让我们可以在函数体内部获取当前的运行环境,这便是this。 因此要明白 this 指向,其实就是要搞清楚 函数的运行环境,说人话就是,谁调用了函数。例如:

  • obj.fn(),便是 obj 调用了函数,既函数中的 this === obj
  • fn(),这里可以看成 window.fn(),因此 this === window

但这种机制并不完全能满足我们的业务需求,因此提供了三种方式可以手动修改 this 的指向:

  • call: fn.call(target, 1, 2)
  • apply: fn.apply(target, [1, 2])
  • bind: fn.bind(target)(1,2)

ES6/ES7

  • 声明

    • let / const: 块级作用域、不存在变量提升、暂时性死区、不允许重复声明
    • const: 声明常量,无法修改
  • 解构赋值

    • class / extend: 类声明与继承
    • Set / Map: 新的数据结构
  • 异步解决方案:

    • Promise的使用与实现
    • generator:
      • yield: 暂停代码
      • next(): 继续执行代码
          function* helloWorld() {
            yield 'hello';
            yield 'world';
            return 'ending';
          }
          const generator = helloWorld();
          
          generator.next()  // { value: 'hello', done: false }
          generator.next()  // { value: 'world', done: false }
          generator.next()  // { value: 'ending', done: true }
          generator.next()  // { value: undefined, done: true }
          
      复制代码
    • await / async: 是generator的语法糖, babel中是基于promise实现。
    async function getUserByAsync(){
       let user = await fetchUser();
       return user;
    }
    
    const user = await getUserByAsync()
    console.log(user)
    复制代码

文章中部分内容整理自

结语

以上是我的第一篇笔记,本人前端小白一只,如果写的不好请各位大佬指正,俺想再学习到更深知识,希望和各位大佬交朋友,希望我的笔记对您提供舒适的观看体验。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!