js-es6

99 阅读19分钟

js

编程

深拷贝的浅拷贝

考虑数组的情况,判断当前对象是数组还是对象。

考虑循环引用

手写call bind和apply

call,参数为一个个传的

apply,参数为一个数组

bind,会返回一个新的函数

实现思路:给function原型绑定一个方法,将绑定的方法指定为绑定对象的属性,执行绑定的函数,删除绑定的函数,返回执行结果,如果被绑定的函数有返回值的话需要返回。

注意:如果被绑定的对象不是一个函数报错。

手写promise

instanceof实现原理

判断对象是否属于某个类或者其子类的实例。

intanceof用于判断左侧对象是否是右侧的实例。

使用左侧实例的隐式原型判断是否等于右侧实例的显示原型。

function myInstanceof(left, right) {
  while (true) {
    if (left.__proto__ === null) return false;
    if (left.__proto__ === right.prototype) return true;
    left = left.__proto__;
  }
}
//不适用__proto__来获取实例的原型,使用Object.getPototypeOf()来获取
//判断原型链上是否包含目标类型,否则返回false
function myInstanceOf(left ,right){
    //返回指定对象的原型,即__proto__属性的值
    let proto = Object.getPrototypeOf(left)
    console.log(proto,right.prototype);
​
    const prototype = right.prototype
​
    while(true){
        if(!proto) return false
        if(proto === prototype) return true
        proto = Object.getPrototypeOf(proto)
    }
}

constractor:通过constractor对象访问它的构造函数,改变了对象的原型之后就不能用来判断数据类型了

返回的是一个构造函数

  • object.prototype.toString.call(),有的对象重写了tostring方法,因此需要使用call方法调用object上的tostring方法才能区分。

prototype proto getPrototypeOf()区别


console.log(Object.getPrototypeOf(Dog)); //class Animal {}
console.log(Dog.__proto__); //class Animal {}
console.log(Dog.prototype); //Animal {constructor: ƒ}

prototype是函数独有的属性,是一个对象,被作为原型对象用于创建新对象实例。 用于给通过该函数创建的所有对象添加方法和属性;

proto是每个对象所独有的内部属性,用于指向该对象的原型对象;

Object.getPrototypeOf()是一个静态方法,用于获取任意对象的原型对象,比proto更加安全和可靠(proto不是浏览器的标准属性)。

ajax

ajax出现实现了异步渲染页面,不用再刷新整个页面。

创建ajax请求步骤:

  • 创建一个XMLHtppRequest对象
  • 使用open方法创建请求,参数:请求方法,地址,是否异步和用户认证信息
  • 可以通过setRequestHeader修改请求头信息
  • XMLHttpRequest对象5哥状态,触发onreadystatechange事件,可以通过设置监听函数,来处理请求成功后的结果
  • readyState 4数据接收完成,可以判断请求状态,请求状态2xx或304表示正常,通过response来更新页面

progress请求收到更多数据时,周期性地触发

两个重要的状态 readyState==4请求完成 status==200 响应完成

readyState == 1 open方法被调用后状态

readyState == 2 send方法被调用后状态

readyState == 3 请求中状态pending

readyState == 4 请求完成

ajax、fetch、axios区别

ajax

优点:是前端早期阶段在不刷新页面的情况下,加载数据的一种解决方案。

缺点:

  • MVC变成,不符合前端MVVM的浪潮
  • 基于远程XHR开发,结构不清晰
  • 配置和调用混乱,多个异步嵌套容易形成回调地狱问题

fetch:es6中ajax的替代,使用了promsie对象,代码结构比ajax简单,不是对ajax的封装,二是原生js,没有使用xmlhttprequest对象

优点:

  • 语法简单,更加语义化
  • 基于promise实现,支持async/await
  • 更加底层,提供的API丰富(request,response)
  • 脱离了XHR,是ES规范里新的实现方式

缺点:

  • 只对网络请求报错,对400 500都当作成功的请求,服务器返回400 500错误码时并不会reject,只有网络错误导致不能完成请求时才会被reject
  • 默认不会带cookie,需要添加配置项
  • 不支持abort,不支持超时控制,使用settimeout及promise.reject实现的超时控制不能组织请求过程继续在后台运行,造成流量浪费
  • 没办法检测请求进度,xhr可以

