前端知识——js部分

js

instanceof 和typeof

浅谈 instanceof 和 typeof 的实现原理
typeof:根据机器码判断

000:对象
010:浮点数
100:字符串
110:布尔
1:整数

but, 对于 undefined 和 null 来说,这两个值的信息存储是有点特殊的。
null:所有机器码均为0
undefined:用 −2^30 整数来表示
所以,typeof 在判断 null 的时候就出现问题了,由于 null 的所有机器码均为0,因此直接被当做了对象来看待。

instanceof原理:
找原型链;
但是判断不出数组和对象
用 Object.prototype.toString.call()最好。

基本数据类型,引用数据类型,区别

基本数据类型String,Null,Undefined,Number,Boolean,Symbol,BigInt
引用数据类型 Object(包括function,array,Date等,除了基本数据类型以外的都是对象),
基本数据类型:存在栈中,按值存放,按值访问。
引用数据类型:保存在堆中,栈中按值存放的是它的地址。将一个对象赋值给另一个对象,实际上是将对象保存在栈中的地址赋值,最终指向同一个对象。

symbol可以作为一个标识符,但不能new symbol()
每个从Symbol()返回的symbol值都是唯一的。一个symbol值能作为对象属性的标识符;这是该数据类型仅有的目的。

const symbol1 = Symbol();
const symbol2 = Symbol(42);
const symbol3 = Symbol('foo');

console.log(typeof symbol1);
// expected output: "symbol"

console.log(symbol2 === 42);
// expected output: false

console.log(symbol3.toString());
// expected output: "Symbol(foo)"

console.log(Symbol('foo') === Symbol('foo'));
// expected output: false 只能创建唯一值

JS 的数字类型是浮点类型的,没有整型。并且浮点类型基于 IEEE 754标准实现,在使用中会遇到某些 Bug。NaN 也属于 number 类型,并且 NaN 不等于自身。

介绍js有哪些内置对象

Object 是 JavaScript 中所有对象的父对象
数据封装类对象:Object、Array、Boolean、Number 和 String
其他对象:Function、Arguments、Math、Date、RegExp、Error

typeof能判断的数据类型
  • typeof能够返回一个值的数据类型,除了null都可以显示正确类型
typeof 123 // "number"
typeof b //b没有声明,仍会显示"undefined"
  • 对于对象,除了函数都会显示object
typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'
  • 对于null,会显示object
    可以用Object.prototype.toString.call(null)正确判断
    在这里插入图片描述
如何区分数组和对象

typeof无法区分数组和对象,都返回object

  • Object.prototype.toString.call()
    在这里插入图片描述
  • Array.isArray()
Array.isArray(arr) //true
Array.isArray(obj) //false
  • constructor属性
arr.constructor==Array  //true
obj.constructor==Object //true
  • instanceOf
console.log(arr instanceof Array)//true
console.log(arr instanceof Object) //false
原型,原型链

每个对象都会在其内部初始化一个属性,就是prototype(原型),当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype又会有自己的prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念
关系:instance.constructor.prototype = instance.proto
特点:
JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变
当我们需要一个属性的时,Javascript引擎会先看当前对象中是否有这个属性, 如果没有,就会查找他的Prototype对象是否有这个属性,如此递推下去,一直检索到 Object 内建对象
JavaScript的所有对象中都包含了一个 [proto] 内部属性,这个属性所对应的就是该对象的原型
JavaScript的函数对象,除了原型 [proto] 之外,还预置了 prototype 属性
当函数对象作为构造函数创建实例时,该 prototype 属性值将被作为实例对象的原型 [proto]。
原型链:
当一个对象调用的属性/方法自身不存在时,就会去自己 [proto] 关联的前辈 prototype 对象上去找
如果没找到,就会去该 prototype 原型 [proto] 关联的前辈 prototype 去找。依次类推,直到找到属性/方法或 undefined 为止。从而形成了所谓的“原型链”
原型特点:
JavaScript对象是通过引用来传递的,当修改原型时,与之相关的对象也会继承这一改变

闭包
var test=(function(i){
     return function(){
         alert(i *=2);
}
})(2);
test(5);

答案:‘4’
test等于函数执行的返回结果
alert弹出来的都要转变成字符串(toString())
在这里插入图片描述
闭包只叫内存不销毁,不叫泄露

谈谈对es6的理解

参考链接

  • 解构赋值
  • 扩展运算符(spread)是三个点(…), 将一个数组转为用逗号分隔的参数序列 。
  • 函数优化:
  1. 给函数参数默认值