axios 基于promise封装的http客户端,支持node和浏览器端;监听请求和返回,对请求和返回进行转化;取消请求;自动转换json数据;客户端支持低于XSRF攻击

异步编程

  • 回调函数,多个回调函数嵌套会造成回调地狱,上下两层的回调函数代码耦合度高,不利于代码的可维护。
  • promsie,使用promise的方式,可以将嵌套的回调函数作为链式调用,多个链式调用会造成代码语义不明确

for ...in和for...of区别

for...of:es6新增的遍历方式,可以遍历含有iterator接口的数据,获取的是对象的键值;不会遍历原型上的属性;遍历数组返回数组的下标对应的属性值

for...in:获取的是键名;会遍历原型上的属性;遍历数组返回的是数组的下标

哪些数据类型含有iterator接口?

array

map

set

string

argument

nodelist

typedArrays:规定数据类型的数组

setTimeout(fn,0)

0表示会立即插入队列,但是不立即执行,要等到主线程任务列表处理完成后才会处理。是否立即执行取决于js线程是拥挤还是空闲。

判断是否为空对象


JSON.stringify(obj) === "{}"Object.keys().length === 0

undefined和void 0区别

void 0返回undefined,为什么不直接判断Undefined?

  • undefined可以被重写,在es5全局中是一个全局的只读属性,在局部作用域中还是可以被重写
  • void运算符能对给定的表达式进行求值,然后返回undefined,void后面跟任意一个表达式都是返回undefined;void不能被重写;void 0是表达式中最短的用0代替undefined可以节省字节。

Object.is(value1,value2)和== 以及===的区别

一般情况下和===相同,处理了一些特殊情况,例如:-0和+0不相等,NaN和NaN相等

和===行为相似,只是对于-0和+0以及NaN的处理不同


console.log(Object.is(-0,+0));//false
console.log(-0 === +0);//trueconsole.log(Object.is(NaN,NaN));//true
console.log(NaN === NaN);//false

js数据类型

js的包装类型

js基本类型是没有属性和方法的,在调用基本类型的属性或方法时,js会隐式将基本类型转为对象

通过object(xxx),将基本类型转换为包装类型,valueof将包装类型转成基本类型


var a = new Boolean(false)
if(!a){
    console.log("opps")//什么都不会打印,false转成了对象类型
}

js数据类型

undefined null number string boolean object symbol bigint

别忘了symbol和bigint

symbol:解决全局变量冲突问题

bigint:数据类型,可以表示任意精度格式的整数,使用bigint可以安全地存储和操作大整数,即使这个数已经超过了Number能够表示的安全整数范围。

number的安全范围:2^53-1

  • 原始类型和引用类型区别:

原始类型:undefined number string boolean null

引用类型:date math object

  • 区别

原始类型存在栈中,占据空间小,大小固定;

引用数据类型存在堆中,占据空间大、大小不固定。

  • 堆和栈的区别

栈->先进后出;堆队列方式,按优先级进行排序的。

js检测数据类型的方法

typeof:只能检测基础类型,对象类型和null统一返回object

instanceof:判断对象的类型,不能明确判断是哪个类型,内部实现原理通过原型链判断

constructor:判断数据类型;对象实例通过constructor访问它的构造函数。如果改变了原型就不能判断了;

Object.prototype.toString.call(),Array和Function等重写了toString方法

js显示转换和隐式转换

显示转换:手动转换

隐式转换:当运算符在运算时,两边数据不统一编译器会自动将两边数据进行数据类型转换成统一的再计算。

逻辑运算,算数运算

==运算符:

  • 不同类型间转换:null == undefined其他都为false

  • 字符串和数字,将字符串转成数字进行比较

    • nan和任意值都不等,包括自己
  • 布尔和数字,转成数字再比较

  • 值是对象,另一个是数字或字符串或symbol,将对象转成原始值再比较

基础类型:number\string\boolean\undefined\null\symbol

对象类型:object(date,regexp,function,array,math,error)

总结:除了Number\string和boolean之间有隐式转换关系,字符串和boolean转成数字比较,字符串和数字,Null和undefined在不是全等的时候相等,其他时候比较相等都为false,nan和任意值比较都为false。

宿主对象:js运行环境

BOM:window,navigator,screent,history,location

DOM:document,input,...html元素都是一个dom对象

null和undefined区别

null和undefined都是基本数据类型,代表的含义不同

null代表空对象,undefined代表未定义的。

undefined不是关键字,可以用来作为一个变量名,


const a = 6
a === undefined
//会被编译成
"use strict"
var a = 6
a === void 0null === undefined  //false
null == undefined //true

事件流

dom2级事件:规定的事件流包括三个阶段“事件捕获阶段”、处于目标阶段,捕获阶段

IE和网景提出了两个不同的事件流,IE冒泡流,网景事件捕获流。

冒泡流:从目标元素到父元素

捕获流:从父元素到目标元素

事件处理程序:

dom2提供了addEventListener和removeEventListener

事件对象:

currentTatrget:当前正在处理事件的元素

preventDefault:取消事件默认行为

stopPropagation:取消事件冒泡

target:事件目标

0.1+0.2!=0.3

js采用IEEE 754标准双精度版本64位

64位中符号位占一位,整数占十一位,其他五十二位都是小数位。因为0.1+0.2都是无线循环的二进制,所以在小数位末尾出需要判断是否进位。

如何解决:

ES6在Number对象上新增一个极小的常量Number.EPSILON。标识1与大于1的最小浮点数之间的差。实际上是js能够表示的最小精度,误差如果小于这个值就可以认为已经没有意义了,即不存在误差了。

toFixed方法,把小数四舍五入位指定小数位数的数字。

或者自定义处理函数:思路,先将数字扩大10的n次方倍,再缩小,以此来计算精度。


function cellNum(num1,num2){
   const num1Digits = (num1.toString().split(".")[1] || '').length
   const num2Digits = (num2.toString().split(".")[1] || '').length
   const baseNum = Math.pow(10,Math.max(num1Digits,num2Digits))
   return (num1 * baseNum + num2 * baseNum) /baseNum
}

number的存储空间是多大?

存储空间+-(Math.pow(2,53)-1)正负2的53次方-1

后台发送了一个超大限制的数字怎么办?

js会发生截断,等于js能支持的最大数字;

超过最大限制的数字可以和服务器约定返回字符串;

如果字符串只需要展示的话,我们直接展示即可。

如果需要计算:整数使用新增的BigInt类型进行计算。

new过程发生了什么?

  • 创建一个空对象
  • 将空对象得原型指向this本身
  • 返回新创建得对象

function newObj(){
    let o = new Object()
    //将空对象的原型指向指定对象的原型
    o.__proto__ = A.prototype
    //更改构造函数内部this,将this指向新创建的对象
    A.call(o)
    return o
}

箭头函数为什么不能用new?

没有proto属性,没有this

原型、原型链

js中使用构造函数来创建一个对象,每个构造函数内部都有一个prototype属性,属性值是一个对象,这个对象包含了可以由该构造函数的所有实例共享的属性和方法。

使用构造函数新建一个对象后,这个对象内部将由一个指针指向构造函数的prototype属性对应的值,es5中这个指针被成为对象的原型。

各大浏览器中使用__proto__属性来访问这个属性,es5中新增了一个Object.getPrototypeOf来获取实例对象的原型。

原型链:

当访问一个属性时,如果这个对象内部不存在这个属性,就会去原型对象里找这个属性,这个原型对象又会有自己的原型,这样一直找下去,就是原型链的概念,直到找到原型链的尽头Object.prototype,如果还没找到这个属性则返回undefined。

js对象时通过引用来传递的,创建的每个新对象实体中并没有属于自己的原型副本。当修改原型时,与之相关的对象也会继承这一改变。

原型链的终点:null

Object是构造函数,原型链终点Object.prototype.__proto__ === null

作用域、作用域链、执行环境、活动对象

执行环境:也叫执行上下文,每个作用域下都有一个自己的执行环境,代码会在自己的执行环境下执行。每个执行环境都有一个与之关联的变量对象,这个变量对象保存着执行环境中定义函数和变量。执行环境分为,全局执行环境和函数执行环境。