function add(a , b = 1) {
  return a + b;
}
console.log(add(10));
  1. 箭头函数
  2. 数组新增map和reduce
  • arguments对象可被不定参数和默认参数完美代替。
  • ES6将promise对象纳入规范,提供了原生的Promise对象。
    简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法
    上说,Promise 是一个对象,从它可以获取异步操作的消息。
  • 增加了let和const命令,用来声明变量。
  • 增加了块级作用域。
    let命令实际上就增加了块级作用域。
  • 还有就是引入module模块的概念
    模块化就是把代码进行拆分,方便重复利用。类似 Java 中的导包:要使用一个包,必须先导入一个包。而 JS 中没有包的概念,换来的是模块。
    模块功能主要是由两个命令构成:export 和 import 。
    export:用于规定模块的对外接口;
    export default xxx;
    import:用于导入其他模块提供的功能。
    import xxx from ‘./xxx’
  • Set和Map数据结构
  • class
  • Generator函数
function* hello () {
    yield "hello";
    yield "world";
    return "done";
}
let h = hello();

console.log(h.next()); //{value: "hello", done: false}
console.log(h.next()); //{value: "world", done: false}
console.log(h.next()); //{value: "done", done: true}
console.log(h.next()); //{value: undefined, done: true}
  • for-of(用来遍历数据—例如数组中的值。)也可以遍历generator函数
function* hello () {
    yield "hello";
    yield "world";
    return "done";
}
let h = hello();
for (let obj of h) {
    console.log(obj);
}

// 输出:
hello
world
(2.7+1.2)==3.9是不是true 为什么?js运算时处理精度误差

2.7+1.2=3.9000000000000004
计算机能读懂的是二进制
0.1+0.2=0.3000000000000004
0.1=> 0.1.toString(2)=>0.0001100110011(无限循环)
0.2=>0.2.toString(2)=>0.001100110011(无限循环…)
双精度浮点数的小数部分最多支持 52 位,所以两者相加之后得到这么一串0.0100110011001100110011001100110011001100110011001100 因浮点数小数位的限制而截断的二进制数字,这时候,我们再把它转换为十进制,就成了 0.30000000000000004。
如何解决?
方法一:
给出明确精度要求,在返回值过程中,计算机自动四舍五入

parseFloat((num1 + num2).toFixed(2)) === 0.30
//parseFloat() 函数可解析一个字符串,并返回一个浮点数。
//该函数指定字符串中的首个字符是否是数字。如果是,则对字符串进行解析,直到到达数字的末端为止,然后以数字返回该数字,而不是作为字符串。
//toFixed()函数指定了四舍五入的位数

方法二:

formatNum = function(f, digit) { 
    var m = Math.pow(10, digit); //10的digit次幂
    return parseInt(f * m, 10) / m; //parseInt() 方法用于将字符串参数作为有符号的十进制整数进行解析。
} 
formatNum(num,1)

为了避免产生精度差异,我们要把需要计算的数字乘以 10 的 n 次幂,换算成计算机能够精确识别的整数,然后再除以 10 的 n 次幂,大部分编程语言都是这样处理精度差异的,我们就借用过来处理一下 JS 中的浮点数精度误差。

看题
//example 1
let a={},b='0',c=0;
a[b]='珠峰';
a[c]='培训';
console.log(a[b])

答案:培训
对象的属性名都是字符串,属性名不能重复,所以第二个覆盖了第一个
在这里插入图片描述

  • 对象和数组的区别:
//example 2
let a={},
    b=Symbol('1'),
    c=Symbol('1');
a[b]='珠峰';
a[c]='培训';
console.log(a[b]);

答案:珠峰
Symbol创建唯一值,所以取a[b],还是以存的时候为主
在这里插入图片描述

  • 自己实现一个Symbol
//example 3
let a={},
    b={
       n:'1'
    },
    c={
       m:'2'
    };
a[b]='珠峰';
a[c]='培训';
console.log(a[b]);

答案:培训
当a[{n:‘1’}]这样存时,会把里面的转成字符串,调用to.String()函数
在这里插入图片描述
所以不管这个对象里的内容如何,都会转化成"[object object]"

  • Object.prototype.toString在项目中的应用/valueOf
js数据类型

基本 Number String Null Undefined Boolean BigInt
引用 Object
BigInt是解决什么问题?