函数执行环境,如果当前是一个函数执行环境那么他的变量就是该函数的活动对象。作用域链的下一个变量都西昂来自所包含这个函数的环境,再下一个变量对象来自下一个包含环境,一直延续到全局执行环境。

js使用ESC(scmascript stack)管理执行上下文

包含的对象:变量对象:存储上下文中的变量和函数声明(包括函数的形参,函数声明,变量声明)

活动对象:函数被调用后,就会创建一个活动对象,活动对象在函数执行环境中作为变量对象使用。

作用域:控制变量和函数的可访问范围,作用域外无法引用作用域内的变量,离开作用域后,作用域的变量内存空间会被清空,执行完函数。

  • 全局作用域和函数作用域

全局作作用域,最外层函数和最外层函数外面定义的变量拥有全局作用域

所有未定义直接赋值的变量自动声明为全局作用域

所有window对象的属性

全局作用域会污染全局命名空间,容易引起命名冲突

  • 函数作用域

函数内部声明的变量,一般只有固定的代码片段可以访问到

  • 块级作用域

let const可以声明块级作用域,块级作用域可以在函数中创建也可以在一个代码块中的创建

作用域链:保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,可以访问到外层环境的变量和函数。

本质是一个指向变量对象的指针列表,变量对象是一个包含执行环境中所有变量和函数的对象。作用域链的前端始终都是当前执行上下文的变量对象。全局执行上下文的变量对象始终是作用域链的最后一个对象。

当查找一个变量时,如果当前执行环境中没找到,可以沿着作用域链向后查找。

this指向问题

  • 默认绑定:非严格模式指向window,严格模式为undefined
  • 严格模式:内部this指向window,外部this指向undefined
  • let和const存在暂时性死区,不会挂载到window下,var声明的没有会挂载到window下面
  • 对象内执行,函数独立运行时,this指向的是函数内的对象
  • 函数内this,指向函数中的对象
  • 自治性函数内的this指向window

隐式绑定:

  • 调用存在上下文
  • this永远指向最后调用它的那个对象
  • 传参形式->函数被当作参数传递过去,和原对象没有关系
  • setTimeout是异步调用的,只有当满足条件并且同步代码执行完毕后,才会执行它的回调函数。

函数中的this指向:

  • 普通函数中:谁调用就指向谁

  • 对象里面:指向当前对象

  • 定时器里面:指向window

  • 箭头函数中:承父级作用域

  • 构造函数:

    • 当作函数调用:指向window
    • new Object:指向新创建的对象

总结:

  • 函数中如果没有指定调用对象,默认指向window
  • 方法调用,作为对象的属性,this指向这个对象
  • 构造器,指向新创建的对象
  • call,apply和bind指定调用函数的this指向。

call apply和bind区别

call和apply在于参数上的区别。call后跟的是参数列表,apply后是一个数组。

call、apply和bind区别在于是否立即执行,前者会立即执行,后者会返回一个新的函数需要手动调用。

宏任务和微任务

微任务:主线程代码没执行完,会先被扔到微任务队列里。process.nextTick

宏任务:setTimeout

requestIdleCallback:实验中的api,可以在浏览器空闲的时候做一些事情,处理不重要且不紧急的任务。

返回一个id,window.cancleIdleCallback(id)结束回调。

callback(idleDeadline),事件循环空闲时即将被调用的函数的引用。参数:获取当前空闲时间以及回调是否在超时时间前已经执行的状态。

requestIdleCallback

不要在requestIdleCallback里执行修改dom操作,原因:requestIdleCallback回调执行之前,样式变更以及布局计算等都已经完成,如果在callback中修改dom的话,之前的布局计算都会失效,如果下一帧里有获取布局相关的操作,浏览器就需要重排,另外修改dom的时间是不可预测的,因此容易超过当前帧空闲的阙值。

推荐在requestAnimationFrame里修改dom

不推荐在promise resolve里修改dom

可以做什么:

数据的分析和上报

requestIdleCallback和requestAnimationFrame区别

requestAnimationFrame的回调会在每一帧确认执行,属于高优先级任务。

FPS低于 60时是比较流畅的。

json

轻量级的数据交换格式,可以被任何的编成语言读取和作为数据格式来传递。