BigInt 是一种内置对象,它提供了一种方法来表示大于 253 - 1 的整数。这原本是 Javascript中可以用 Number 表示的最大数字。BigInt 可以表示任意大的整数。
const theBiggestInt = 9007199254740991n;

const alsoHuge = BigInt(9007199254740991);
// ↪ 9007199254740991n

//typeof可测
typeof 1n === 'bigint'; // true
typeof BigInt('1') === 'bigint'; // true
聊聊null,undefined
  • 意义相似 ==等 ===不等;if语句中都被转为false
  • null是没有对象,作为对象原型链的终点;
    undefined是未定义,“缺少值”,被声明了没有赋值;
    调用函数时,该提供的参数没有提供;没有返回值;
  •  Number(undefined) //NaN
     Number(null)  //0
    
Number对象哪个方法1把数字转换为字符串,使用本地数字格式顺序。

toLocaleString() 方法返回这个数字在特定语言环境下的表示字符串。

parseInt,parseFloat

parseInt () 忽略前面空格,解析数字,直到非数字停止;
识别各种进制的数,多进制转十进制
parseInt(11,8) //9
parseFloat()忽略字符串前面空格,从第一个字符开始解析直到遇见一个无效的浮点数字字符为止(第二个".")

[1,2,3].map(parseInt)
//[1, NaN, NaN]
[1,2,3].map(parseFloat)
//[1, 2, 3]

parseInt和parseFloat的区别

Math.round(),Math.ceil(),Math.floor()的区别

区别

domready和onload事件的区别:
  • 前者是在dom文档结构准备完毕之后就可以对DOM进行操作
  • 后者是当页面完全加载完毕之后(整个document文档包括图片、css外部资源文件、js文件)就会触发window上的load事件
  • Dom文档加载的步骤:
  1. 解析html结构
  2. 加载外部脚本和样式表文件
  3. 解析并执行脚本
  4. dom树构建完成
  5. 加载图片等外部文件
  6. 页面加载完毕

DOM ready是在第四步完成之后触发
图片 Load是在第五步完成之后触发
onLoad是在第六步完成之后触发

DOM完整的解析过程:
解析HTML结构。
加载外部脚本和样式表文件。
解析并执行脚本代码。//js之类的
DOM树构建完成。//DOMContentLoaded
加载图片等外部文件。
页面加载完毕。//load
在第4步的时候DOMContentLoaded事件会被触发。
在第6步的时候load事件会被触发。
触发
1、当 onload事件触发时,页面上所有的DOM,样式表,脚本,图片,flash都已经加载完成了。
2、当 DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片,flash。

元素绑定事件与解绑

方法一:

        var btn=document.getElementById("event");
        btn.onclick=function(){
            alert("点我了!")
        }
        btn.onclick=null;

方法二:

        var btn=document.getElementById("event");
        function f1(){
            alert("点我了!")
        }
        btn.addEventListener("click",f1,false)//false冒泡,true捕获
        btn.removeEventListener("click",f1,false);

方法三:
浏览器已经不支持了。

  var btn=document.getElementById("event");
        function f1(){
            alert("点我了!")
        }
        btn.attachEvent('onclick',f1);
        btn.detachEvent("onclick",f1);
for in /of

for(let key in obj) 遍历的是key
for (let key of obj) 遍历的是内容,且不能遍历纯对象,可以是数组、字符串等。

深浅拷贝

原始类型浅拷贝:
方法一:

// 现在想把对象A的值复制给B,由于对象A的两个值都是原始类型,用浅复制即可

function copy(sub, sup) {
  for (var key in sup) {
    sub[key] = sup[key];
  }
}
copy(objB, objA);

方法二:
一层深,二层浅
IE不支持 Object.assign

object.assign(目标对象,需要复制的对象1,23)

方法三:
lodash

var obj2=_.clone(obj)

方法四:

var obj2={...obj}

方法五:
数组中concat和slice方法

var obj2=[]
console.log(obj2.concat(obj))

var obj=[1,2,{a:1}]
//slice最后一个不包括在内
var obj2=obj.slice(0,obj.length)
console.log(obj2)

深拷贝:
方法一:
只能处理json能理解的数据格式
JSON.parse(JSON.stringify(obj))

var obj=[1,2,{a:1}]
var obj2=JSON.parse(JSON.stringify(obj))
obj[2].a=2;
console.log(obj)// a=1
console.log(obj2)// a=2

方法二:
lodash _.cloneDeep()

var obj2=_.cloneDeep(obj)

方法三:
自己实现:

function deepCopy (obj) {
    var result;

    //引用类型分数组和对象分别递归
    if (Object.prototype.toString.call(obj) == '[object Array]') {
      result = []
      for (i = 0; i < obj.length; i++) {
        result[i] = deepCopy(obj[i])
      }
    } else if (Object.prototype.toString.call(obj) == '[object Object]') {
      result = {}
      for (var attr in obj) {
        result[attr] = deepCopy(obj[attr])
      }
    }
    //值类型直接返回
    else {
      return obj
    }
    return result
}
call,apply,bind:

作用:都是用来改变this指向的。
接受参数不同:call,bind传参用‘,’分隔,而apply是传入数组
如果call()和apply()的第一个参数是null或者undefined,那么this的指向就是全局变量,在浏览器里就是window对象。


var cat = {
  name:"喵喵",
  eatFish:function(param1,param2){
    console.log("吃鱼");
	console.log("this的指向=>");
	console.log(this);
	console.log(param1,param2);
  }
}
 
var dog = {
	name:"汪汪",
	eatBone:function(param1,param2){
		console.log("啃骨头");
		console.log("this的指向=>");
		console.log(this);
		console.log(param1,param2)
	}


//第一种,用call方法
cat.eatFish.call(dog,"旺财-13岁","call");

//第二种,用apply方法,参数不一样
cat.eatFish.apply(dog,["旺财-13岁","apply"]);

//把吃鱼的方法教给dog,在这里已经传参,返回的函数就不需要传参了。
var eatFishFun = cat.eatFish.bind(dog,"旺财-13岁","bind"); //返回的是方法
eatFishFun();
js垃圾回收

栈内存:栈内存调用栈上下文切换后就被回收,比较简单
堆内存的回收:V8堆内存分为新生代内存和老生代内存,新生代内存是临时分配的内存,存在时间段,老生代内存存在时间长。
新生代内存回收机制:FROM TO两部分,先扫描FROM,将非存活对象回收,将存活对象复制到To中,之后调换FROM/to 等待下一次回收
老生代内存回收机制
晋升:如果新生代变量经过多次回收仍然存在,则被放入老生代内存
标记清除:老生代内存会先遍历所有对象并打上标记,然后对正在使用或者被强引用的对象取消标记,回收被标记的对象。
整理内存碎片:把对象挪到内存的一段。
垃圾回收

写一个闭包

闭包的概念:函数A内部有一个函数B,函数B可以访问到A中的变量,那么函数B就是一个闭包;
闭包就是能够读取其他函数内部变量的函数;
闭包的意义:模块化、保护变量(我们只能间接访问函数内部的变量);

  • 可以读取函数内部的变量
  • 让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。
  • 是封装对象的私有属性和私有方法。
    闭包的应用:应用闭包的主要场合是:设计私有的方法和变量。
    任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数外部访问这些变量。私有变量包括函数的参数、局部变量和函数内定义的其他函数。
    匿名函数最大的用途是创建闭包,并且还可以构建命名空间,以减少全局变量的使用。从而使用闭包模块化代码,减少全局变量的污染。
    闭包的缺陷
    • 闭包的缺点就是常驻内存会增大内存使用量,并且使用不当很容易造成内存泄露。
    • 如果不是因为某些特殊任务而需要闭包,在没有必要的情况下,在其它函数中创建函数是不明智的,因为闭包对脚本性能具有负面影响,包括处理速度和内存消耗。
function books(){
    var book='书包里的书本'
    return function(){
    console.log(book);
    }
}
var result=books();
result(); //书包里的书本

//尝试拿到内部变量
function books(){
    var book='书包里的书本'
    return function(){
    return {
     books:book
    }
    }
}
var result=books();
var obj=result();
obj.books//书包里的书本

本来外部是无法访问函数里面的变量的,通过闭包,外部就有权访问内部变量了。
解决setTimeOut的闭包问题:
用立即执行函数:

for(var i=0;i<5;i++){
(function(x){
   setTimeOut(function(){
   console.log(x++);
   },4000)
})(i);
}
//创建全局执行上下文 变量i
//创建立即函数执行上下文 变量x 进入任务队列
//再退出,再次创建立即函数执行上下文,重复
//任务队列里面有数值0 1 2 3 4
//输出0 1 2 3 4
写原型链

prototype是函数才有的属性,_proto_是每个对象都有的属性
大多数情况下,proto__可以理解为“构造器的原型”,即__proto===constructor.prototype,但是通过 Object.create()创建的对象有可能不是, Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象。
prototype就是原型
当一个对象调用的属性、方法自身不存在时,就会去自己_proto_关联的prototype去找,依次递增,直至找到最顶层。
原型链特点:
js对象是通过引用传递的,修改原型时,与之相关的对象也会继承这一改变。

arr=[]
//原型对象
arr.__proto__==Array.prototype
//Array的原型对象为Object
Array.prototype.__proto__==Object.prototype
//object的原型对象为null
Object.prototype.__proto__==null
//构造函数的proto
Array.__proto__==Function.prototype
//构造函数的proto=函数的prototype
Function.__proto__==Function.prototype
作用域

作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。
作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。

  • 全局作用域

    • 最外层函数 和在最外层函数外面定义的变量拥有全局作用域
    • 所有末定义直接赋值的变量自动声明为拥有全局作用域
    • 所有 window 对象的属性拥有全局作用域
  • 函数作用域

    • 是指声明在函数内部的变量,和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,最常见的例如函数内部。
    • 作用域是分层的,内层作用域可以访问外层作用域的变量,反之则不行;
this理解

js 中的指向
两句话理解js中的this
1, 普通函数指向函数的调用者:有个简便的方法就是看函数前面有没有点,如果有点,那么就指向点前面的那个值;
2, 箭头函数指向函数所在的所用域: 注意理解作用域,只有函数的{}构成作用域,对象的{}以及 if(){}都不构成作用域;

普通函数

  • this在函数定义时确定不了,只有在函数执行时才能确定this到底指向谁,最终指向的是那个调用它的对象。
  • 看的是最后执行时是谁调用的。
  • 没有明确调用对象,返回window;
var a=11
function test1(){
  this.a=22;
  let b=function(){
    console.log(this.a);
  };
  b();
}
var x=new test1();
//输出11

es6 箭头函数的this:

  • this在定义时绑定。
  • 不能通过call、apply、bind来改变this值,但可以调用方法;
  • 没有arguments对象
  • 一般this指向全局对象
var a = 10
var obj = {
  a: 20,
  say: () => {
    console.log(this.a)
  }
}
obj.say() 
var anotherobj={a:30}
obj.say.apply(anotherobj)
正确答案: 10 10
  • 箭头函数没有this,会继承外部函数的this;
var a=11;
function test2(){
  this.a=22;
  let b=()=>{console.log(this.a)}
  b();
}
var x=new test2();
//输出22

在这里插入图片描述

  • 其他题目:
--------------------
name='ni'
var obj={
    name:'qunaer',
    Hi:function(){
        console.log(this.name)
    }
}
obj.Hi();
var obj2=obj.Hi();
obj2();

qunaer qunaer
//变量name不加var就变成了全局变量
---------------------

name='ni'
var obj={
    name:'qunaer',
    Hi:()=>{
        console.log(this.name)
    }
}
obj.Hi();
var obj2=obj.Hi();
obj2();

ni ni
--------------------
继承的方式及实现。

共六种:原型链、借用构造函数、组合式、原型式、寄生式、寄生组合式

function Person(name){
    this.name=name;
    this.like=["computer","swimming"] 
    this.age=10;//父类实例属性
}
Person.prototype.func=function(){
    return this.name;
}; //父类原型方法

1.原型链继承

function Student(){
}
Student.prototype=new Person();
var instance=new Student();

console.log(instance);//继承了属性
console.log(instance instanceof Student) //true
console.log(instance instanceof Person)  //true
instance.like.push("singing");
console.log(instance.__proto__) //所有实例共享,会受影响
console.log(instance.func())//继承了原型上的函数

原型链继承问题:
1.会共享父辈属性,在子辈中更改属性,其他都会受影响
2.创建子类型的实例时,不能向超类型的构造函数中传参;

2.构造函数继承

function Student(){
    Person.call(this,"Geoge");
}
var instance=new Student();
var instance2=new Student();
console.log(instance);
console.log(instance instanceof Student) //true
console.log(instance instanceof Person);   //false
instance.like.push("singing") 
console.log(instance.like); // [ 'computer', 'swimming', 'singing' ]
console.log(instance2.like);//[ 'computer', 'swimming' ]
console.log(instance.func) //undefined 不能继承父类原型上的属性和方法

特点:
1.解决了共享属性问题
2.可以向超类传递参数;
缺点:
1.方法都在构造函数中定义,无法复用
2.子类看不到超类,原型链上没有Person

3.组合式继承

function Student(name){   //解决不共享
    Person.call(this,name);
    this.hi="hi";
}
Student.prototype=new Person();   //解决在原型链上的问题;继承原型属性和方法。
Student.prototype.constructor=Student;  //一定要修改指向

var instance=new Student("jojo")
var instance2=new Student("haha")
console.log(instance instanceof Student)//true
console.log(instance instanceof Person);//true
console.log(instance)
instance.like.push("singing") 
console.log(instance.like); //[ 'computer', 'swimming', 'singing' ]
console.log(instance2.like);//[ 'computer', 'swimming' ]
console.log(instance.age) //10

解决上述问题后:不共享,可以继承原型属性。
缺点:会调用两次Person,在Student的原型上多创建非必要的属性。
4.寄生组合式继承
包括原型式继承和寄生式继承;
特点:解决组合继承两次调用父类构造函数问题;
优点:相比组合继承,只用调用一次父类方法,避免了父类在Student上创建的不必要,多余属性。

//原型式继承 等于把所需要继承的父类进行一次浅复制; 同样有原型链的问题;
function object(obj){
    function F(){};
    F.prototype=obj;
    return new F();
}
//寄生式继承 创建新对象,增强对象,返回对象 不能函数复用
function inheritPrototype(SubType,SuperType){
    var prototype=object(Person.prototype);
    prototype.constructor=Student;
    Student.prototype=prototype;
}

function Student(name){
    Person.call(this,name)
}
inheritPrototype(Student,Person);
var instance=new Student("lala");
console.log(instance);
节流防抖
  • 防抖
    触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间;
    版本一:停止触发后隔一秒运行
var count = 1;
var container = document.getElementById('container');
function getUserAction() {
    container.innerHTML = count++;
};
//防抖函数

function debounce(func, wait) {
    var timeout; //标记定时器返回值
    return function () {
        var context = this; //改变this指向
        var args = arguments;  //得到执行函数的event
        clearTimeout(timeout)//把前一个clear掉
        timeout = setTimeout(function(){
            func.apply(context, args)
        }, wait);
    }
}
container.onmousemove = debounce(getUserAction,1000);

版本二:立即触发函数,等到一定时间后再允许触发函数

function debounce(func, wait, immediate) {

    var timeout;

    return function () {
        var context = this;
        var args = arguments;

        if (timeout) clearTimeout(timeout);
        if (immediate) {
            // 如果已经执行过,不再执行
            var callNow = !timeout;
            timeout = setTimeout(function(){
                timeout = null;
            }, wait)
            if (callNow) func.apply(context, args)
        }
        else {
            timeout = setTimeout(function(){
                func.apply(context, args)
            }, wait);
        }
    }
}

JavaScript专题之跟着underscore学防抖 #22

  • 节流
    高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率
    方法一:使用时间戳
        function throttle(func,wait){
            var context,args;
            var previous=0;
            return function(){
                var now=+new Date();
                context=this;
                args=arguments;
                if(now-previous>wait){
                    func.apply(context,args);
                    previous=now;
                }
            }
        }
        container.onmousemove = throttle(getUserAction,3000);

方法二:定时器
当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。

        function throttle(func,wait){
            var timeout;
            var context,args;
            return function(){
                context=this;
                args=arguments;
                if(!timeout){
                    timeout=setTimeout(function(){
                        timeout=null;  //到了一定时间才会执行,清空定时器,而清空之后才能执行。
                        func.apply(context,args);
                    },wait);
                }
            }
        }

所以比较两个方法:
第一种事件会立刻执行,第二种事件会在 n 秒后第一次执行
第一种事件停止触发后没有办法再执行事件,第二种事件停止触发后依然会再执行一次事件

JavaScript专题之跟着 underscore 学节流 #26

节流版本二:


function throttle(fn) {
    let canRun = true; // 通过闭包保存一个标记
    return function () {
        if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return
        canRun = false; // 立即设置为false
        setTimeout(() => { // 将外部传入的函数的执行放在setTimeout中
            fn.apply(this, arguments);
            // 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。当定时器没有执行的时候标记永远是false,在开头被return掉
            canRun = true;
        }, 500);
    };
}
function sayHi(e) {
    console.log('节流');
}
window.addEventListener('resize', throttle(sayHi));

节流防抖

es6去重,es5去重(手写)

es5:

1.
//新建数组,遍历数组,把值不在新数组里的推入新数组。indexOf
if(hash.indexOf(arr[i])==-1)
//或者把值第一次出现不适indexOf==i的去掉
 if(arr.indexOf(arr[i])==i){
 //或者filter
function unique(arr){
  return arr.filter(function(item,index,arr){
    return arr.indexOf(item)==index;
})
}
2.
//排序后去除重复项
//先sort
 if(arr[i]!=hash[hash.length-1]){
3.优化遍历数组法 es5最常用
//双重循环 去掉重复项
function unique(arr){
	for(let i=0;i<arr.length;i++){
		for(let j=i+1;j<arr.length;j++){
             if(arr[i]==arr[j]){
				arr.splice(j,1);
				j--;
			}
        }
	}
	return arr;
}

4.去重有引用类型的数组
function unique(arr){
  let newArr=[];
  let obj={}
  arr.forEach(item => {
      if(typeof item !=="object"){
          if(newArr.indexOf(item)==-1){
              newArr.push(item)
          }
      }else{
          let str=JSON.stringify(item);
          if(!obj[str]){
              newArr.push(item);
              obj[str]=1
          }
      }
  });
   return newArr;
}
var array=[1,2,3,4,4,{a:1},{a:1},{},{}]
console.log(unique(array))
//[ 1, 2, 3, 4, { a: 1 }, {} ]

es6:

1.
var x=new Set(arr)
return [...x]
//或 return Array.from(x);  但无法去掉空对象
//直接一行代码解决[...new Set(arr)]

2.
//for循环一遍 
Array.includes(arr[i]);
3.
//reduce+includes
function unique(arr){
	return arr.reduce((prev,cur)=>{
		prev.includes(cur)?prev:[...prev,cur];
	})
}
promise,async,await

promise
三种状态:

状态意义
待定(pending)初始状态,既没有被兑现,也没有被拒绝
已兑现(fulfilled)意味着操作成功完成
已拒绝(rejected)意味着操作失败

如果一个promise已有状态,则不会再改变。
状态改变:
Promise对象的状态改变,只有两种可能:
从pending变为fulfilled
从pending变为rejected。
这两种情况只要发生,状态就凝固了,不会再变了,这时就称为resolved(已定型)

链式调用:
promise.then(),
resolve()
添加解决(fulfillment)和拒绝(rejection)回调到当前 promise, 返回一个新的 promise, 将以回调的返回值来resolve.
promise.catch(),
reject()
添加一个拒绝(rejection) 回调到当前 promise, 返回一个新的promise。当这个回调函数被调用,新 promise 将以它的返回值来resolve,否则如果当前promise 进入fulfilled状态,则以当前promise的完成结果作为新promise的完成结果.
promise.finally()
添加一个事件处理回调于当前promise对象,并且在原promise对象解析完毕后,返回一个新的promise对象。回调会在当前promise运行完毕后被调用,无论当前promise的状态是完成(fulfilled)还是失败(rejected)
静态方法:

Promise.all(iterable)
这个方法返回一个新的promise对象,该promise对象在iterable参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。这个新的promise对象在触发成功状态以后,会把一个包含iterable里所有promise返回值的数组作为成功回调的返回值,顺序跟iterable的顺序保持一致;如果这个新的promise对象触发了失败状态,它会把iterable里第一个触发失败的promise对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个promise对象的状态集合。(可以参考jQuery.when方法—译者注)
Promise.allSettled(iterable)
等到所有promises都已敲定(settled)(每个promise都已兑现(fulfilled)或已拒绝(rejected))。
返回一个promise,该promise在所有promise完成后完成。并带有一个对象数组,每个对象对应每个promise的结果。
Promise.any(iterable)
接收一个Promise对象的集合,当其中的一个 promise 成功,就返回那个成功的promise的值。
Promise.race(iterable)
当iterable参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。
Promise.reject(reason)
返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法
Promise.resolve(value)
返回一个状态由给定value决定的Promise对象。如果该值是thenable(即,带有then方法的对象),返回的Promise对象的最终状态由then方法执行决定;否则的话(该value为空,基本类型或者不带then方法的对象),返回的Promise对象状态为fulfilled,并且将该value传递给对应的then方法。通常而言,如果您不知道一个值是否是Promise对象,使用Promise.resolve(value) 来返回一个Promise对象,这样就能将该value以Promise对象形式使用。

创建promise
简单说就是一个容器,里面保存着某个未来才回结束的事件(通常是一个异步操作)的结果。
Promise 详解

function getNum(){
   return new Promise((resolve,reject)=>{
     resolve();
     reject();
   })
}
getNum.then(res=>{
//resolve 返回的值
},res=>{
//reject
}).catch((error)=>{
//reject或前面链式调用出错
})
generator与async,await区别
  • 异步:执行一个任务 先执行第一段 然后执行其他任务 等做好了准备再执行第二段;这种不连续的执行,就叫异步;
  • 回调函数:所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。
  • Promise:多个回调函数嵌套,
  • 协程
    第一步,协程A开始执行。
    第二步,协程A执行到一半,进入暂停,执行权转移到协程B。
    第三步,(一段时间后)协程B交还执行权。
    第四步,协程A恢复执行。
function asnycJob() {
  // ...其他代码
  var f = yield readFile(fileA);
  // ...其他代码
}

面代码的函数 asyncJob 是一个协程,它的奥妙就在其中的 yield 命令。它表示执行到此处,执行权将交给其他协程。也就是说,yield命令是异步两个阶段的分界线。
协程遇到 yield 命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。它的最大优点,就是代码的写法非常像同步操作,如果去除yield命令,简直一模一样。

  • Generator:Generator是协程的es6实现,最大特点就是可以交出函数的执行权(即暂停执行);
    整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用 yield 语句注明;
function* gen(x){
  var y = yield x + 2;
  return y;
}

var g = gen(1);
g.next() // { value: 3, done: false }
g.next() // { value: undefined, done: true }

上面代码中,调用 Generator 函数,会返回一个内部指针(即遍历器 )g 。这是 Generator 函数不同于普通函数的另一个地方,即执行它不会返回结果,返回的是指针对象。调用指针 g 的 next 方法,会移动内部指针(即执行异步任务的第一段),指向第一个遇到的 yield 语句,上例是执行到 x + 2 为止。
换言之,next 方法的作用是分阶段执行 Generator 函数。每次调用 next 方法,会返回一个对象,表示当前阶段的信息( value 属性和 done 属性)。value 属性是 yield 语句后面表达式的值,表示当前阶段的值;done 属性是一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段。

Generator 函数的含义与用法

  • async函数:async函数是Generator函数的语法糖。
    语法糖:就相当于汉语里的成语。即,用更简练的言语表达较复杂的含义。
    语法糖就是为了避免coder出现错误并提高效率的语法层面的一种优雅简洁的写法;

async替换Generator函数中的* ,await替换yield;
async 对Generator函数改进:
1)内置执行器,与普通函数执行一样;
2)更好的语义,async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。
3)更广适用性;yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。