基于Js的,json对象格式更加严格,json属性不能为函数,不能出现nan这样的属性值等。

json缺点:

  • json的对象值为function时,会被转成[Function name]
  • date对象,json.stringify后再JSON.parse时间将转成字符串的形式,而不是对象的形式
  • 对象属性有regexp,error对象,则序列化的结果只能得到空对象
  • 对象属性值为undefined,则拷贝之后会丢失属性
  • 性能上比其他数据遍历慢
  • nan、正无穷和负无穷会变为Null
  • 会抛弃constructor,所有的构造函数都指向Object,只能枚举自身的属性
  • 对象中有循环引用 ,会导致循环引用,会报错
  • 不支持拷贝含有symbol属性名的对象

escape、encodeURI、encodeURIComponent区别

escape对字符串进行编码,不会对ASCII字母和数字进行编码,也不会对ASCII标点符号进行编码:* @ _ + . /其他所有的字符都会被转义序列替换。unicode编码为0xff之外字符,escape直接在字符的unicode编码前加上%u,二encodeURI会将字符串转换为UTF-8的格式,再在每个字节前加上%

encodeURI对整个URI进行转义,将URI中的非法字符转换为合法字符,不会转义特殊字符。

encodeURIComponent对URI的组成部分进行转义,所以一些特殊字符也会得到转义,所以特殊字符也会得到转义。

bigint

js中number.MAX_SAVE_INTEGER表示最大安全数字,在这个范围内不会出现精度丢失,超过这个范围,js就会出现计算不准确的情况,需要第三方库进行解决,因此官方提出了bigInt来解决此问题。

扩展运算符使用场景


let obj = {a:1,b:2}
let arr = [1,2,3,4,5]
console.log({...obj});//{ a: 1, b: 2 }
console.log(...[1,[2,3],[4,5]]);//1 [ 2, 3 ] [ 4, 5 ]
​
console.log(Math.min(...arr))//1 结合math函数获取最大值最小值
//将函数参数结果转为数组
//es5写法:Array.prototype.sclice.call(arguments)
//es6写法:[...arguments]
​
//将字符串转成数组
[..."hello"] //["h","e","l","l","o"]
​
//用于数组赋值,只能放在参数的最后
const [...rest,last] = [1,2,3,4,5]//报错
const [first,...reset]=[1,2,3,4,5]//1 [ 2, 3, 4, 5 ]
​
//合并数组
console.log([...arr,...reset])

js脚本延迟加载的方式

页面加载完成之后再加载js文件,js延迟加载有助于提高页面加载速度。

  • defer,会让脚本的加载与文档的解析同步解析,文档解析完之后再执行脚本文件,这样就可以使页面的渲染不被阻塞。多个设置defer的脚本,按照最后顺序执行的。不同浏览器的表现不同。
  • async,脚本异步加载不会阻塞页面的解析过程,当脚本加载完成后立即执行js脚本,这时如果文档没有解析完成的话会阻塞,多个async属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序以此执行的。
  • 动态创建dom方式,当文档加载完成后创建script标签
  • settimeout延迟
  • 将Js放在文档后面执行,使js脚本尽可能再最后加载执行。

es6

扩展运算符

  • 对象扩展运算:用来取出参数对象的所有可遍历属性,拷贝到当前对象中。,相当于Object.assign方法,用于对象的合并,将源对象的所有可枚举属性,复制到目标对象,多个同名属性会被后面的覆盖,前面的表示目标,后面的表示源对象。
  • 数组扩展运算:将数组转为逗号分隔的参数序列,如果用于赋值,只能放在第一位。将参数扩展为数组,将数组结构为单个对象。

Proxy

代替Object.defineProperty实现数据响应式。

通常和reflect一起使用

尾调用

特点:

  • 函数的最后return另一个函数
  • return后面只能是一个函数,不能有其他操作

函数的最后一步调用另一个函数。代码执行是基于执行栈的,当在一个函数里调用另一个函数时,会保留当前的执行上下文,然后再新建另一个执行上下文加入栈中。使用尾调用已经是函数的最后一部,不必再保留当前的执行上下文,从而节省内存。

es6优化只在严格模式下开启,正常模式是无效的。