async 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。
同 Generator 函数一样,async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。

await返回的是Promise对象;

await 命令只能用在 async 函数之中,如果用在普通函数,就会报错。
async 函数的含义和用法

js异步的几种方式

1.回调函数
优点是简单、容易理解和实现,
缺点是不利于代码的阅读和维护,各个部分之间高度耦合,使得程序结构混乱、流程难以追踪(尤其是多个回调函数嵌套的情况),而且每个任务只能指定一个回调函数。此外它不能使用 try catch 捕获错误,不能直接 return。

ajax(url, () => {
    // 操作A
    ajax(url1, () => {
        // 操作B
        ajax(url2, () => {
            // 操作C
        })
    })
})

2.延时器setTimeout
延时器本身就是异步操作,它不取决于代码的执行顺序,取决于某个事件是否发生。

fun1.on('done', fun2);
// 待fun1发生done事件,就执行fun2。

function fun1() {
  setTimeout(function () {
    // ...
    fun1.trigger('done');
  }, 1000);
}
// fun1.trigger('done')表示,执行完成后,立即触发done事件,从而开始执行fun2。

优点:每个事件都可以指定若干个回调函数,可以"去耦合",有利于实现模块化
缺点:整个程序都要变成事件驱动型,无法保障顺序,运行流程不清晰,不方便看出主流程
3.发布订阅模式
就和用户订阅微信公众号道理一样,公众号可以被若干用户同时订阅,当公众号有新增内容时候,只要发布就好了,用户就能接收到最新的内容。(不等同于 观察者模式)
4.Promise
5.Generators
6.async / await
async 和 await 相比直接使用 Promise 来说,优势在于处理 then 的调用链,能够更清晰准确的写出代码。缺点在于滥用 await 可能会导致性能问题,因为 await 会阻塞代码,也许之后的异步代码并不依赖于前者,但仍然需要等待前者完成,导致代码失去了并发性。

JS异步的几种方式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值