添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

JS 知识点总结

零 基本操作符的考察

一元操作符 递增递减操作符 :--age(前置递减) 变量值改变之后 进行执行语句,age--(后置递减) 变量值改变之 前 进行执行语句
位操作符 按位非 正数:先加一再取反,  负数: 先取反再加一,按位与 1&0 =0  1&1=1,按位或 只要有1个为1 就为1,只有全为0才为0,
布尔操作符 逻辑非 !,逻辑与 A&&B( A位true 无论B 是true或者false 都返回 B,A为 false 无论B 是 true或者false 都返回A), 逻辑或A|| B(如果A为 true 无论 B 是true 或者 false 都返回 A;如果 A为false 无论 B是true或者false 都返回B;)
相等操作符 1. 如果有一个操作数是布尔值,则在比较相等性之前,将其转换为数值;2. 如果一个操作数是字符串,另一个操作数是数值,在比较之前先将字符串转换为数值;3. 如果一个操作数是对象,另一个操作数不是,则调用对象的 valueOf() 方法,用得到的基本类型值按照前面的规则进行比较;4. 如果有一个操作数是 NaN,无论另一个操作数是什么,相等操作符都返回 false;5. 如果两个操作数都是对象,则比较它们是不是同一个对象。如果指向同一个对象,则相等操作符返回 true;6. 在比较相等性之前,不能将 null 和 undefined 转成其他值。7. null 和 undefined 是相等的。null是对象,表示一个空指针,undefinded是表示未定义的类型,它的类型本身就叫做”未定义类型“(和object类型的区别)null != underfind  ==> false . 总结原则:  执行规则从 1-3的顺序执行 . 1 如果相等操作符两边的操作数,不包含 null 或者 undefined,且两个操作数 全不是 对象,NaN 单独说,在执行相等比较之前,会先调用 Number() 将两个操作数强制转为 Number 类型,然后进行比较 . 2 如果一个操作数是对象,另一个操作数不是,则调用对象的valueOf()方法,如果得到的值不是基本类型值,则基于返回值再调用toString方法(这个过程即ToPrimitive),用得到的基本类型值按照前面的规则进行比较. 3 如果两个操作数都是对象,则比较他们是不是同一个对象。如果两个操作数指向同一个对象,则相等操作符返回true, 否则返回false。总体原则,1和2可以合并为1点,就是如果一个对象一个其他值,这样情况也可以使用Number进行转化,暂时没有发现有情况不满足例如 [] == '' ==> 0 == 0
在js中,当运算符在运算时,如果两边数据不统一,CPU就无法计算,这时我们编译器会自动将运算符两边的数据做一个数据类型转换,转成一样的数据类型再计算!  隐式转换规则:  1.转成string类型:+(字符串连接符)  2.转成number类型:++/--(自增自减运算符)+ - * %(算术运算符)> >= < <= != == === !==(关系运算符)  3.转成boolean类型:!(逻辑非运算符)
一元操作符
var num1 = 2; var num2 = 20; var num3 = --num1 + num2 // 21
var num4 = num1+ num2 //21
var num1 = 2; var num2 = 20; var num3 = num1-- + num2 // 22
var num4 = num1+ num2 //21
var i = 1; a = (++i) + (++i) + (++i);  console.log(a)  9
var i = 1; b= (i++) + (i++) + (i++); console.log(b)   6
var num1 = 25   二进制 0000 0000 0000 0000 0000 0000 0001 1001
var num2 = ~num1      1111 1111 1111 1111 1111 1111 1110 0110
结果 -26
var result = 25 & 3. // 1
25 = 0000 0000 0000 0000 0000 0000 0001 1001
3  = 0000 0000 0000 0000 0000 0000 0000 0011
AND= 0000 0000 0000 0000 0000 0000 0000 0001
var result = 25 | 3。//27
25 = 0000 0000 0000 0000 0000 0000 0001 1001
3  = 0000 0000 0000 0000 0000 0000 0000 0011
AND= 0000 0000 0000 0000 0000 0000 0001 1011
!'' ,!NaN, !underfind, !0, !null, !false  都为 true,![] !{} false
localStorage.setItem('show', false);
console.log(localStorage.show || '显示')   false || '显示' ==> false
'' || 'abc'  ==>'abc';
1&&2 ==> 2
! 运算符的规则:: !可将变量转换成boolean类型,null、undefined、NaN以及空字符串('')取反都为true,
其余都为false。! 的优先级是大于 == 的
null == undefined true
"NaN" == NaN false
5 == NaN. false
NaN == NaN false
NaN !== NaN true
false == 0 true
true == 1 true
true == 2 false
underfined == 0 false
null == 0
"5" == 5 true
'55' == 55;       //true
false == 0;       //true
"wise" == 3;      //false ( Number("wise") -> NaN )
[] == 0;          //true  ( Number([]) -> 0 )
[] == [] 返回时 false, {} =={} 返回也是 false,原因是比较两个对象时候实际比较是两个对象指针指向哪里,左右两边相当于两个[]以及两个{},
[] == ![] 逻辑非得运算符优先级高于 ==,所以 
[] == false(!规则) ==>  
(规则1 合并) Number([]) == Nmber(false)   ==>
 0==0   返回true
这个题连个
{} ==!{} ==>  
{} == false  ->  
Number({}) == Number(false)  ->  
NaN == 0 返回 false
{} == {}, [] == [],  {} != {}, [] != [], [] == ![], {} ==!{}, [] == 0. [] != 0, null == unserfind, null != underfind, null == NaN ,underfind == NaN,  NaN == NaN ,NaN != null, NaN != underfind, NaN != NaN, "NaN" == NaN ,"NaN" != NaN
人类身份验证 - SegmentFault
JavaScript 中的相等操作符 ( 详解 [] == []、[] == ![]、{} == !{} )
JS中 [] == ![]结果为true,而 {} == !{}却为false, 追根刨底
JavaScript 中的相等操作符 ( 详解 [] == []、[] == ![]、{} == !{} )
Object.is() 与比较操作符 “===”、“==” 的区别?
使用双等号(==)进行相等判断时,如果两边的类型不一致,则会进行强制类型转化后再进行比较。
使用三等号(===)进行相等判断时,如果两边的类型不一致时,不会做强制类型准换,直接返回 false。
使用 Object.is 来进行相等判断时,一般情况下和三等号的判断相同,它处理了一些特殊的情况,比如 -0 和 +0 不再相等,两个 NaN 是相等的
+ 操作符什么时候用于字符串的拼接?
如果 + 的其中一个操作数是字符串(或者通过以上步骤最终得到字符串),则执行字符串拼接,否则执行数字加法。
左移运算符(<<)
定义: 将一个运算对象的各二进制位全部左移若干位,左边的二进制位丢弃,右边补0。
设 a=1010 1110,a = a<< 2 将a的二进制位左移2位、右补0,即得a=1011 1000。
若左移时舍弃的高位不包含1,则每左移一位,相当于该数乘以2。
6. 右移运算符(>>)
定义: 将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。
例如:a=a>>2 将a的二进制位右移2位,左补0 或者 左补1得看被移数是正还是负。
操作数每右移一位,相当于该数除以2。
7. 原码、补码、反码
上面提到了补码、反码等知识,这里就补充一下。
计算机中的有符号数有三种表示方法,即原码、反码和补码。三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位,三种表示方法各不相同。
(1)原码
原码就是一个数的二进制数。例如:10的原码为0000 1010
(2)反码
正数的反码与原码相同,如:10 反码为 0000 1010
负数的反码为除符号位,按位取反,即0变1,1变0。
例如:-10
原码:1000 1010
反码:1111 0101
(3)补码
正数的补码与原码相同,如:10 补码为 0000 1010
负数的补码是原码除符号位外的所有位取反即0变1,1变0,然后加1,也就是反码加1。
例如:-10
原码:1000 1010
反码:1111 0101
补码:1111 0110。

== 操作符的强制类型转换规则

一 基本数据类型考点

1 基本数据类型成员方法考察,自己实现数据的方法,数组不改变api, symbol 使用

基础类型:undefined 、 null、number、string、boolean、symbol(唯一,可以用属性名,不可覆盖) 
引用类型:object对象类型(Object 、Array 、Function 、Data)
number api ,string api , symbol的使用场景,object 方法 ,array方法,function
数据类型在内存中保存,堆栈的关系
no people:Array String方法总结
字符串方法:str.slice(start,end),不会改变原来的值 str.subString(start,end) str.concat(str1,str2....). str.split(分隔符, 分隔片段数量)
Number 的特性:
Number([]) 0 
Number({}) NaN 
Number(false) 0 
Number(NaN) NaN
 Number("a") NaN 
Number("11") 11
Number("")) 0  
Number(null)
var a; console.log(Number(a))  NaN
Number [] '' null false 都为0 Number('11') 11  其他都为NaN
其中 Symbol 和 BigInt 是ES6 中新增的数据类型:
 什么是尾调用,使用尾调用有什么好处?
尾调用指的是函数的最后一步调用另一个函数。代码执行是基于执行栈的,所以当在一个函数里调用另一个函数时,会保留当前的执行上下文,然后再新建另外一个执行上下文加入栈中。使用尾调用的话,因为已经是函数的最后一步,所以这时可以不必再保留当前的执行上下文,从而节省了内存,这就是尾调用优化。但是 ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。


数组方面的问题

1 for 循环和 forEach循环的区别在于?
for循环为原生语法糖,无上下文,而forEach则是Array上的方法。
for 循环可以通过break, continue进行中途退出循环,而forEach则不行。
2 不改变原数组的方法有哪些?
数组方法:slice(start,end),返回值是截取的数组, 不改变原属组。splice(start,num,addEle,....) 是为了解决 获取截取后的数组因为slice为切片,单纯截取的功能,返回值是 要删除的元素 数组,会改变原数组 .  forEach 返回 underfined
不改变原属组的方法: slice,concat,flat, join ,判断存在方法,循环方法
3 类数组和真实数组转换方法?
Array.prototye.slice.call(arrayLike)
array = Array.from(arrayLike)
P.S. 伪数组与真数组的区别就是:伪数组的原型链中没有 Array.prototype,而真数组的原型链中有 Array.prototype。因此伪数组没有 pop、join 等属性。

js内存模型

这些数据可以分为原始数据类型和引用数据类型:
栈:原始数据类型(Undefined、Null、Boolean、Number、String)
堆:引用数据类型(对象、数组和函数)
两种类型的区别在于存储位置的不同:
引用类型的值是同时保存在栈内存和堆内存中的对象。
原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
堆和栈的概念存在于数据结构和操作系统内存中,在数据结构中:
在数据结构中,栈中数据的存取方式为先进后出。
堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。
在操作系统中,内存被分为栈区和堆区:
栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
堆区内存一般由开发着分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。
let obj = {a: 0};
function fun(obj){
   obj.a =1;
   obj = {a:1}; // 相当于新建一个obj 和上面传入的并不一样
   obj.b = 2;
   console.log(obj)
fun(obj)
console.log(obj)

JS 类型转换

1 原始值转布尔
在 JavaScript 中,只有 6 种值可以被转换成 false,其他都会被转换成 true。
console.log(Boolean()) // false
console.log(Boolean(false)) // false
console.log(Boolean(undefined)) // false
console.log(Boolean(null)) // false
console.log(Boolean(+0)) // false
console.log(Boolean(-0)) // false
console.log(Boolean(NaN)) // false
console.log(Boolean("")) // false
console.log(Boolean({})) // true
console.log(Boolean([])) // true
其他值到布尔类型的值的转换规则?
以下这些是假值: • undefined • null • false • +0、-0 和 NaN • ""
假值的布尔强制类型转换结果为 false。从逻辑上说,假值列表以外的都应该是真值。
2 原始值转数字
console.log(Number()) // +0
console.log(Number(undefined)) // NaN
console.log(Number(null)) // +0
Number(NaN) NaN
Number([]) 0 
Number({}) NaN 
console.log(Number(false)) // +0
console.log(Number(true)) // 1
console.log(Number("123")) // 123
console.log(Number("-123")) // -123
console.log(Number("1.2")) // 1.2
console.log(Number("000123")) // 123
console.log(Number("-000123")) // -123
console.log(Number("0x11")) // 17
console.log(Number("")) // 0
console.log(Number(" ")) // 0
console.log(Number("123 123")) // NaN
console.log(Number("foo")) // NaN
console.log(Number("100a")) // NaN
3 原始值转字符
如果 String 函数不传参数,返回空字符串,如果有参数,调用 ToString(value),而 ToString 也给了一个对应的结果表。表如下:
Undefined	"undefined"
Null	"null"
Boolean	如果参数是 true,返回 "true"。参数为 false,返回 "false"
Number	又是比较复杂,可以看例子
String	返回与之相等的值
4 ToPrimitive
那接下来就要看看 ToPrimitive 了,在了解了 toString 和 valueOf 方法后,这个也很简单。
让我们看规范 9.1,函数语法表示如下:
ToPrimitive(input[, PreferredType])
第一个参数是 input,表示要处理的输入值。
第二个参数是 PreferredType,非必填,表示希望转换成的类型,有两个值可以选,Number 或者 String。
当不传入 PreferredType 时,如果 input 是日期类型,相当于传入 String,否则,都相当于传入 Number。
如果传入的 input 是 Undefined、Null、Boolean、Number、String 类型,直接返回该值。
如果是 ToPrimitive(obj, Number),处理步骤如下:
如果 obj 为 基本类型,直接返回
否则,调用 valueOf 方法,如果返回一个原始值,则 JavaScript 将其返回。
否则,调用 toString 方法,如果返回一个原始值,则 JavaScript 将其返回。
否则,JavaScript 抛出一个类型错误异常。
如果是 ToPrimitive(obj, String),处理步骤如下:
如果 obj为 基本类型,直接返回
否则,调用 toString 方法,如果返回一个原始值,则 JavaScript 将其返回。
否则,调用 valueOf 方法,如果返回一个原始值,则 JavaScript 将其返回。
否则,JavaScript 抛出一个类型错误异常。
对象转字符串
所以总结下,对象转字符串(就是 Number() 函数)可以概括为:
如果对象具有 toString 方法,则调用这个方法。如果他返回一个原始值,JavaScript 将这个值转换为字符串,并返回这个字符串结果。
如果对象没有 toString 方法,或者这个方法并不返回一个原始值,那么 JavaScript 会调用 valueOf 方法。如果存在这个方法,则 JavaScript 调用它。如果返回值是原始值,JavaScript 将这个值转换为字符串,并返回这个字符串的结果。
否则,JavaScript 无法从 toString 或者 valueOf 获得一个原始值,这时它将抛出一个类型错误异常。
对象转数字
对象转数字的过程中,JavaScript 做了同样的事情,只是它会首先尝试 valueOf 方法
如果对象具有 valueOf 方法,且返回一个原始值,则 JavaScript 将这个原始值转换为数字并返回这个数字
否则,如果对象具有 toString 方法,且返回一个原始值,则 JavaScript 将其转换并返回。
否则,JavaScript 抛出一个类型错误异常。
举个例子:
console.log(Number({})) // NaN
console.log(Number({a : 1})) // NaN
console.log(Number([])) // 0
console.log(Number([0])) // 0
console.log(Number([1, 2, 3])) // NaN
console.log(Number(function(){var a = 1;})) // NaN
console.log(Number(/\d+/g)) // NaN
console.log(Number(new Date(2010, 0, 1))) // 1262275200000
console.log(Number(new Error('a'))) // NaN
注意,在这个例子中,[] 和 [0] 都返回了 0,而 [1, 2, 3] 却返回了一个 NaN。我们分析一下原因:
当我们 Number([]) 的时候,先调用 [] 的 valueOf 方法,此时返回 [],因为返回了一个对象而不是原始值,所以又调用了 toString 方法,此时返回一个空字符串,接下来调用 ToNumber 这个规范上的方法,参照对应表,转换为 0, 所以最后的结果为 0。
而当我们 Number([1, 2, 3]) 的时候,先调用 [1, 2, 3] 的 valueOf 方法,此时返回 [1, 2, 3],再调用 toString 方法,此时返回 1,2,3,接下来调用 ToNumber,参照对应表,因为无法转换为数字,所以最后的结果为 NaN。

Symbol和BigInt 问题

Symbol 代表创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题。
BigInt 是一种数字类型的数据,它可以表示任意精度格式的整数,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围。
JavaScript中Number.MAX_SAFE_INTEGER表示最⼤安全数字,计算结果是9007199254740991,即在这个数范围内不会出现精度丢失(⼩数除外)。但是⼀旦超过这个范围,js就会出现计算不准确的情况,这在⼤数计算的时候不得不依靠⼀些第三⽅库进⾏解决,因此官⽅提出了BigInt来解决此问题。

2 变量类型判断,typeof, instanceof实现原理缺陷,Object.prototype.toString.call(),

1 typeof
typeof 记住特殊的两个 null和数组 不可以判断,其他类型都可以判断
typeof null == 'object'(小写)(原因,因为null的机器码是000,和对象机器码相同) typeof  [] == 'object';
typeof NaN; // "number"
000:对象 1:整数 010:浮点数 100:字符串 110:布尔
有 2 个值比较特殊: undefined:用 - (−2^30)表示。 null:对应机器码的 NULL 指针,一般是全零。
2 instanceof
检测某个实例是否属于某个类,测试构造函数的prototype属性是否出现在对象的原型链中的任何位置
instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
instanceof用法: A(实例) instanceof B(构造函数);实现原理主要是判断A._proto_属相是否指向 B的prototype 即 原型对象。链接
基本数据值都不可以用instanceof来检测
用instanceof检测的时候,只要当前的这个类在实例的原型链上(可以通过原型链__proto__找到它),检测出来的结果都是true
var oDiv = document.getElementById("div1");
//HTMLDivElement->HTMLElement->Element->Node->EventTarget->Object
console.log(oDiv instanceof HTMLDivElement);//->true
console.log(oDiv instanceof Node);//->true
console.log(oDiv instanceof Object);//->true  
 在类的原型继承中,instanceof检测出来的结果其实是不准确的 
 function Fn() {
    Fn.prototype = new Array;//->Fn子类继承了Array这个父类中的属性和方法
    var f = new Fn;
    console.log(f instanceof Array);//->true
function myInstanceof(left, right) {
  // 获取对象的原型
  let proto = Object.getPrototypeOf(left)
  // 获取构造函数的 prototype 对象
  let prototype = right.prototype; 
  // 判断构造函数的 prototype 对象是否在对象的原型链上
  while (true) {
    if (!proto) return false;
    if (proto === prototype) return true;
    // 如果没有找到,就继续从其原型上找,Object.getPrototypeOf方法用来获取指定对象的原型
    proto = Object.getPrototypeOf(proto);
3 Object.prototype.toString.call
Object.prototype.toString.call(value) ->找到Object原型上的toString方法,让方法执行,并且让方法中的this变为value(value->就是我们要检测数据类型的值)
Object.prototype.toString常用来判断对象值属于哪种内置属性,它返回一个JSON字符串——"[object 数据类型]"。
var toString = Object.prototype.toString;
console.log(toString.call(1));//[object Number]
console.log(toString.call(undefined));//[object Undefined]
console.log(toString.call(null));//[object Null]
console.log(toString.call(false));//[object Boolean]
console.log(toString.call("s"));//[object String]
console.log(toString.call({}));//[object Object]
console.log(toString.call(/[a]/g));//[object RegExp]
Object.prototype.toString.call([]) // [object Array]
Object.prototype.toString.call(NaN) // [object Number]
同样是检测对象obj调用toString方法,obj.toString()的结果和Object.prototype.toString.call(obj)的结果不一样,这是为什么?
这是因为toString是Object的原型方法,而Array、function等类型作为Object的实例,都重写了toString方法。不同的对象类型调用toString方法时,根据原型链的知识,调用的是对应的重写之后的toString方法(function类型返回内容为函数体的字符串,Array类型返回元素组成的字符串…),而不会去调用Object上原型toString方法(返回对象的具体类型),所以采用obj.toString()不能得到其对象类型,只能将obj转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用Object原型上的toString方法。

3 深拷贝和浅拷贝的实现以及缺陷

如何写出一个惊艳面试官的深拷贝? - SegmentFault 思否

1 实现浅拷贝的方法
 = 赋值操作
展开运算符 {...a}
object.assign()
map, filter, reduce , Array.slice
展开运算符:
const a = {
  en: 'Bye',
  de: 'Tschüss'
let b = {...a}
b.de = 'Ciao'
console.log(b.de) // Ciao
console.log(a.de) // Tschüss
const a = {
  foods: {
    dinner: 'Pasta'
let b = {...a}
b.foods.dinner = 'Soup' // changes for both objects
console.log(b.foods.dinner) // Soup
console.log(a.foods.dinner) // Soup
如果对象内部包含对象,那么内部嵌套的对象也不会被拷贝,因为它们只是引用。因此改变嵌套对象,所有的实例中的嵌套对象的属性都会被改变。所以说上面的场景全部都只实现了浅拷贝。
assign:
const a = {
  en: 'Bye',
  de: 'Tschüss',
  cc: { 'name': 'aa' }
let b = Object.assign({}, a)
b.de = 'Ciao'
b.cc.name = 'bb'
console.log(b.de) // Ciao
console.log(a.de) // Tschüss
console.log(b.cc) //  { name: 'bb'}
console.log(a.cc) //  { name: 'bb'}
如果对象内部包含对象,那么内部嵌套的对象也不会被拷贝,因为它们只是引用。因此改变嵌套对象,所有的实例中的嵌套对象的属性都会被改变。所以说上面的场景全部都只实现了浅拷贝。
自己只实现拷贝一层的浅拷贝方法:
// 只复制第一层的浅拷贝
function simpleCopy(obj1) {
   var obj2 = Array.isArray(obj1) ? [] : {};
   for (let i in obj1) {
   obj2[i] = obj1[i];
   return obj2;
var obj1 = {
   a: 1,
   b: 2,
var obj2 = simpleCopy(obj1);
obj2.a = 3;
obj2.c.d = 4;
alert(obj1.a); // 1
alert(obj2.a); // 3
alert(obj1.c.d); // 4
alert(obj2.c.d); // 4
function shallowClone(copyObj){
var obj = {}
for(var i in copyObj){
  obj[i] = copyObj[i]
return obj
var x = {
a: 1,
b: { f: {g: 1}},
c: [1,2,3]
let y = shallowClone(x)
y.a=2  这时x不受影响,当修改y.c.push(4),这样x会受影响,因为浅拷贝只是针对一层的拷贝。
2 实现深拷贝方法
JSON.stringify与JSON.parse
lodash _.clone
Array的slice和concat方法 返回新数组,只是对于数组中元素为基本类型来说是深拷贝,如果为对象,则为浅拷贝
自己实现深拷贝以及有哪些缺点
JSON.stringify 和JSON.parse
const a = {
  foods: {
    dinner: 'Pasta'
let b = JSON.parse(JSON.stringify(a))
b.foods.dinner = 'Soup'
console.log(b.foods.dinner) // Soup
console.log(a.foods.dinner) // Pasta
undefined、任意的函数以及symbol作为对象属性值时JSON.stringify()对跳过(忽略)它们进行序列化
undefined、任意的函数以及symbol作为数组元素值时,JSON.stringify()将会将它们序列化为null 
undefined、任意的函数以及symbol被JSON.stringify()作为单独的值进行序列化时,都会返回undefined 
布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
序列化可枚举的属性。
对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。
let obj = {
    name: 'muyiy',
    a: undefined,
    b: Symbol('muyiy'),
    c: function() {}
console.log(obj);
//     name: "muyiy", 
//     a: undefined, 
//  b: Symbol(muyiy), 
//  c: ƒ ()
let b = JSON.parse(JSON.stringify(obj));
console.log(b);
// {name: "muyiy"}
let obj = {
    a: 1,
        c: 2,
obj.a = obj.b;
obj.b.c = obj.a;
let b = JSON.parse(JSON.stringify(obj));
// Uncaught TypeError: Converting circular structure to JSON
slice.concat 局限性
var arr1 = [{"name":"weifeng"},{"name":"boy"}];//原数组
var arr2 = [].concat(arr1);//拷贝数组
arr1[1].name="girl";
console.log(arr1);// [{"name":"weifeng"},{"name":"girl"}]
console.log(arr2);//[{"name":"weifeng"},{"name":"girl"}
自己实现的
function deepCopy(obj1) {
      var obj2 = Array.isArray(obj1) ? [] : {};
      if (obj1 && typeof obj1 === "object") {
        for (var i in obj1) {
          var prop = obj1[i]; // 避免相互引用造成死循环,如obj1.a=obj
          if (prop == obj1) {
            continue;
          if (obj1.hasOwnProperty(i)) {
            // 如果子属性为引用数据类型,递归复制
            if (prop && typeof prop === "object") {
              obj2[i] = (prop.constructor === Array) ? [] : {};
              arguments.callee(prop, obj2[i]); // 递归调用
            } else {
              // 如果是基本数据类型,只是简单的复制
              obj2[i] = prop;
      return obj2;
    var obj1 = {
      a: 1,
      b: 2,
    var obj2 = deepCopy(obj1);
    obj2.a = 3;
    obj2.c.d = 4;
    alert(obj1.a); // 1
    alert(obj2.a); // 3
    alert(obj1.c.d); // 3
    alert(obj2.c.d); // 4
https://www.jianshu.com/p/cf1e9d7e94fb
www.jianshu.com

4 基本能数据类型面试题

什么是 JavaScript 中的包装类型?
在 JavaScript 中,基本类型是没有属性和方法的,但是为了便于操作基本类型的值,在调用基本类型的属性或方法时 JavaScript 会在后台隐式地将基本类型的值转换为对象,如:
const a = "abc";
a.length; // 3
a.toUpperCase(); // "ABC"
在访问'abc'.length时,JavaScript 将'abc'在后台转换成String('abc'),然后再访问其length属性。
JavaScript也可以使用Object函数显式地将基本类型转换为包装类型:
var a = 'abc'
Object(a) // String {"abc"}
也可以使用valueOf方法将包装类型倒转成基本类型:
var a = 'abc'
var b = Object(a)
var c = b.valueOf() // 'abc'
看看如下代码会打印出什么:
var a = new Boolean( false );
if (!a) {
	console.log( "Oops" ); // never runs
答案是什么都不会打印,因为虽然包裹的基本类型是false,但是false被包裹成包装类型后就成了对象,所以其非值为false,所以循环体中的内容不会运行。
JavaScript 中如何进行隐式类型转换?
首先要介绍ToPrimitive方法,这是 JavaScript 中每个值隐含的自带的方法,用来将值 (无论是基本类型值还是对象)转换为基本类型值。如果值为基本类型,则直接返回值本身;如果值为对象,其看起来大概是这样:
* @obj 需要转换的对象
* @type 期望的结果类型
ToPrimitive(obj,type)
type的值为number或者string。
(1)当type为number时规则如下:
调用obj的valueOf方法,如果为原始值,则返回,否则下一步;
调用obj的toString方法,后续同上;
抛出TypeError 异常。
(2)当type为string时规则如下:
调用obj的toString方法,如果为原始值,则返回,否则下一步;
调用obj的valueOf方法,后续同上;
抛出TypeError 异常。
可以看出两者的主要区别在于调用toString和valueOf的先后顺序。默认情况下:
如果对象为 Date 对象,则type默认为string;
其他情况下,type默认为number。
总结上面的规则,对于 Date 以外的对象,转换为基本类型的大概规则可以概括为一个函数:
var objToNumber = value => Number(value.valueOf().toString())
objToNumber([]) === 0
objToNumber({}) === NaN
而 JavaScript 中的隐式类型转换主要发生在+、-、*、/以及==、>、<这些运算符之间。而这些运算符只能操作基本类型值,所以在进行这些运算前的第一步就是将两边的值用ToPrimitive转换成基本类型,再进行操作。
以下是基本类型的值在不同操作符的情况下隐式转换的规则 (对于对象,其会被ToPrimitive转换成基本类型,所以最终还是要应用基本类型转换规则):
+操作符的两边有至少一个string类型变量时,两边的变量都会被隐式转换为字符串;其他情况下两边的变量都会被转换为数字。
1 + '23' // '123'
 1 + false // 1 
 1 + Symbol() // Uncaught TypeError: Cannot convert a Symbol value to a number
 '1' + false // '1false'
 false + true // 1
-、*、\操作符
NaN也是一个数字
1 * '23' // 23
 1 * false // 0
 1 / 'aa' // NaN
对于==操作符
操作符两边的值都尽量转成number:
3 == true // false, 3 转为number为3,true转为number为1
'0' == false //true, '0'转为number为0,false转为number为0
'0' == 0 // '0'转为number为0
对于<和>比较符
如果两边都是字符串,则比较字母表顺序:
'ca' < 'bd' // false
'a' < 'b' // true
其他情况下,转换为数字再比较:
'12' < 13 // true
false > -1 // true
以上说的是基本类型的隐式转换,而对象会被ToPrimitive转换为基本类型再进行转换:
var a = {}
a > 2 // false
其对比过程如下:
a.valueOf() // {}, 上面提到过,ToPrimitive默认type为number,所以先valueOf,结果还是个对象,下一步
a.toString() // "[object Object]",现在是一个字符串了
Number(a.toString()) // NaN,根据上面 < 和 > 操作符的规则,要转换成数字
NaN > 2 //false,得出比较结果
var a = {name:'Jack'}
var b = {age: 18}
a + b // "[object Object][object Object]"
运算过程如下:
a.valueOf() // {},上面提到过,ToPrimitive默认type为number,所以先valueOf,结果还是个对象,下一步
a.toString() // "[object Object]"
b.valueOf() // 同理
b.toString() // "[object Object]"
a + b // "[object Object][object Object]"
3 forEach和map方法有什么区别
这方法都是用来遍历数组的,两者区别如下:
forEach()方法会针对每一个元素执行提供的函数,对数据的操作会改变原数组,该方法没有返回值;
map()方法不会改变原数组的值,返回一个新数组,新数组中的值为原数组调用函数处理之后的值;
4 如何使用for...of遍历对象
for…of是作为ES6新增的遍历方式,允许遍历一个含有iterator接口的数据结构(数组、对象等)并且返回各项的值,普通的对象用for..of遍历是会报错的。
如果需要遍历的对象是类数组对象,用Array.from转成数组即可。
var obj = {
    0:'one',
    1:'two',
    length: 2
obj = Array.from(obj);
for(var k of obj){
    console.log(k)
5 for...in和for...of的区别
for…of 是ES6新增的遍历方式,允许遍历一个含有iterator接口的数据结构(数组、对象等)并且返回各项的值,和ES3中的for…in的区别如下
for…of 遍历获取的是对象的键值,for…in 获取的是对象的键名;
for… in 会遍历对象的整个原型链,性能非常差不推荐使用,而 for … of 只遍历当前对象不会遍历原型链;
对于数组的遍历,for…in 会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for…of 只返回数组的下标对应的属性值;
总结: for...in 循环主要是为了遍历对象而生,不适用于遍历数组;for...of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象。
6 常见的DOM操作有哪些
1)DOM 节点的获取
DOM 节点的获取的API及使用:
getElementById // 按照 id 查询
getElementsByTagName // 按照标签名查询
getElementsByClassName // 按照类名查询
querySelectorAll // 按照 css 选择器查询
// 按照 id 查询
var imooc = document.getElementById('imooc') // 查询到 id 为 imooc 的元素
// 按照标签名查询
var pList = document.getElementsByTagName('p')  // 查询到标签为 p 的集合
console.log(divList.length)
console.log(divList[0])
// 按照类名查询
var moocList = document.getElementsByClassName('mooc') // 查询到类名为 mooc 的集合
// 按照 css 选择器查询
var pList = document.querySelectorAll('.mooc') // 查询到类名为 mooc 的集合
2)DOM 节点的创建
创建一个新节点,并把它添加到指定节点的后面。 已知的 HTML 结构如下:
    <title>DEMO</title>
  </head>
    <div id="container"> 
      <h1 id="title">我是标题</h1>
  </body>
</html>
要求添加一个有内容的 span 节点到 id 为 title 的节点后面,做法就是:
// 首先获取父节点
var container = document.getElementById('container')
// 创建新节点
var targetSpan = document.createElement('span')
// 设置 span 节点的内容
targetSpan.innerHTML = 'hello world'
// 把新创建的元素塞进父节点里去
container.appendChild(targetSpan)
3)DOM 节点的删除
删除指定的 DOM 节点, 已知的 HTML 结构如下:
    <title>DEMO</title>
  </head>
    <div id="container"> 
      <h1 id="title">我是标题</h1>
  </body>
</html>
需要删除 id 为 title 的元素,做法是:
// 获取目标元素的父元素
var container = document.getElementById('container')
// 获取目标元素
var targetNode = document.getElementById('title')
// 删除目标元素
container.removeChild(targetNode)
或者通过子节点数组来完成删除:
// 获取目标元素的父元素var container = document.getElementById('container')// 获取目标元素var targetNode = container.childNodes[1]// 删除目标元素container.removeChild(targetNode)
4)修改 DOM 元素
修改 DOM 元素这个动作可以分很多维度,比如说移动 DOM 元素的位置,修改 DOM 元素的属性等。
将指定的两个 DOM 元素交换位置, 已知的 HTML 结构如下:
    <title>DEMO</title>
  </head>
    <div id="container"> 
      <h1 id="title">我是标题</h1>
      <p id="content">我是内容</p>
  </body>
</html>
现在需要调换 title 和 content 的位置,可以考虑 insertBefore 或者 appendChild:
// 获取父元素
var container = document.getElementById('container')   
// 获取两个需要被交换的元素
var title = document.getElementById('title')
var content = document.getElementById('content')
// 交换两个元素,把 content 置于 title 前面
container.insertBefore(content, title)
6 对类数组对象的理解,如何转化为数组
一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象,类数组对象和数组类似,但是不能调用数组的方法。常见的类数组对象有 arguments 和 DOM 方法的返回结果,函数参数也可以被看作是类数组对象,因为它含有 length属性值,代表可接收的参数个数。
常见的类数组转换为数组的方法有这样几种:
通过 call 调用数组的 slice 方法来实现转换
Array.prototype.slice.call(arrayLike);
通过 call 调用数组的 splice 方法来实现转换
Array.prototype.splice.call(arrayLike, 0);
通过 apply 调用数组的 concat 方法来实现转换
Array.prototype.concat.apply([], arrayLike);
通过 Array.from 方法来实现转换
Array.from(arrayLike);

二 作用域,作用域链,this,闭包,箭头函数的面试题

this指向,什么是闭包,箭头函数

变量提升

JavaScript为什么要进行变量提升,它导致了什么问题?
变量提升的表现是,无论在函数中何处位置声明的变量,好像都被提升到了函数的首部,可以在变量声明前访问到而不会报错。
造成变量声明提升的本质原因是 js 引擎在代码执行前有一个解析的过程,创建了执行上下文,初始化了一些代码执行时需要用到的对象。当访问一个变量时,
会到当前执行上下文中的作用域链中去查找,而作用域链的首端指向的是当前执行上下文的变量对象,这个变量对象是执行上下文的一个属性,它包含了函数的形参、所有的函数和变量声明,这个对象的是在代码解析的时候创建的。
首先要知道,JS在拿到一个变量或者一个函数的时候,会有两步操作,即解析和执行。
在解析阶段,JS会检查语法,并对函数进行预编译。解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来,变量先赋值为undefined,函数先声明好可使用。在一个函数执行之前,也会创建一个函数执行上下文环境,跟全局执行上下文类似,不过函数执行上下文会多出this、arguments和函数的参数。
全局上下文:变量定义,函数声明
函数上下文:变量定义,函数声明,this,arguments
在执行阶段,就是按照代码的顺序依次执行。
那为什么会进行变量提升呢?主要有以下两个原因:
容错性更好
(1)提高性能
在JS代码执行之前,会进行语法检查和预编译,并且这一操作只进行一次。这么做就是为了提高性能,如果没有这一步,那么每次执行代码前都必须重新解析一遍该变量(函数),而这是没有必要的,因为变量(函数)的代码并不会改变,解析一遍就够了。
在解析的过程中,还会为函数生成预编译代码。在预编译时,会统计声明了哪些变量、创建了哪些函数,并对函数的代码进行压缩,去除注释、不必要的空白等。这样做的好处就是每次执行函数时都可以直接为该函数分配栈空间(不需要再解析一遍去获取代码中声明了哪些变量,创建了哪些函数),并且因为代码压缩的原因,代码执行也更快了。
(2)容错性更好
变量提升可以在一定程度上提高JS的容错性,看下面的代码:
a = 1;var a;console.log(a);
如果没有变量提升,这两行代码就会报错,但是因为有了变量提升,这段代码就可以正常执行。
虽然,在可以开发过程中,可以完全避免这样写,但是有时代码很复杂的时候。可能因为疏忽而先使用后定义了,这样也不会影响正常使用。由于变量提升的存在,而会正常运行。
解析和预编译过程中的声明提升可以提高性能,让函数可以在执行时预先为变量分配栈空间
声明提升还可以提高JS代码的容错性,使一些不规范的代码也可以正常执行
变量提升虽然有一些优点,但是他也会造成一定的问题,在ES6中提出了let、const来定义变量,它们就没有变量提升的机制。下面看一下变量提升可能会导致的问题:
var tmp = new Date();
function fn(){
	console.log(tmp);
	if(false){
		var tmp = 'hello world';
fn();  // undefined
在这个函数中,原本是要打印出外层的tmp变量,但是因为变量提升的问题,内层定义的tmp被提到函数内部的最顶部,相当于覆盖了外层的tmp,所以打印结果为undefined。
var tmp = 'hello world';
for (var i = 0; i < tmp.length; i++) {
	console.log(tmp[i]);
console.log(i); // 11
由于遍历时定义的i会变量提升成为一个全局变量,在函数结束之后不会被销毁,所以打印出来11。

作用域

1. 函数内访问外层,内有变量访问内变量,内无变量,访问外变量,这种变量称为自由变量,自由变量的取值要到创建那个函数作用域中去找,而不是调用这个函数作用域中(错过,布本),称为静态作用域。创建时候确定。

2. 函数内覆盖函数外问题: 如果函数内定义不带var(属于window.变量),函数外也有同名变量,则函数内的值覆盖函数外的值,并且覆盖是紧挨着(上一个变量)(错过·水滴)

3. 函数外不可以访问函数内,不管函数内是带var或者不带var,需要注意是 if或者switch语句,或者是for和while语句不会创建新作用域,所以这种是可以访问的。

闭包的两个应用场景

  1. 函数作为返回值
  2. 函数作为参数传递

函数提升以及变量提升

  1. 函数提升大于变量提升
  2. 函数提升只有函数声明式可以提升,表达式不会提升
  3. 函数提升知识函数名提升,真正执行还是按照原来顺序

例如:函数表达式 var test = function(){} 函数声明 function test(){}

闭包

闭包的实现原理和作用,可以列举几个开发中闭包的实际应用

1 闭包是什么?

闭包是指有权访问另一个函数作用域变量的 函数 ,创建闭包的通常方式,是在一个函数内部创建另一个函数。你可以说内部函数访问内部函数外的变量,导致变量不会被GC机制回收。

浏览器的回收机制

在我们普通浏览器一般使用的是标记清除方法,老的版本的ie浏览器使用的是引用计数。

哪些场景造成内存泄漏?

  • 意外的全局变量
  • 闭包
  • 循环引用

2 闭包的原理是 作用域链


闭包的两大作用:

  • 减少全局变量;
  • 将函数中的遍历阿静的值存储在内存中
  • 封装(闭包是函数式编程及其核心思想“Lambda 计算法”(Lambda Calculus)的必备基本设定。); 缺点:
  • 比普通函数更占用内存,会导致网页性能变差,在IE下容易造成内存泄露。 如何解决闭包造成的内存泄漏? jb51.net/article/78597.
  • 意外的全局变量
  • 被遗忘的计时器或回调函数
  • 闭包 cnblogs.com/leftJS/p/11

闭包应用场景有哪些?

  • 保存变量
  • 循环保存变量 1 构造函数调用模式 new 一个函数出来,那么函数中this, 如果没有显式返回对象或者函数,指向才是返回生成的新对象,如果有返回则指向返回的函数或者对象。
function add() {
    const a = 1;
    const addOne = function(b) { return b + a; }
    return addOne;
const addOne = add();
console.log(addOne(1)); // 2

jianshu.com/p/9fc2e3ee4



2 如果函数式对象的一个方法,并且通过点出发,那么this指向谁点方法的,就指向谁. 函数没有作为对象的方法调用,而是最后通过window调用,则指向window

3 原型链的调用中 指向新对象

4 箭头函数调用形式

1 箭头函数的this指向谁?箭头函数解决什么问题?实现原理是什么?编译出来是什么?箭头函数与普通函数区别?

  • 箭头函数没有原型属性prototype
  • 箭头函数作为匿名函数,是不能作为构造函数的, 不能使用new
  • 箭头函数不绑定arguments,取而代之用rest参数…解决 let test3=(...a)=>{console.log(a[1])}
  • call apply不改变箭头函数指向 箭头函数this指向 向该函数构造时的环境, 箭头函数的this是在定义函数时绑定的,不是在执行过程中绑定的。简单的说,函数在定义时,this就继承了定义函数的对象。
  • 箭头函数会捕获其所在上下文的 this 值,作为自己的 this 值

箭头函数和类普通函数、constructor 里 bind 的函数有什么区别?

  • 普通函数: 在 babel 编译后,会被放在函数的 prototype 上
  • constructor 里 bind 的函数: 在编译后,它不仅会被放在函数的 prototype 里,而且每一次实例化,都会产生一个绑定当前实例上下文的变量(this.b = this.b.bind(this))。
  • 箭头函数:在 babel 编译后,每一次实例化的时候,都会调用 defineProperty 将箭头函数内容绑定在当前实例上下文上。
  • 箭头函数解决this作用域问题, 箭头函数this指向有以下几种情况:

普通函数this指向

  1. 指向window情况,直接调用,二级调用, setTimeout
  2. 指向调用者
  3. new情况: 如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例。
  4. call apply bind影响this的指向指向改变的那个对象
  5. 箭头函数this指向 上下文this, 定义时所在的对象

三 原型链和继承方式

1 原型链

1 function Foo(){} ====> 这是构造函数(用来初始化新创建的对象的函数叫做构造函数)

  • 2 var f1 = new Foo ====> 实例对象 (通过构造函数的new操作创建的对象是实例对象,可以一个构造函数,构造多个实例对象)
  • 3 Foo.prototype ====>原型对象(实例对象的原型对象);
  • 4 原型对象(Foo.prototype)的constructor 属性 ====> 原型对象对应的构造函数(Foo)
  • Foo.prototype.constructor === Foo
  • 5 由于实例对象继承了原型对象 所以 实例对象( f1 )的 constructor 属性 ====>原型对象对应的构造函数
  • f1.constructor === Foo
  • 6 实例对象(f1)的 _proto_ 属性 指向 原型对象(Foo.prototype)
  • f1.__proto__ === Foo.prototype

实例对象f1是通过构造函数Foo()的new操作创建的。构造函数Foo()的原型对象Foo.prototype;

实例对象f1通过__proto__属性也指向原型对象Foo.prototype

function Foo(){};
var f1 = new Foo;
console.log(f1.__proto === Foo.prototype);//true

实例对象f1本身并没有constructor属性,但它可以继承原型对象Foo.prototype的constructor属性

function Foo(){};
var f1 = new Foo;console.log(Foo.prototype.constructor === Foo);//true
console.log(f1.constructor === Foo);//true
console.log(f1.hasOwnProperty('constructor'));//false
  • 1 Foo.prototype是f1的原型对象,同时它也是实例对象(任何对象都可以看做是通过Object()构造函数的new操作实例化的对象) 实例对象的_proto_ = 原型对象的.prototype,
  • Foo.prototype.__proto__ === Object.prototype
  • 2 Foo.prototype作为实例对象,它的构造函数是Object(),原型对象是Object.prototype
  • 3 实例对象的constructor 等于原型中的构造函数Foo
  • 4 关于实例对象的两点
  • 1 实例对象的_proto_ = 原型对象的.prototype,
  • 2 实例对象的constructor 等于原型中的构造函数Foo

shangmayuan.com/a/d89ec

2 继承的方式及原理

  • 原型链继承
  • 构造继承
  • 组合继承
  • 原型式继承
  • 寄生式继承
  • 寄生组合继承

JS继承的实现方式

既然要实现继承,那么首先我们得有一个父类,代码如下:

// 定义一个动物类
function Animal (name) {
  // 属性
  this.name = name || 'Animal';
  // 实例方法
  this.sleep = function(){
    console.log(this.name + '正在睡觉!');
// 原型方法
Animal.prototype.eat = function(food) {
  console.log(this.name + '正在吃:' + food);
};

1、原型链继承

核心: 将父类的实例作为子类的原型

function Cat(){ 
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.eat('fish'));
console.log(cat.sleep());
console.log(cat instanceof Animal); //true 
console.log(cat instanceof Cat); //true

特点:

  1. 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
  2. 父类新增原型方法/原型属性,子类都能访问到
  3. 简单,易于实现

缺点:

  1. 要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中
  2. 无法实现多继承
  3. 来自原型对象的所有属性被所有实例共享(来自原型对象的引用属性是所有实例共享的)(详细请看附录代码: 示例1)
  4. 创建子类实例时,无法向父类构造函数传参

1 创建子类实例无法向父类构造函数传参。( 如果传参后,父类的属性将成为子类原型属性,不属于子类实例属性不符合面向对象规范 )

2 由于子类原型中包含父类的属性,子类所有实例都共享这些属性,当某个实例修改引用类型属性,其他实例的这个引用属性也一同被修改。 链接

Child.prototype = new Parent();
var Person1 = new Child();
Person1.newArr.push("kuli");
console.log(Person1.newArr); // ["heyushuo","kebi","kuli"]
var Person2 = new Child();
console.log(Person2.newArr); // ["heyushuo","kebi","kuli"]

推荐指数:★★(3、4两大致命缺陷)

2017-8-17 10:21:43补充:感谢 MMHS 指出。缺点1中描述有误:可以在Cat构造函数中,为Cat实例增加实例属性。如果要新增原型属性和方法,则必须放在new Animal()这样的语句之后执行。

2018-9-10 00:03:45补充:感谢 IRVING_J 指出。缺点3中的描述不够充分。更正为:来自原型对象的所有属性被所有实例共享。

2、构造继承

核心: 使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

function Cat(name){
  Animal.call(this,name);
  this.name = name || 'Tom';
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true

特点:

  1. 解决了1中,子类实例共享父类引用属性的问题
  2. 创建子类实例时,可以向父类传递参数
  3. 可以实现多继承(call多个父类对象)

缺点:

  1. 实例并不是父类的实例,只是子类的实例
  2. 只能继承父类的实例属性和方法,不能继承原型属性/方法
  3. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

构造函数继承解决了原型链继承中不可以给父类传参数的问题,同时因为借用父类的属性,所以每次new一个子实例都是新的一份父实例属性副本。

缺点:在父类型构造函数( testFun )以及原型( getSuperValue )中各定义一个方法,子实例只能调用构造函数中方法( testFun )

推荐指数:★★(缺点3)

3、组合继承

核心: 通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
Cat.prototype = new Animal();
// 感谢 @学无止境c 的提醒,组合继承也是需要修复构造函数指向的。
Cat.prototype.constructor = Cat;
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true

特点:

  1. 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
  2. 既是子类的实例,也是父类的实例
  3. 不存在引用属性共享问题
  4. 可传参
  5. 函数可复用

缺点:

  1. 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

推荐指数:★★★★(仅仅多消耗了一点内存)

特点:弥补了原型链继承的引用属性共享问题以及不可以给父类传参问题,同时解决了方式二构造函数继承中 子类实例不可以继承父类原型中方法问题,既可以继承构造函数以及原型中方法。

缺点:两次调用父类构造函数,子类继承父类的属性,一组在子类实例上,一组在子类原型上,造成多余的属性,实例上的屏蔽原型上的同名属性。

3、实例继承(原型式继承)

核心: 为父类实例添加新特性,作为子类实例返回

function Cat(name){
  var instance = new Animal();
  instance.prototype = name || 'Tom';
  return instance;
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // false

特点:

  1. 不限制调用方式,不管是new 子类()还是子类(),返回的对象具有相同的效果

缺点:

  1. 实例是父类的实例,不是子类的实例
  2. 不支持多继承

原型式继承的的实现方法与普通继承的实现方法不同,原型式继承并没有使用严格意义上的构造函数,而是借 助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。

缺点:和原型链继承一样,所有实例都会继承父类原型上的属性

给原型添加属性后,所有实例都会所有实例都会继承原型上的属性; 无法实现复用 。

4、寄生式继承

给原型式继承套了个壳子。

    function object(o) {
      function F() { }
      F.prototype = o;
      return new F();
    /* 寄生式继承 */
    function createAnother(original) {
      var clone = object(original);
      clone.sayHi = function () {
        alert('hi');
      console.log(clone);
      return clone;
    var person = {
      name: 'wuyuchang',
      friends: ['yzy', 'Nick', 'Rose']
    var anotherPerson = createAnother(person);

缺点:和原型式继承一样,子实例共享父实例的属性

6、寄生组合继承

核心: 通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
(function(){
  // 创建一个没有实例方法的类
  var Super = function(){};
  Super.prototype = Animal.prototype;
  //将实例作为子类的原型
  Cat.prototype = new Super();
})();
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true
感谢 @bluedrink 提醒,该实现没有修复constructor。
Cat.prototype.constructor = Cat; // 需要修复下构造函数

特点:

  1. 堪称完美

缺点:

  1. 实现较为复杂

推荐指数:★★★★(实现复杂,扣掉一颗星)

理解es6 class构造以及继承的底层实现原理 链接

类的实现

class Parent {
  constructor(a){
    this.filed1 = a;
  filed2 = 2;
  func1 = function(){}
// babel转后
function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
var Parent = function Parent(a) {
  _classCallCheck(this, Parent);
  this.filed2 = 2;
  this.func1 = function () { };
  this.filed1 = a;
};

class的底层依然是构造函数,

  • 首先调用_classCallCheck来检查是否通过new函数来调用函数,如果有 person(实例对象) instanceof Perent 返回的肯定是true,如果没有返回时false,报错。
  • 将class内部的变量和函数赋给this
  • 执行constuctor内部的逻辑
  • return this (构造函数默认在最后我们做了)。

继承实现

class Child extends Parent {
    constructor(a,b) {
      super(a);
      this.filed3 = b;
  filed4 = 1;
  func2 = function(){}
// babel转后
var Child = function (_Parent) {
  _inherits(Child, _Parent);
  function Child(a, b) {
    _classCallCheck(this, Child);
    var _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, a));
    _this.filed4 = 1;
    _this.func2 = function () {};
    _this.filed3 = b;
    return _this;
  return Child;
}(Parent);
  • 调用_inherits函数继承父类的proptype

1) 校验父构造函数。

(2) 典型的寄生继承:用父类构造函数的proptype创建一个空对象,并将这个对象指向子类构造函数的proptype。

  • 用一个闭包保存父类引用,在闭包内部做子类构造逻辑。
  • new检查。
  • 用当前this调用父类构造函数。
  • 将行子类class内部的变量和函数赋给this。
  • 执行子类constuctor内部的逻辑。 es6实际上是为我们提供了一个“组合寄生继承”的简单写法。

super

super代表父类构造函数。super.fun1() 等同于 Parent.fun1() 或 Parent.prototype.fun1()。

super() 等同于Parent.prototype.construtor() 当我们没有写子类构造函数时:

var Child = function (_Parent) {
  _inherits(Child, _Parent);
  function Child() {
    _classCallCheck(this, Child);
    return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments));
  return Child;
}(Parent);

可见默认的构造函数中会主动调用父类构造函数,并默认把当前constructor传递的参数传给了父类。所以当我们声明了constructor后必须主动调用super(),否则无法调用父构造函数,无法完成继承。典型的例子就是Reatc的Component中,我们声明constructor后必须调用super(props),因为父类要在构造函数中对props做一些初始化操作。

四 异步和事件循环

1 异步发展史

1 回调函数 jianshu.com/p/9c49c332b

优点:1 无命名传递函数,减少变量使用 2 函数调用操作委托给另一个函数,减少代码编写

缺点:1 使用try catch 无法捕获错误(try...catch是被设计成捕获当前执行环境的异常,意思是只能捕获同步代码里面的异常,异步调用里面的异常无法捕获。 2 不能return 3 回调地狱

2 promise

优点 1 解决了回调地狱的问题 3 更好的错误捕获 reject 3 将异步操作以同步操作的流程表达出来

缺点

人类身份验证 - SegmentFault

  • 当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成) Promise Q实现此功能 Progress Notification

It's possible for promises to report their progress, e.g. for tasks that take a long time like a file upload. Not all promises will implement progress notifications, but for those that do, you can consume the progress values using a third parameter to then:

return uploadFile()
.then(function () {
    // Success uploading the file
}, function (err) {
    // There was an error, and we get the reason for error
}, function (progress) {
    // We get notified of the upload's progress as it is executed
});

Like fail, Q also provides a shorthand for progress callbacks called progress:

return uploadFile().progress(function (progress) {
    // We get notified of the upload's progress
});
  • 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
  • 中间变量无法共用 解决办法 高阶作用域,在最顶端定义变量
  • 回调带来的真正问题,并不是代码不停往右边延展,而是你不能以正常的「函数」概念来思考问题

原理:

1 const p1 = new Promise(function (resolve, reject) {
  // ...
const p2 = new Promise(function (resolve, reject) {
  // ...
  resolve(p1);
})

1 resolve 参数可以有两个值,如果非promise,那么直接将状态结束,传递value给then,如果是resolve一个promise,那么就会先执行(resolve里面的promise),将 (里面的promise)resolve后,再出来执行执行resolve(p1).会把resove值传给then。

2 then方法会返回新的promise,这是串行promise的基础,从而支持链式调用。执行handle方法,handle方法会把then的参数(那个参数 promise)以及 then生成的promise(桥接 promise)的 resolve传进去,然后,如果then中的参数是个promise,那么传入handle执行,我们桥接promise会变为pedding状态,把这个桥接promise存入callback队列,return,然后对(参数promise)进行resolve操作,等到参数resolve完成后,接着我们桥接resove执行。

3 最后执行then后面的then方法

function Promise(fn){
  var state = 'pending',value = null,callbacks = [];
  this.then = function(onFulfilled){
    return new Promise(function(resolve){
      handle({
        onFulfilled: onFulfilled || null,
        resolve: resolve
  function handle(callback){
    if(state == 'pending'){
      callbacks.push(callback);
      return;
    if(!callback.onFulfilled){
      callback.resolve(value);
      return;
    var ret = callback.onFulfilled(value);
    callback.resolve(ret)
  function resolve(newValue){
    if(newValue && (typeof newValue === 'object' || typeof newValue === 'function')){
      var then = newValue.then;
      if(typeof then === 'function'){
        then.call(newValue,resolve,reject);
        return;
    state = 'fulfilled';
    value = newValue;
    execute();
  function reject(reason){
    state = 'rejected';
    value = reason;
    execute();
  function execute(){
    setTimeout(function(){
      callbacks.forEach(function(callback){
        handle(callback)
  fn(resolve,reject)
}

3 Generator

function * count() {
  yield 1
  yield 2
  return 3
var c = count()
console.log(c.next()) // { value: 1, done: false }
console.log(c.next()) // { value: 2, done: false }
console.log(c.next()) // { value: 3, done: true }
console.log(c.next()) // { value: undefined, done: true }

优点:没有了Promise的一堆then(),异步操作更像同步操作,代码更加清晰。

缺点:不能自动执行异步操作,需要写多个next()方法,需要配合使用Thunk函数和Co模块才能做到自动执行。

4 async/await

1 async后返回promise对象,必须等到 await后面promise对象执行完才会执行后面的then操作。

2 正常情况下await后面是一个promise对象,如果不是就返回该值,第二种情况 await后面是一个thenable对象,最燃没有promise,但是有then方法

3 await语句前面没有return,await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。若不想终端,放在try catch里面

优点:

  • 比promise捕获异常更加舒服 使用try catch,
  • 在回调地狱上处理更加舒服,去掉了then
  • 中间变量 可以优雅写出
  • 调试

2 事件循环

同步任务和异步任务的关系

同步和异步任务分别进入不同的执行”场所”,同步的进入主线程,异步的进入Event Table并注册函数。当指定的事情完成时(比如setInterval延迟多少毫秒,Promise的resolve更新状态完成),Event Table会将这个函数移入Event Queue。主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。先从微任务queue里拿回掉函数,然后微任务queue空了后再从宏任务的queue拿函数。 上述过程会不断重复,也就是常说的Event Loop(事件循环)。

主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在"任务队列"中加入各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。

回答:

JavaScript代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(task queue)来搞定另外一些代码的执行。整个执行过程,我们称为事件循环过程。一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs

2.宏任务macrotask:

(事件队列中的每一个事件都是一个macrotask)

优先级:主代码块 > setImmediate > MessageChannel > setTimeout / setInterval

比如:setImmediate指定的回调函数,总是排在setTimeout前面

3.微任务包括:

优先级 async=>promise=>promise.then =>awiat=> setTimeOut

优先级:process.nextTick > async> Promise > MutationObserver

阶梯思路

五 ES6 面试点

ES7、ES8、ES9、ES10新特性_Jason_Maron的博客-CSDN博客_es9

ES6 入门教程

Let Const Var

let 特点: 块级作用域,不存在变量提升, 暂时性死区, let、const声明的变量不会被挂载到window下

let之前模拟块级作用域?匿名函数模拟

没有块级作用域的危害:1 内层变量可能会覆盖外层变量。2 循环变量泄漏为全局变量

let const 声明的变量也不可以被delete删除

块级作用域对函数的影响: 为了兼容老代码,在块级作用域中声明函数,函数声明类似于var,即会提升到全局作用域或函数作用域的头部。所以我们在声明函数时候应尽量使用函数表达式方法声明函数

Const:const实际上保证的,并不是变量的值不得改动,而是 变量指向的那个内存地址所保存的数据不得改动 。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但 对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的 (即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。为了保证对象属性也不可更改可以使用冻结const foo = Object.freeze({});

const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
function(){
    //块级作用域
var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
a[6](); // 10
var tmp = new Date();
function f() {
  console.log(tmp);
  if (false) {
    var tmp = 'hello world';
f(); // undefined

Set Map

set: 类似于数组,但是成员的值都是唯一的,没有重复的值。Set本身是一个构造函数,用来生成 Set 数据结构

const set = new Set([1, 2, 3, 4, 4]); [...set] 数组去重: [...new Set(array)]

WeakSet 与set区别

WeakSet 的成员只能是对象,而不能是其他类型的值。

WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。

Map: JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。

它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。

ES6 Set
Set 结构的实例有以下属性。
Set.prototype.constructor:构造函数,默认就是Set函数。
Set.prototype.size:返回Set实例的成员总数。
Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。
Set.prototype.add(value):添加某个值,返回 Set 结构本身。
Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
Set.prototype.clear():清除所有成员,没有返回值。
Set 结构的实例有四个遍历方法,可以用于遍历成员。
Set.prototype.keys():返回键名的遍历器
Set.prototype.values():返回键值的遍历器
Set.prototype.entries():返回键值对的遍历器
Set.prototype.forEach():使用回调函数遍历每个成员
WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。
首先,WeakSet 的成员只能是对象,而不能是其他类型的值。
const ws = new WeakSet();
ws.add(1)
// TypeError: Invalid value used in weak set
ws.add(Symbol())
// TypeError: invalid value used in weak set
上面代码试图向 WeakSet 添加一个数值和Symbol值,结果报错,因为 WeakSet 只能放置对象。
其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。
这是因为垃圾回收机制根据对象的可达性(reachability)来判断回收,如果对象还能被访问到,垃圾回收机制就不会释放这块内存。结束使用该值之后,有时会忘记取消引用,导致内存无法释放,进而可能会引发内存泄漏。WeakSet 里面的引用,都不计入垃圾回收机制,所以就不存在这个问题。因此,WeakSet 适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在 WeakSet 里面的引用就会自动消失。
由于上面这个特点,WeakSet 的成员是不适合引用的,因为它会随时消失。另外,由于 WeakSet 内部有多少个成员,取决于垃圾回收机制有没有运行,运行前后很可能成员个数是不一样的,而垃圾回收机制何时运行是不可预测的,因此 ES6 规定 WeakSet 不可遍历。
这些特点同样适用于本章后面要介绍的 WeakMap 结构。
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
Map.prototype.set(key, value)
Map.prototype.get(key)
Map.prototype.has(key)
Map.prototype.delete(key)
Map.prototype.keys():返回键名的遍历器。
Map.prototype.values():返回键值的遍历器。
Map.prototype.entries():返回所有成员的遍历器。
Map.prototype.forEach():遍历 Map 的所有成员。

Promise

promise为什么会产生,promise 有什么优缺点? 如何理解promise将异步操作以同步操作流程表达出来?promise 类型是什么?创建promise的常用方式有哪些?promise的参数是什么?

promise实际 就是一个构造函数 ,可以通过new 来创造一个promise实例,所以通常创建promise实例的方式有两种。promise的参数是一个函数,这个函数有resolve和reject两个参数,他们两个是两个函数。

方式一:声明一个变量 = 声明一个promise类型的对象
const myFirstPromise = new Promise((resolve, reject) => {
方式二: 声明一个函数,函数返回返回一个promise类型的对象
function myAsyncFunction(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(xhr.statusText);
    xhr.send();
};

如何理解 resolve以及 reject 的参数? resolve以及reject函数调用者是谁 ,挂在哪个上面?调用resolve以及reject后,后面的语句还会执行吗?为什么需要return resolve(data)? resolve以及reject函数返回值是什么?

resolve以及reject返回值是一个Promise实例?????不太确定

1 书写promise cb两种方式:一就是直接new promise后两个参数 在new promise中执行 resolve(data),reject(data) 二是调用then,then两个参数为两个函数,分别执行resolve和reject,三就是promise实例后面跟,then,和catch分别处理 resolve和reject

2 new promise属于同步的,then属于异步的

3 resolve后不应该有语句,应该直接return掉 resolve语句,因为相当于resolve后流程旧结束所以不应该有,如果有还会执行,并且resolve后面语句属于同步语句 打印 2 3 1

3 catch方法的作用?catch和then的第二个参数有什么区别?catch方法返回值是什么? catch方法作用是为了处理promise内部发生的错误,如果没有catch方法,放发生错误时候,不会退出进程,终止脚本执行,promise的内部错误不会影响到外部的代码,会吃掉错误。catch语句不仅可以替代then第二个参数,来实现reject的回调函数,而且还是可以捕获promise内部错误,相当于比then的第二个参数多一些功能,所以我们尽量不要写then的而参数即reject的回调,要多使用catch函数。catch返回值是一个promise对象,后面还以跟then方法

4 then方法挂在什么上面?参数是什么?返回值是什么?then 必须return吗?then方法是挂在原型对象Promise.prototype上,参数是两个匿名函数,匿名函数参数是 resolve(data)或者reject(data)中的data,两个匿名函数第一个是resolve的回调函数,第二个是reject的回调函数。返回值是一个新的Promise实例,不是之前那个,因此可以采用链式。采用链式方法调用,如果有多个then,那么第一个then如果返回时一个promise,那么第二个then就会等到返回的这个promise状态改变了才会继续执行第二个then

5 finally方法作用是什么?它的参数是什么?返回值是什么?是否与状态有关?finally方法用于在执行完then catch最后执行的方法,它没有参数,不管前面状态如何,与状态无关的一个方法。返回值是总会返回原来的值,就是resolve(data)和reject(data)中的data。

6 all 方法作用是什么? 参数是什么,返回值是什么?返回值得状态是由什么决定的?使用场景有哪些?作用是将多个promise实例包装成一个新的promise实例,参数是一个数组,数组中所有参数是单个promise,返回值是一个新的promise实例。返回值得状态是由内部小·的promise公共决定的,分以下情况:const p = Promise.all([p1, p2, p3]);只有p 1,2,3都变为fulfilled,p的状态才变为fulfilled,p1,2,3的返回值组成一个数组,传递给p的回调函数。只要p1,2,3中一个被rejected,p的状态就变成rejected,此时第一个被reject的实例但绘制,会传递给p的回调函数(在单个promise实例自身没有catch函数情况下,如果自身有,则不会传到all后面的回调)

7 race方法作用是什么?参数是什么?返回值是什么?Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。在执行多个异步操作中,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数

8 Promse.all在处理多个异步处理时非常有用,比如说一个页面上需要等两个或多个ajax的数据回来以后才正常显示,在此之前只显示loading图标。

new Promise((resolve, reject) => {
  resolve(1);
  console.log(2);
}).then(r => {
  console.log(r);
console.log(3)
best methods
new Promise((resolve, reject) => {
  return resolve(1);
  // 后面的语句不会执行
  console.log(2);
------------------------------------
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
情况一: resolve不带参数,调用resolve会直接 返回状态为resolved的Promise对象,如果想得到一个
Promise对象最直接办法就是Promise.resolve()
var p = Promise.resolve();
p.then((resolve)=>{
}).catch((reject)=>{
情况二:resolve 参数不为对象或者是不具有then方法的对象。Promise.resolve方法返回一个新的Promise对象,状态为Resolved
var p = Promise.resolve("hello 1024");//字符串不属于异步操作,判断方法是不具有then方法,返回的Promise的实例从生成起就是Resolved,所以
回调函数会立即执行。Promise.resolve参数就会同时传给回调函数
p.then((value)=>{
    console.log(value);//hello 1024
情况三: 参数是一个thenable对象,Promise.resolve方法会将这个对象转为Promise对象,然后立即执行thenable对象的then方法
//thenable对象
let thenable = {
    then:function(resolve,reject){
        resolve(42);
//下面会将thenable对象转换为Promise对象
let p = Promise.resolve(thenable);
p.then((value)=>{
    console.log(value);//42
情况四: 参数是一个Promise对象,果参数是一个Promise的实例,那么Promise.resolve将不作任何修改,原封不动的返回这个实例
const p1 = new Promise(function (resolve, reject) {
  // ...
const p2 = new Promise(function (resolve, reject) {
  // ...
  resolve(p1);
此时p1的状态会传给p2,p1的状态决定了p2的状态,导致p2的自己状态无效了,如果p1的状态是pedding,那么p2的回调函数就会等待p1的状态改变,如果p1的状态已经是resolved或者
rejected,那么p2的回调函数就会立即执行。
问题:下面的几秒后打印,以及打印的是什么
const p1 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error('fail')), 3000)
const p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000)
  .then(result => console.log(result))
  .catch(error => console.log(error))
// Error: fail
p2的状态在 1 秒之后改变,resolve方法返回的是p1。由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都
变成针对后者(p1)。又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数
---------------------------------
const someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行会报错,因为x没有声明
    resolve(x + 2);
someAsyncThing().then(function() {
  console.log('everything is great');
setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 123
-----------------------------------
// bad
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
// good
promise
  .then(function(data) { //cb
    // success
  .catch(function(err) {
    // error
--------------------------------------------
// resolve 的值是 undefined
Promise.resolve(2).then(() => {}, () => {})
// resolve 的值是 2
Promise.resolve(2).finally(() => {})
// reject 的值是 undefined
Promise.reject(3).then(() => {}, () => {})
// reject 的值是 3
Promise.reject(3).finally(() => {})
----------------------------------------------------------------------
const p1 = new Promise((resolve, reject) => {
  resolve('hello');
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
p1会resolved,p2首先会rejected,但是p2有自己的catch方法,该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。该实例执行完catch方法后,也会变成resolved,导致Promise.all()方法参数里面的两个实例都会resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。
---------
let wake = (time) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`${time / 1000}秒后醒来`)
    }, time)
let p1 = wake(3000)
let p2 = wake(2000)
Promise.all([p1, p2]).then((result) => {
  console.log(result)       // [ '3秒后醒来', '2秒后醒来' ]
}).catch((error) => {
  console.log(error)
Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,即p1的结果在前,即便p1的结果获取的比p2要晚。
这带来了一个绝大的好处:在前端开发请求数据的过程中,偶尔会遇到发送多个请求并根据请求顺序获取和使用数据的场景,使用Promise.all毫无疑问可以解决这个问题。
---------------------------------------
var fun1 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 3000, 'fun1');
var fun2 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 200, 'fun2');
// *注:这里使用的示例和promise.all用的示例类似,
// 可是没有用()执行,因为fun1就是一个promise对象,不需要执行
Promise.race([fun1, fun2]).then(function(result) {
    console.log(result); // 'fun2'   因为fun2比较早执行结束

手写promise

function Promise(fn){
  var state = 'pending',value = null,callbacks = [];
  this.then = function(onFulfilled){
    return new Promise(function(resolve){
      handle({
        onFulfilled: onFulfilled || null,
        resolve: resolve
  function handle(callback){
    if(state == 'pending'){
      callbacks.push(callback);
      return;
    if(!callback.onFulfilled){
      callback.resolve(value);
      return;
    var ret = callback.onFulfilled(value);
    callback.resolve(ret)
  function resolve(newValue){
    if(newValue && (typeof newValue === 'object' || typeof newValue === 'function')){
      var then = newValue.then;
      if(typeof then === 'function'){
        then.call(newValue,resolve,reject);
        return;
    state = 'fulfilled';
    value = newValue;
    execute();
  function reject(reason){
    state = 'rejected';
    value = reason;
    execute();
  function execute(){
    setTimeout(function(){
      callbacks.forEach(function(callback){
        handle(callback)
  fn(resolve,reject)
1 const p1 = new Promise(function (resolve, reject) {
  // ...
const p2 = new Promise(function (resolve, reject) {
  // ...
  resolve(p1);
1 resolve 参数可以有两个值,如果非promise,那么直接将状态结束,传递value给then,如果是resolve一个promise,那么就会先执行(resolve里面的promise),将 (里面的promise)resolve后,再出来执行执行resolve(p1).会把resove值传给then。
2 then方法会返回新的promise,这是串行promise的基础,从而支持链式调用。执行handle方法,handle方法会把then的参数(那个参数 promise)以及 then生成的promise(桥接 promise)的 resolve传进去,然后,如果then中的参数是个promise,那么传入handle执行,我们桥接promise会变为pedding状态,把这个桥接promise存入callback队列,return,然后对(参数promise)进行resolve操作,等到参数resolve完成后,接着我们桥接resove执行。
3 最后执行then后面的then方法

async

1 async 函数的作用是什么?返回值是什么?async函数常用写法是?

1 async 关键词的作用就是将async后面的函数变为一个一个promise对象,不管函数里面是否return,它都是一个promise对象。如果在async中return 一个值,这个值会经过resolve处理Promise.resolve()封装成 Promise 对象。然后也会将resolve(data)传到then函数后面,如果没有return 则会返回underfind,promise的resolve是不管return和不return 都可以返回。async 函数返回的是一个 Promise 对象。从 文档 中也可以得到这个信息。async 函数(包含函数语句、函数表达式、Lambda表达式)会返回一个 Promise 对象,如果在函数中return一个直接量, async 会把这个直接量通过Promise.resolve()封装成 Promise 对象。如果 async 函数没有返回值,又该如何?很容易想到,它会返回Promise.resolve(undefined)。也是一个promise对象,只不过then的参数没有了。 在没有await的情况下执行 async 函数,它会立即执行,返回一个 Promise 对象,并且,绝不会阻塞后面的语句。这和普通返回 Promise 对象的函数并无二致。

2 await 命令作用是什么,await参数是什么, await返回值是什么? 需要return吗?调用then的是谁?

await参数可以使一个promise对象也可以是一个正常数值,如果是一个字符串或者非promise,await会将其调用Promise.resolve(字符串),产生一个promise对象,然后一定要return await ‘123’。然后我们就可以通过调用这个async函数来后面跟then函数了,因为async 将这个函数转变为一个promise对象,不管return 不return,都是一个promise对象,但是 如果return了 then的调用者就是 await 123 这个promise,如果不return 那么then的调用者是谁?答案是 Promise.resolve(undefined) 这个promise。还有一种情况await后面参数是一个thenable对象(即定义then方法的对象

3 await的错误处理如何处理,await后面的reject会中断程序吗?如果会如何防止?如果await后面跟的是Promise.reject('123'),即使在async函数里面不return,也会被 函数的catch捕获。防止await后面的reject中断程序运行,将await放入try catch中。

4 相对于promise 两者最大应用场景不同是什么?

如果两个接口不存在继发关系,即一个接口依赖另一个接口这种关系,最好使用promise.all来,如果存在继发关系,相当于async await将多个promise 封装为一个promise,这样对于减少定义变量很有帮助,并且写法更加优雅 精简 清晰。相互依赖场景:一个提交表单的页面,里面有姓名、地址等巴拉巴拉的信息,其中有一项是手机验证码,我们不得不等待手机验证码接口通过,才能发出后续的请求操作,这时候接口之间就存在了彼此依赖的关系,Async跟Await就有了用武之地,让异步请求之间可以按顺序执行。们不得不用.then()的方式,在第一个请求验证码的接口有返回值之后,才能执行后续的的Promise,并且还需要一个then输出结果。我们将逻辑分装在一个async函数里。这样我们就可以直接对promise使用await了,也就规避了写then回调。最后我们调用这个async函数,然后按照普通的方式使用返回的promise

async function testAsync() {
    return "hello async";
const result = testAsync();
console.log(result);  // Promise { 'hello async' }
testAsync().then(v => {
    console.log(v);    // 输出 hello async
------
async function f() {
  return 'hello world';
f().then(v => console.log(v))
基础写法:
函数声明: async function foo() {}
函数表达式: const foo = async function(){}
箭头函数:const foo = async()=>{}
对象写法:let obj = {async foo(){}}
obj.foo().then(()=>{console.log('123')})
------------
async function f() {
  // 等同于
  // return 123;
  return await 123;
f().then(v => console.log(v))
-------------------------
async function f() {
  await Promise.reject('出错了');   //没有return
  console.log('reject后面不执行')    // reject后面不执行
.then(v => console.log(v))
.catch(e => console.log(e))         // 也会捕获
// 出错了
----------------------------
补救 reject
async function f() {
  try {
    await Promise.reject('出错了');
  } catch(e) {
  return await Promise.resolve('hello world');
.then(v => console.log(v))
相互不依赖的多个请求用promise.all

Class 理解

1 为什么会产生class类语法?

js和java都是面向对象语言,但是js中只有对象,没有类的概念,对比传统 OOP 语言中的类写法,这种写法让许多学过其他 OOP 语言的 JS 初学者感到困惑,实现在 JS 中写 Java 的心愿,当时有人将构造函数写法封装成了类似于 Java 中类的写法的 klass 语法糖,直到 es6 发布,在 es6 中, klass 终于备胎转正,摇身一变变成了 class ,终于从官方角度实现了梦想。

2 class与之前普通函数有什么不同?相同点?

  • 改写了构造函数的静态方法的写法
  • class声明并不像function声明,他 不存在提升 。他类似let声明,存在TDZ(temporal dead zone)
  • class中的代码都会自动的使用严格模式,没办法选择
  • class中 所有的方法都是不可枚举的(non-enumerable), 注:非绑定当前对象的方法
  • 尝试在类的方法中改变类名会出错 相同点:
  • 类的数据类型就是函数 静态方法 3 静态方法为什么会出现?es5的静态方法和es6有什么区别?
  • 类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上 static关键字,就表示该方法不会被实例继承 ,而是直接通过 类点方法 来调用,这就称为“静态方法”。要不然不用静态方法, 要先new 一个类,要调用new一个实例,然后调用实例上的方法,要注意的是,调用实例上的方法,实际上也是调用原型上的方法,方法都是定义在类的prototype上即定义在原型上。

之所以出现静态方法就是因为原型对象这个东西,在类上定义方法实际上就相当于es5的在实例对象上定义方法。构造函数的prototype属性叫做原型对象, 原型对象很特殊,具有一个普通对象没有的能力,将它的属性共享给其他对象。

谈谈你对constructor的理解?

  • constructor相当于ES5中的构造方法一样, constructor方法返回实例对象this???? 但是也可以指定constructor方法但会一个全新的对象,让返回的实例对象不是该类的实例
  • new一个类的时候自动调用 该constructor方法 ,如果没写,自动添加。
  • 类本身就是指向构造函数。 谈谈你对类的实例的理解?
  • 首先类的实例是和constructor有很大关系的,new一个,就是new的constructor,所以 constructor上定义的变量都是定义在实例对象上面的,注意是实例对象,实例对象,不同于类中的方法是定义在原型对象上的。
  • 所以在理解类的实例时候,挂在类的实例上的是constructor里面的值
  • 所有类的实例都是共享一个原型的,可以用过实例的_ proto __给 原型上添加方法,因为实例的proto等于原型对象,然后方法又挂在原型对象上面。

谈谈你对class中的getter和setter的理解?

首先谈谈对象的getter以及setter属性,对象的属性分为两种,至少现在我知道有两种,一种是 数据属性 ,下面的a,是一个简单的值。另一种是 存取器属性 ,这种用getter和setter方法定义的属性,其实也是属性,这个函数没有function关键字,也没有使用冒号将属性名和函数体分开,但函数体的结束和下一个方法之前是有逗号,当程序查询存取器属性值时,js调用getter方法,无参数,这个方法的返回值就是该属性存取表达式的值。当程序设置一个存取器属性值时,可以调用setter方法,将赋值表达式右侧的值当做参数传入setter。

两者在特性上有什么不同呢?

数据属性有4个描述其行为的特性:

  • [Configurable]] : 表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。[[Enumerable]] : 表示能否通过 for-in 循环返回属性。[[Writable]] : 表示能否修改属性的值[[Value]] : 包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值时,把新值保存在这个位置。默认值是 undefined。 像这样(var obj = new Object(); obj.name = "percy";)或者像通过对象字面量(var obj = {name: "percy"};)直接在对象上定义的属性,它们的 [[Configurable]]、[[Enumerable]] 和 [[Writable]] 特性默认都被设置为 true,而 [[Value]] 特性被设置为指定的值。

没有按上面的2种方法为对象添加属性,而直接通过Object.defineProperty()为对象添加属性以及值,这种情况下,这个对象的这个属性的另外3个特性的默认值都是 false。

要修改属性默认的特性,必须使用 ECMAScript 的 Object.defineProperty() 方法。

  • Object.defineProperty( obj, prop, descriptor)
  • obj:需要定义属性的对象。
  • prop:需定义或修改的属性的名字。
  • descriptor:一个包含设置特性的对象

存取器属性几种特性:访问器属性不包含数据值,它们包含一对儿getter和setter函数(不过,这两个函数都不是必需的)。在读取访问器属性时,会调用 getter 函数,在写入访问器属性时,又会调用 setter 函数并传入新值。访问器属性有如下4个特性:对比数据属性 没有Writable以及value

  • [[Configurable]] :表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。 [[Enumerable]] :表示能否通过 for-in 循环返回属性。 [[Get]] :在读取属性时调用的函数。默认值为 undefined。 [[Set]] :在写入属性时调用的函数。默认值为 undefined。

访问器属性不能直接定义,必须使用Object.defineProperty()来定义。 那么在class 中getter和setter是原型属性方法还是实例对象上的方法?既然有了数据属性为什么还要有这个属性,对比数据属性有哪些优点?相当于一个队属性value设施一个钩子,如果钩子函数里面值改变从而会导致这个函数值也会改变,之前对于数据属性这个没有一个连带的效果的。这个属性实际用在什么地方?

类中方法的this默认指向的是类的实例,因为是new出来的对象调用的所以指向这个实例对象,但是当单独拿出来时候没和可能指向的是window,但是在严格模式下,this又不能指向window,所以this指向是underfind,在class中 ,this默认指向类的实例。但是,如果将这个方法提取出来单独使用,this会指向该方法运行时所在的环境。既然是全局环境的话,那么此时的this应该是输出window,但是却不是window而是undefined。 那么问题出在哪? 其实,就是在class中,定义类的时候默认使用的是严格模式。所以在严格模式下,由于this不能指向全局变量,因此这里就变成了undefined。

类的实例属性以及静态属性

类的实例属性有两种定义的方法:1 定义在constructor里面,2 定义在类的最顶端(不用加this) 类的实例属性是定义在类的实例对象上面,会被实例继承。静态属性是定义在class本身的属性,并不是定义在实例对象上的属性,所欲静态属性不会被实例继承,也是直接通过类来调用。 静态方法和静态属性都是类本来的属性,定义在class类上,所以可以直接调用,即不定义在类的原型上也不定义在类的实例上,比如实例属性是定义在类的实例上,方法定义在类的原型上,我们制作一可以在实例上调用方法是因为,实例继承了原型,所以可以在实例上调用原型上的方法。

8谈谈你对类的私有方法以及私有属性的理解? 为什么出现,是为了解决类中的所有方法以及属性都可以暴露给外部,所以类的私有方法以及私有属性对外是不可以见的,只能在内部使用,

proposal-class-fields proposal-private-methods 定义了 Class 的私有属性以及私有方法,这 2 个提案已经处于 Stage 3,这就意味着它们已经基本确定下来了,等待被加入到新的 ECMAScript 版本中。事实上,最新的 Chrome 已经支持了 Class 私有属性

使用#来定义,主要私有属性和实例属性不要同名。实例是 不可以直接引用私有属性的,但是如果被传进class里面,那么就可以调用

个新的前缀#表示私有属性,而没有采用private关键字,是因为 JavaScript 是一门动态语言,没有类型声明,使用独立的符号似乎是唯一的比较方便可靠的方法,能够准确地区分一种属性是否为私有属性。另外,Ruby 语言使用@表示私有属性,ES6 没有用这个符号而使用#,是因为@已经被留给了 Decorator。

function Person() {
  this.name = name
// 1. 首先给 Person.prototype 原型对象添加了 describe 方法 。
Person.prototype.describe = function(){
  console.log('Hello, my name is ' + this.name + '.');
// 2. 实例化对象的 __proto__ 指向 Person.prototype
var jane = new Person('jane');
jane.__proto__ === Person.prototype;
// 3. 读取 describe 方法时,实际会沿着原型链查找到 Person.prototype 原型对象上。
jane.describe() // Hello, my name is jane.
--------------------
es5 静态方法
function Person(name,age) {
     //构造函数里面的方法和属性
      this.name=name;  // 实例属性,可以通过对象.属性访问的属性叫实例属性
      this.age=age;
      this.run=function(){ // 实例方法
           console.log(`${this.name}---${this.age}`)
//原型链上面的属性和方法可以被多个实例共享
Person.prototype.sex='男';
Person.prototype.work=function(){ // 实例方法,挂载在原型链,生成的对象可直接点方法的方式调用
      console.log(`${this.name}---${this.age}---${this.sex}`);
// 静态属性,挂载在构造函数
Person.info = 'nice'
//静态方法
Person.setName=function(){
      console.log('静态方法');
var p=new Person('zhangsan','20');   /*实例方法是通过实例化来调用的,静态是通过类名直接调用*/
p.run();
p.work();
Person.setName();  /*执行静态方法*/
---------------------
es6的静态方法
class Person{
    #x; // 私有属性
    _count = 0;   实例属性 1
    constructor(name){
        this._name=name;  /*实例属性 2*/
    run(){  /*实例方法*/
        console.log(this._name);
  // 静态属性
  static info = 'es6 静态方法'
    static work(){   /*静态方法*/
        console.log('这是es6里面的静态方法');
// 私有方法
 #method = () => {
        // ...
Person.instance='这是一个静态方法的属性';
var p=new  Person('张三');
p.run();
Person.work();   /*es6里面的静态方法*/
console.log(Person.instance);
-----------------------------------------------------------------------
//理解 属性挂在类的实例上,方法挂在类的原型上
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  toString() {
    return '(' + this.x + ', ' + this.y + ')';
var point = new Point(2, 3);
point.toString() // (2, 3)
point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true
point.__proto__ = 构造函数.prototype = 原型对象
---------------------------------------------------
// 理解可以通过proto来改变添加  原型上的方法
var p1 = new Point(2,3);
var p2 = new Point(3,2);
p1.__proto__.printName = function () { return 'Oops' };
p1.printName() // "Oops"
p2.printName() // "Oops"
var p3 = new Point(4,2);
p3.printName() // "Oops"
-------------------------------------------------------------------
var person = {};   // 新建一个空对象
Object.defineProperty(person,"name",{
  value: "percy"
console.log(Object.getOwnPropertyDescriptor(person,"name"));
// 打印:Object {value: "percy", writable: false, enumerable: false, configurable: false}
--------------------------------------------------------------------
var person = {name: "percy"};
Object.defineProperty(person,"name",{
  writable: false
console.log(person.name);  // percy
person.name = "zyj";
console.log(person.name);  // percy
for(let prop in person){
  console.log(prop + " : " + person[prop]);
}                   // name : percy 
Object.defineProperty(person,"name",{
  enumerable: false
for(let prop in person){
  console.log(prop + " : " + person[prop]);
}                   // 什么也没打印
Object.defineProperty(person,"name",{
  configurable: false
Object.defineProperty(person,"name",{
  configurable: true
}); // 报错:TypeError: Cannot redefine property: name(…)
// 一旦把属性定义为不可配置的,那么就再也不能把属性定义回可配置的了。
----------------------------------------------------------------------------------------------
var book = {
    _year : 2004,
    edition : 1
Object.defineProperty(book,"year",{ 
    get : function () {
        alert(this._year);
    set : function (newValue) {
        if (newValue > 2004) {
            this._year = newValue;
            this.edition += newValue - 2004;
book.year;      // 弹出窗口,显示 2004
book.year = 2005;
console.log(book.edition);   // 2
---------------------
var obj = {
    get a(){
        return this._a_;
    set a(val){
        this._a_ = val;
obj.a = 1;
console.log(obj.a);//1
----------------------
class App {
        handleClick() {
            console.log(this)
    const app = new App
    const func = app.handleClick
<button onclick="func()">click</button>
// 打印 undefined
<button onclick="app.handleClick()">click</button>
// 打印 App
---------------
 const func = app.handleClick;
 func(); //调用func的环境是全局环境。
-------------------------------------
 class App {
       handle() {
           console.log(this)
   function test1 () {
       console.log(this);
   var test2 = function(){
       "use strict";
       console.log(this);
   const app = new App();
   const func = app.handle;
   func();  //undefined
   test1();  //window
   test2();  //undefined
--------------
解决办法就是绑定this,箭头函数
class Logger{
    constructor(){
        this.printName = this.printName.bind(this);
//...
class Logger{
    constructor(){
        this.printName = (name = 'there') => {
            this.print(name);
----------------------
class IncreasingCounter {
  _count = 0;   实例属性
---------------------------
constructor() {
    this._count = 0;  实例属性
  get value() {
    console.log('Getting the current value!');
    return this._count;
  increment() {
    this._count++;
-------------
/  老写法
class Foo {}
Foo.prop = 1;
//  新写法
class Foo {
	static prop = 1;//Class 内部只有静态方法,没有静态属性。现在有一个提案提供了类的静态属性,写法是在实例属性法的前面,加上static关键字。
---------------
class Point {
    constructor(x, y) {
        this.#x = x;
        this.#y = y;
    equals(point) {
        return this.#x === point.#x && this.#y === point.#y;
------------
class Foo {
    #privateValue = 42;
    static getPrivateValue(foo) {
        return foo.#privateValue;
Foo.getPrivateValue(new Foo()); // >> 42
------------------
class Foo {
    constructor() {
        this.#method();
    #method() {
        // ...
class Foo {
    constructor() {
        this.#method();
    #method = () => {
        // ...
}


1 ES6模块与CommonJS模块有什么异同?
ES6 Module和CommonJS模块的区别:
CommonJS是对模块的浅拷⻉,ES6 Module是对模块的引⽤,即ES6 Module只存只读,不能改变其值,也就是指针指向不能变,类似const;
import的接⼝是read-only(只读状态),不能修改其变量值。 即不能修改其变量的指针指向,但可以改变变量内部指针指向,可以对commonJS对重新赋值(改变指针指向),但是对ES6 Module赋值会编译报错。
ES6 Module和CommonJS模块的共同点:
CommonJS和ES6 Module都可以对引⼊的对象进⾏赋值,即对对象内部属性的值进⾏改变。
2 map和weakMap的区别
(1)Map
map本质上就是键值对的集合,但是普通的Object中的键值对中的键只能是字符串。而ES6提供的Map数据结构类似于对象,但是它的键不限制范围,可以是任意类型,是一种更加完善的Hash结构。如果Map的键是一个原始数据类型,只要两个键严格相同,就视为是同一个键。
实际上Map是一个数组,它的每一个数据也都是一个数组,其形式如下:
const map = [
     ["name","张三"],
     ["age",18],
Map数据结构有以下操作方法:
size: map.size 返回Map结构的成员总数。
set(key,value):设置键名key对应的键值value,然后返回整个Map结构,如果key已经有值,则键值会被更新,否则就新生成该键。(因为返回的是当前Map对象,所以可以链式调用)
get(key):该方法读取key对应的键值,如果找不到key,返回undefined。
has(key):该方法返回一个布尔值,表示某个键是否在当前Map对象中。
delete(key):该方法删除某个键,返回true,如果删除失败,返回false。
clear():map.clear()清除所有成员,没有返回值。
Map结构原生提供是三个遍历器生成函数和一个遍历方法
keys():返回键名的遍历器。
values():返回键值的遍历器。
entries():返回所有成员的遍历器。
forEach():遍历Map的所有成员。
const map = new Map([
     ["foo",1],
     ["bar",2],
for(let key of map.keys()){
    console.log(key);  // foo bar
for(let value of map.values()){
     console.log(value); // 1 2
for(let items of map.entries()){
    console.log(items);  // ["foo",1]  ["bar",2]
map.forEach( (value,key,map) => {
     console.log(key,value); // foo 1    bar 2
(2)WeakMap
WeakMap 对象也是一组键值对的集合,其中的键是弱引用的。其键必须是对象,原始数据类型不能作为key值,而值可以是任意的。
该对象也有以下几种方法:
set(key,value):设置键名key对应的键值value,然后返回整个Map结构,如果key已经有值,则键值会被更新,否则就新生成该键。(因为返回的是当前Map对象,所以可以链式调用)
get(key):该方法读取key对应的键值,如果找不到key,返回undefined。
has(key):该方法返回一个布尔值,表示某个键是否在当前Map对象中。
delete(key):该方法删除某个键,返回true,如果删除失败,返回false。
其clear()方法已经被弃用,所以可以通过创建一个空的WeakMap并替换原对象来实现清除。
WeakMap的设计目的在于,有时想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用。一旦不再需要这两个对象,就必须手动删除这个引用,否则垃圾回收机制就不会释放对象占用的内存。
而WeakMap的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。
Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
WeakMap 结构与 Map 结构类似,也是用于生成键值对的集合。但是 WeakMap 只接受对象作为键名( null 除外),不接受其他类型的值作为键名。而且 WeakMap 的键名所指向的对象,不计入垃圾回收机制。
3  let、const、var的区别
(1)块级作用域: 块作用域由 { }包括,let和const具有块级作用域,var不存在块级作用域。块级作用域解决了ES5中的两个问题:
内层变量可能覆盖外层变量
用来计数的循环变量泄露为全局变量
(2)变量提升: var存在变量提升,let和const不存在变量提升,即在变量只能在声明之后使用,否在会报错。
(3)给全局添加属性: 浏览器的全局对象是window,Node的全局对象是global。var声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let和const不会。
(4)重复声明: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。
(5)暂时性死区: 在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区。
(6)初始值设置: 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。
(7)指针指向: let和const都是ES6新增的用于创建变量的语法。 let创建的变量是可以更改指针指向(可以重新赋值)。但const声明的变量是不允许改变指针的指向。
区别	var	let	const
是否有块级作用域	×	✔️	✔️
是否存在变量提升	✔️	×	×
是否添加全局属性	✔️	×	×
能否重复声明变量	✔️	×	×
是否存在暂时性死区	×	✔️	✔️
是否必须设置初始值	×	×	✔️
能否改变指针指向	✔️	✔️	×
4 const对象的属性可以修改吗
const保证的并不是变量的值不能改动,而是变量指向的那个内存地址不能改动。对于基本类型的数据(数值、字符串、布尔值),其值就保存在变量指向的那个内存地址,因此等同于常量。
但对于引用类型的数据(主要是对象和数组)来说,变量指向数据的内存地址,保存的只是一个指针,const只能保证这个指针是固定不变的,至于它指向的数据结构是不是可变的,就完全不能控制了。
5 如果new一个箭头函数的会怎么样
箭头函数是ES6中的提出来的,它没有prototype,也没有自己的this指向,更不可以使用arguments参数,所以不能New一个箭头函数。
new操作符的实现步骤如下:
创建一个对象
将构造函数的作用域赋给新对象(也就是将对象的__proto__属性指向构造函数的prototype属性)
指向构造函数中的代码,构造函数中的this指向该对象(也就是为这个对象添加属性和方法)
返回新的对象
所以,上面的第二、三步,箭头函数都是没有办法执行的。
6 箭头函数与普通函数的区别
(1)箭头函数比普通函数更加简洁
如果没有参数,就直接写一个空括号即可
如果只有一个参数,可以省去参数的括号
如果有多个参数,用逗号分割
如果函数体的返回值只有一句,可以省略大括号
如果函数体不需要返回值,且只有一句话,可以给这个语句前面加一个void关键字。最常见的就是调用一个函数:
let fn = () => void doesNotReturn();
(2)箭头函数没有自己的this
箭头函数不会创建自己的this, 所以它没有自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中this的指向在它在定义时已经确定了,之后不会改变。
(3)箭头函数继承来的this指向永远不会改变
var id = 'GLOBAL';
var obj = {
  id: 'OBJ',
  a: function(){
    console.log(this.id);
  b: () => {
    console.log(this.id);
obj.a();    // 'OBJ'
obj.b();    // 'GLOBAL'
new obj.a()  // undefined
new obj.b()  // Uncaught TypeError: obj.b is not a constructor
对象obj的方法b是使用箭头函数定义的,这个函数中的this就永远指向它定义时所处的全局执行环境中的this,即便这个函数是作为对象obj的方法调用,this依旧指向Window对象。需要注意,定义对象的大括号{}是无法形成一个单独的执行环境的,它依旧是处于全局执行环境中。
(4)call()、apply()、bind()等方法不能改变箭头函数中this的指向
var id = 'Global';
let fun1 = () => {
    console.log(this.id)
fun1();                     // 'Global'
fun1.call({id: 'Obj'});     // 'Global'
fun1.apply({id: 'Obj'});    // 'Global'
fun1.bind({id: 'Obj'})();   // 'Global'
(5)箭头函数不能作为构造函数使用
构造函数在new的步骤在上面已经说过了,实际上第二步就是将函数中的this指向该对象。 但是由于箭头函数时没有自己的this的,且this指向外层的执行环境,且不能改变指向,所以不能当做构造函数使用。
(6)箭头函数没有自己的arguments
箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是它外层函数的arguments值。
(7)箭头函数没有prototype
(8)箭头函数不能用作Generator函数,不能使用yeild关键字
5. 箭头函数的this指向哪⾥?
箭头函数不同于传统JavaScript中的函数,箭头函数并没有属于⾃⼰的this,它所谓的this是捕获其所在上下⽂的 this 值,作为⾃⼰的 this 值,并且由于没有属于⾃⼰的this,所以是不会被new调⽤的,这个所谓的this也不会被改变。
6 扩展运算符的作用及使用场景
 Proxy 可以实现什么功能?
在 Vue3.0 中通过 Proxy 来替换原本的 Object.defineProperty 来实现数据响应式。
Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。
let p = new Proxy(target, handler)
target 代表需要添加代理的对象,handler 用来自定义对象中的操作,比如可以用来自定义 set 或者 get 函数。
下面来通过 Proxy 来实现一个数据响应式:
let onWatch = (obj, setBind, getLogger) => {
  let handler = {
    get(target, property, receiver) {
      getLogger(target, property)
      return Reflect.get(target, property, receiver)
    set(target, property, value, receiver) {
      setBind(value, property)
      return Reflect.set(target, property, value)
  return new Proxy(obj, handler)
let obj = { a: 1 }
let p = onWatch(
  (v, property) => {
    console.log(`监听到属性${property}改变为${v}`)
  (target, property) => {
    console.log(`'${property}' = ${target[property]}`)
p.a = 2 // 监听到属性a改变
p.a // 'a' = 2
在上述代码中,通过自定义 set 和 get 函数的方式,在原本的逻辑中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。
当然这是简单版的响应式实现,如果需要实现一个 Vue 中的响应式,需要在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要使用 Proxy 替换原本的 API 原因在于 Proxy 无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是 Proxy 可以完美监听到任何方式的数据改变,唯一缺陷就是浏览器的兼容性不好。
对 rest 参数的理解
作者:CUGGZ
链接:https://juejin.cn/post/6940945178899251230
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

其他JS考点

1 什么是 DOM 和 BOM?
DOM 指的是文档对象模型,它指的是把文档当做一个对象,这个对象主要定义了处理网页内容的方法和接口。
BOM 指的是浏览器对象模型,它指的是把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互的法和接口。BOM的核心是 window,而 window 对象具有双重角色,它既是通过 js 访问浏览器窗口的一个接口,又是一个 Global(全局)对象。这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方法存在。window 对象含有 location 对象、navigator 对象、screen 对象等子对象,并且 DOM 的最根本的对象 document 对象也是 BOM 的 window 对象的子对象。
2 Unicode、UTF-8、UTF-16、UTF-32的区别?
(1)Unicode
在说Unicode之前需要先了解一下ASCII码:ASCII 码(American Standard Code for Information Interchange)称为美国标准信息交换码。
它是基于拉丁字母的一套电脑编码系统。
它定义了一个用于代表常见字符的字典。
它包含了"A-Z"(包含大小写),数据"0-9" 以及一些常见的符号。
它是专门为英语而设计的,有128个编码,对其他语言无能为力
ASCII码可以表示的编码有限,要想表示其他语言的编码,还是要使用Unicode来表示,可以说Unicode是ASCII 的超集。
Unicode全称 Unicode Translation Format,又叫做统一码、万国码、单一码。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。
Unicode的实现方式(也就是编码方式)有很多种,常见的是UTF-8、UTF-16、UTF-32和USC-2。
(2)UTF-8
UTF-8是使用最广泛的Unicode编码方式,它是一种可变长的编码方式,可以是1—4个字节不等,它可以完全兼容ASCII码的128个字符。
注意: UTF-8 是一种编码方式,Unicode是一个字符集合。
UTF-8的编码规则:
对于单字节的符号,字节的第一位为0,后面的7位为这个字符的Unicode编码,因此对于英文字母,它的Unicode编码和ACSII编码一样。
对于n字节的符号,第一个字节的前n位都是1,第n+1位设为0,后面字节的前两位一律设为10,剩下的没有提及的二进制位,全部为这个符号的Unicode码 。
来看一下具体的Unicode编号范围与对应的UTF-8二进制格式 :
编码范围(编号对应的十进制数)二进制格式0x00—0x7F (0-127)0xxxxxxx0x80—0x7FF (128-2047)110xxxxx 10xxxxxx0x800—0xFFFF  (2048-65535)1110xxxx 10xxxxxx 10xxxxxx0x10000—0x10FFFF  (65536以上)11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
那该如何通过具体的Unicode编码,进行具体的UTF-8编码呢?步骤如下:
找到该Unicode编码的所在的编号范围,进而找到与之对应的二进制格式
将Unicode编码转换为二进制数(去掉最高位的0)
将二进制数从右往左一次填入二进制格式的X中,如果有X未填,就设为0
来看一个实际的例子:
“马” 字的Unicode编码是:0x9A6C,整数编号是39532
(1)首选确定了该字符在第三个范围内,它的格式是 1110xxxx 10xxxxxx 10xxxxxx
(2)39532对应的二进制数为1001 1010 0110 1100
(3)将二进制数填入X中,结果是:11101001 10101001 10101100
(3)UTF-16
1. 平面的概念
在了解UTF-16之前,先看一下平面的概念:
Unicode编码中有很多很多的字符,它并不是一次性定义的,而是分区进行定义的,每个区存放65536(216)个字符,这称为一个平面,目前总共有17 个平面。
最前面的一个平面称为基本平面,它的码点从0 — 216-1,写成16进制就是U+0000 — U+FFFF,那剩下的16个平面就是辅助平面,码点范围是 U+10000—U+10FFFF。
2. UTF-16 概念:
UTF-16也是Unicode编码集的一种编码形式,把Unicode字符集的抽象码位映射为16位长的整数(即码元)的序列,用于数据存储或传递。Unicode字符的码位需要1个或者2个16位长的码元来表示,因此UTF-16也是用变长字节表示的。
3. UTF-16 编码规则:
编号在 U+0000—U+FFFF 的字符(常用字符集),直接用两个字节表示。
编号在 U+10000—U+10FFFF 之间的字符,需要用四个字节表示。
4. 编码识别
那么问题来了,当遇到两个字节时,怎么知道是把它当做一个字符还是和后面的两个字节一起当做一个字符呢?
UTF-16 编码肯定也考虑到了这个问题,在基本平面内,从 U+D800 — U+DFFF 是一个空段,也就是说这个区间的码点不对应任何的字符,因此这些空段就可以用来映射辅助平面的字符。
辅助平面共有 220 个字符位,因此表示这些字符至少需要 20 个二进制位。UTF-16 将这 20 个二进制位分成两半,前 10 位映射在 U+D800 — U+DBFF,称为高位(H),后 10 位映射在 U+DC00 — U+DFFF,称为低位(L)。这就相当于,将一个辅助平面的字符拆成了两个基本平面的字符来表示。
因此,当遇到两个字节时,发现它的码点在 U+D800 —U+DBFF之间,就可以知道,它后面的两个字节的码点应该在 U+DC00 — U+DFFF 之间,这四个字节必须放在一起进行解读。
5. 举例说明
以 " " 字为例,它的 Unicode 码点为 0x21800,该码点超出了基本平面的范围,因此需要用四个字节来表示,步骤如下:
首先计算超出部分的结果:0x21800 - 0x10000
将上面的计算结果转为20位的二进制数,不足20位就在前面补0,结果为:0001000110 0000000000
将得到的两个10位二进制数分别对应到两个区间中
U+D800 对应的二进制数为 1101100000000000, 将0001000110填充在它的后10 个二进制位,得到 1101100001000110,转成 16 进制数为 0xD846。同理,低位为 0xDC00,所以这个字的UTF-16 编码为 0xD846 0xDC00
(4) UTF-32
UTF-32 就是字符所对应编号的整数二进制形式,每个字符占四个字节,这个是直接进行转换的。该编码方式占用的储存空间较多,所以使用较少。
比如“马” 字的Unicode编号是:U+9A6C,整数编号是39532,直接转化为二进制:1001 1010 0110 1100,这就是它的UTF-32编码。
(5)总结
Unicode、UTF-8、UTF-16、UTF-32有什么区别?
Unicode 是编码字符集(字符集),而UTF-8、UTF-16、UTF-32是字符集编码(编码规则);
UTF-16 使用变长码元序列的编码方式,相较于定长码元序列的UTF-32算法更复杂,甚至比同样是变长码元序列的UTF-8也更为复杂,因为其引入了独特的代理对这样的代理机制;
UTF-8需要判断每个字节中的开头标志信息,所以如果某个字节在传送过程中出错了,就会导致后面的字节也会解析出错;而UTF-16不会判断开头标志,即使错也只会错一个字符,所以容错能力教强;
如果字符内容全部英文或英文与其他文字混合,但英文占绝大部分,那么用UTF-8就比UTF-16节省了很多空间;而如果字符内容全部是中文这样类似的字符或者混合字符中中文占绝大多数,那么UTF-16就占优势了,可以节省很多空间;
3 JavaScript脚本延迟加载的方式有哪些?
延迟加载就是等页面加载完成之后再加载 JavaScript 文件。 js 延迟加载有助于提高页面加载速度。
一般有以下几种方式:
defer 属性: 给 js 脚本添加 defer 属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。
async 属性: 给 js 脚本添加 async 属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js 脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。
动态创建 DOM 方式: 动态创建 DOM 标签的方式,可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本。
使用 setTimeout 延迟方法: 设置一个定时器来延迟加载js脚本文件
让 JS 最后加载: 将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行
4 常用的正则表达式有哪些?
5 JavaScript有哪些内置对象
全局的对象( global objects )或称标准内置对象,不要和 "全局对象(global object)" 混淆。这里说的全局的对象是说在
全局作用域里的对象。全局作用域中的其他对象可以由用户的脚本创建或由宿主程序提供。
标准内置对象的分类:
(1)值属性,这些全局属性返回一个简单值,这些值没有自己的属性和方法。例如 Infinity、NaN、undefined、null 字面量
(2)函数属性,全局函数可以直接调用,不需要在调用时指定所属对象,执行结束后会将结果直接返回给调用者。例如 eval()、parseFloat()、parseInt() 等
(3)基本对象,基本对象是定义或使用其他对象的基础。基本对象包括一般对象、函数对象和错误对象。例如 Object、Function、Boolean、Symbol、Error 等
(4)数字和日期对象,用来表示数字、日期和执行数学计算的对象。例如 Number、Math、Date
(5)字符串,用来表示和操作字符串的对象。例如 String、RegExp
(6)可索引的集合对象,这些对象表示按照索引值来排序的数据集合,包括数组和类型数组,以及类数组结构的对象。例如 Array
(7)使用键的集合对象,这些集合对象在存储数据时会使用到键,支持按照插入顺序来迭代元素。
例如 Map、Set、WeakMap、WeakSet
(8)矢量集合,SIMD 矢量集合中的数据会被组织为一个数据序列。
例如 SIMD 等
(9)结构化数据,这些对象用来表示和操作结构化的缓冲区数据,或使用 JSON 编码的数据。例如 JSON 等
(10)控制抽象对象
例如 Promise、Generator 等
(11)反射。例如 Reflect、Proxy
(12)国际化,为了支持多语言处理而加入 ECMAScript 的对象。例如 Intl、Intl.Collator 等
(13)WebAssembly
(14)其他。例如 arguments
js 中的内置对象主要指的是在程序执行前存在全局作用域里的由 js 定义的一些全局值属性、函数和用来实例化其他对象的构造函数对象。一般经常用到的如全局变量值 NaN、undefined,全局函数如 parseInt()、parseFloat() 用来实例化对象的构造函数如 Date、Object 等,还有提供数学计算的单体内置对象如 Math 对象。
作者:CUGGZ
链接:https://juejin.cn/post/6940945178899251230
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


原生API

DOM相关

DOM(文档对象模型)之操作DOM的API总结_LoveyL0201的博客-CSDN博客

js中简单改变HTML中样式(设置颜色,宽度,背景色等以及如何取消自定义设置)_coding_uncle的博客-CSDN博客_js改变css样式背景色

原生JS设置CSS样式的几种方式 - 云+社区 - 腾讯云

传递
html中,一切都是节点
DOM对象
对象	描述
Document:文档对象	每个载入浏览器的 HTML 文档都会成为 Document 对象
Element:元素对象	Element 对象可以拥有类型为元素节点、文本节点、注释节点的子节点。
Attribute:节点属性对象	Attr 对象表示 HTML 属性
Event:事件对象	事件在其中发生的元素、键盘按键的状态、鼠标的位置、鼠标按钮的状
Document文档对象
Document对象所有属性
属性	描述
document.body	获取body
document.Head	获取head
document.Element	获取html
document.cookie	获取cookie
document.domain	当前文档域名,可做跨域操作
document.lastModified	文档最后修改日期时间
document.referrer	当前文档的url
document.title	标题
document.URL	当前文档的URL
Document常用方法
方法	描述
document.write()	文档写入内容
document.open()	打开一个流,以收集来自任何 document.write() 或 document.writeln() 方法的输出。
document.close()	关闭用 document.open() 方法打开的输出流,并显示选定的数据。
document.writeIn()	等同于 write() 方法,不同的是在每个表达式之后写一个换行符
document.getElementById()	通过id获取元素
document.getElementsByName()	通过name获取相关元素数组 如果没有这个标签匹配的元素, 返回一个空的伪数组
document.getElementsByTagName()	通过标签获取相关元素数组 不能使用forEach循环  伪数组
document.getElementsByClassName()	通过class获取相关元素数组 不能使用forEach循环  伪数组
document.querySelector()	获取第一个匹配条件的标签对象 --- 只会获取一个标签对象  伪数组
document.querySelectorAll()	获取所有匹配条件的标签对象 执行结果是伪数组
document.createAttribute()	创建属性对象
document.createElement()	创建元素对象
document.createTextNode()	创建文本对象
document.createComment()	创建注释
element文档对象
element元素对象常用的方法
方法	描述
元素增,删,改,克隆	
appendChild(doc)	插入节点到最后
insertBefore(ndoc, oldoc)	插入节点到某个节点之前
removeChild(doc)	移除该节点
replaceChild(doc)	替换节点
cloneNode()	克隆节点
cloneNode(true)	克隆节点及其内容
getAttribute()	获取属性
setAttribute()	设置属性
removeAttribute()	移除属性
getAttributeNode()	指定属性节点
setAttributeNode()	设置属性节点
removeAttributeNode()	移除属性节点
getElementsByTagName()	指定标签名的所有子元素的集合
nodelist.item()	NodeList 中位于指定下标的节点
.classList.add()	添加class
.classList.remove()	移除class
element元素对象常用的属性
属性	描述
id	元素id
style	样式
className	class属性
innerHML	标签内容
innerText	文本内容
childNodes	获取元素子节点
parentNode	获取元素父节点
attributes	获取所有属性
children	获取所有标签子节点
firstchild	第一个子节点
lastchild	最后一个子节点
firstElementchild	第一个标签子节点
lastElementChild	最后一个标签子节点
previousSibling	上一个兄弟节点
nextsibling	下一个兄弟节点
previousElementsibling	上一个标签
nextElemntSibling	下一个标签
parentNode	父级节点
parentElement	父级标签节点
nodeName	名字:元素节点--标签名称、属性节点--属性名、文本节点--#text、注释节点--#comment
nodeType	节点类型:1元素, 2属性 3文本, 8注释
nodeValue	元素值:属性值、文本内容、注释内容
nodelist.length	NodeList 中的节点数
clientHeight	高度-内容+padding
Clientwidth	宽度
offsetHeight	高度-内容+padding+border
Offsetwidth	宽度
ClientTop	上边框宽度
clientLeft	做边框宽度
offsetTop	父物体顶部距离
offsetLeft	父物体左侧距离
DOM事件操作
名称	描述
click	点击事件
dbclick	双击事件
contextmenu	右键点击事件
mousedown	按下事件,执行一次
mouseup	抬起事件
mousemove	鼠标移动
mouseover	移入
mouseout	移除
mouseenter	移入,不发生冒泡
mouseleave	移除,不冒泡
获取点击时的事件对象
E/event
IE低版本
Window.event
兼容写法:var e=e||window.event
兼容事件:ele.addEventListener(type,fun)
        ele.attachEvent('on'+type,fun)
删除事件:ele.removeEventListener(type,fun)
        ele.detachEvent('on'+type,fun)
阻止事件:
        e.stopPropagation()
        e.cancelBubble=true
阻止默认事件执行:
    e.preventDefault
    e.returnValue=false

BOM相关

BOM全称brower object model(浏览器对象模型),用于管理窗口及窗口间的通讯,其核心对象是window。称其为窗口,可能并不准确。因为,有的浏览器窗口可能包含多个标签页,每个标签页都有自己的window对象。本文将详细该内容

Screen
clientHeight也可以表示页面大小(不包含滚动条)
innerHeight 表示的是页面大小+底部滚动条的高度 = clientHeight + 滚动条高度
outerHeight属性用于表示浏览器窗口本身的尺寸(带导航栏的高度)
screen.availHeight:属性返回访问者屏幕的宽/高度,以像素计,减去界面特性,比如窗口任务栏。
兼容性写法:window.innerHeight||document.documentElement.clientHeight||document.body.clientHeigh
----------
Location
location.herf	       当前url
location.hostname	主机域名
location.pathname	当前页面路径和文件名
location.port	端口
location.protocol	协议(http/https)
location.assign	加载新的文档
location.search	url参数
-------
History  浏览器历史,可以不用写window这个前缀
history.length	次数
history.back	上一页
history.forward	下一页
history.go	小括号中,设定数值和 正负号,+数值 向下一个跳转的次数,-数值 向上一个跳转的次数,次数计算 : 结束页面 - 起始页面 ,错误跳转次数,没有执行效果
--------
navigator 获取浏览器相关信息   判断demo 	型号,内核,版本,平台 demo https://blog.csdn.net/zjjcchina/article/details/121375236
navagator.appVersion	浏览器版本信息
navagator.appName	浏览器名称
navagator.platform	操作系统
navagator.geolocation	位置信息包括经度longitude和纬度latitude

Object

ct.cre
ate()
Object.defineProperties() 直接在一个对象上定义新的属性或修改现有属性,并返回该对象。
Object.defineProperty() 会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
Object.entries()Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)
Object.freeze()
Object.fromEntries() 方法把键值对列表转换为一个对象
Object.getOwnPropertyDescriptor() 方法返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)
Object.getOwnPropertyDescriptors() 方法用来获取一个对象的所有自身属性的描述符。
Object.getOwnPropertyNames() 由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。
Object.getOwnPropertySymbols() 方法返回一个给定对象自身的所有 Symbol 属性的数组。
Object.getPrototypeOf() 方法返回指定对象的原型(内部[[Prototype]]属性的值)。
Object.prototype.hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。
Object.is() 判断两个值是否为同一个值
Object.prototype.isPrototypeOf() 方法用于测试一个对象是否存在于另一个对象的原型链上。
Object.keys()
Object.setPrototypeOf() 方法设置一个指定的对象的原型 ( 即, 内部[[Prototype]]属性)到另一个对象或 null。
Object.prototype.toLocaleString() 方法返回一个该对象的字符串表示。此方法被用于派生对象为了特定语言环境的目的(locale-specific purposes)而重载使用。
toString() toString()方法返回一个表示该对象的字符串。
Object.prototype.valueOf() valueOf() 方法返回指定对象的原始值。
-------------
var obj = {};
Object.defineProperties(obj, {
  'property1': {
    value: true,
    writable: true
  'property2': {
    value: 'Hello',
    writable: false
  // etc. etc.
const object1 = {
  a: 'somestring',
  b: 42
for (const [key, value] of Object.entries(object1)) {
  console.log(`${key}: ${value}`);
// expected output:
// "a: somestring"
// "b: 42"
const entries = new Map([
  ['foo', 'bar'],
  ['baz', 42]
const obj = Object.fromEntries(entries);
console.log(obj);
// expected output: Object { foo: "bar", baz: 42 }
const object1 = {
  property1: 42
const descriptor1 = Object.getOwnPropertyDescriptor(object1, 'property1');
console.log(descriptor1.configurable);
// expected output: true
console.log(descriptor1.value);
// expected output: 42
var arr = ["a", "b", "c"];
console.log(Object.getOwnPropertyNames(arr).sort()); // ["0", "1", "2", "length"]
var obj = {};
var a = Symbol("a");
var b = Symbol.for("b");
obj[a] = "localSymbol";
obj[b] = "globalSymbol";
var objectSymbols = Object.getOwnPropertySymbols(obj);
console.log(objectSymbols.length); // 2
console.log(objectSymbols)         // [Symbol(a), Symbol(b)]
console.log(objectSymbols[0])      // Symbol(a)
const prototype1 = {};
const object1 = Object.create(prototype1);
console.log(Object.getPrototypeOf(object1) === prototype1);
// expected output: true
var dict = Object.setPrototypeOf({}, null);

正则考察

添加千分符 正则表达式/\B(?=(\d{3})+(?!\d))/解释

blog.csdn.net/qq_371525 添加千分符 正则表达式/\B(?=(\d{3})+(?!\d))/解释

千分位分隔符: /\B(?=(\d{3})+(?!\d))/g
验证邮箱:  /^[\w_-]+@[\w_-]+(\.[\w_-]+)+$/
手机号: ^(1[3-9])\d{9}$
// (1)匹配 16 进制颜色值
var regex = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g;
// (2)匹配日期,如 yyyy-mm-dd 格式
var regex = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;
// (3)匹配 qq 号
var regex = /^[1-9][0-9]{4,10}$/g;
// (4)手机号码正则
var regex = /^1[34578]\d{9}$/g;
// (5)用户名正则
var regex = /^[a-zA-Z\$][a-zA-Z0-9_\$]{4,16}$/;
添加图片注释,不超过 140 字(可选)

以下列出 ?=、?<=、?!、?<! 的使用区别

exp1(?=exp2) :查找 exp2 前面的 exp1。

添加图片注释,不超过 140 字(可选)

(?<=exp2)exp1 :查找 exp2 后面的 exp1。

添加图片注释,不超过 140 字(可选)

exp1(?!exp2) :查找后面不是 exp2 的 exp1。

添加图片注释,不超过 140 字(可选)


(?<!exp2)exp1 :查找前面不是 exp2 的 exp1。

添加图片注释,不超过 140 字(可选)

函数的扩展

不适合用箭头函数的场景:由于箭头函数使得this从“动态”变成“静态”,下面两个场合不应该使用箭头函数。第一个场合是定义对象的方法,且该方法内部包括this。第二个场合是需要动态this的时候,也不应使用箭头函数。

const cat = {
  lives: 9,
  jumps: () => {
    this.lives--;
上面代码中,cat.jumps()方法是一个箭头函数,这是错误的。调用cat.jumps()时,如果是普通函数,该方法内部的this指向cat;如果写成上面那样的箭头函数,使得this指向全局对象,因此不会得到预期结果。这是因为对象不构成单独的作用域,导致jumps箭头函数定义时的作用域就是全局作用域。
globalThis.s = 21;
const obj = {
  s: 42,
  m: () => console.log(this.s)
obj.m() // 21
上面例子中,obj.m()使用箭头函数定义。JavaScript 引擎的处理方法是,先在全局空间生成这个箭头函数,然后赋值给obj.m,这导致箭头函数内部的this指向全局对象,所以obj.m()输出的是全局空间的21,而不是对象内部的42。上面的代码实际上等同于下面的代码。
var button = document.getElementById('press');
button.addEventListener('click', () => {
  this.classList.toggle('on');
上面代码运行时,点击按钮会报错,因为button的监听函数是一个箭头函数,导致里面的this就是全局对象。如果改成普通函数,this就会动态指向被点击的按钮对象。

尾递归优化


Number

Number.MAX_SAFE_INTEGER   常量表示在 JavaScript 中最大的安全整数(maxinum safe integer)(2 53- 1)。 
Number.MAX_VALUE Number.MAX_VALUE属性表示在 JavaScript 里所能表示的最大数值。MAX_VALUE 属性值接近于1.79E+308。大于MAX_VALUE 的值代表 "Infinity"。 
Number.MIN_SAFE_INTEGER Number.MIN_SAFE_INTEGER代表在 JavaScript中最小的安全的integer型数字 (-(2 53- 1)). 
Number.MIN_VALUE Number.MIN_VALUE属性表示在 JavaScript 中所能表示的最小的正值。MIN_VALUE 属性是 JavaScript 里最接近 0 的正值,而不是最小的负值。MIN_VALUE 的值约为 5e-324。小于 MIN_VALUE ("underflow values") 的值将会转换为 0。 
Number.NaN Number.NaN 表示“非数字”(Not-A-Number)。和 NaN 相同。不必创建一个 Number 实例来访问该属性,使用 Number.NaN 来访问该静态属性。 
Number.NEGATIVE_INFINITY Number.NEGATIVE_INFINITY 属性表示负无穷大。
不用创建一个 Number 实例,使用 Number.NEGATIVE_INFINITY 来访问该静态属性。 
Number.POSITIVE_INFINITY Number.POSITIVE_INFINITY 属性表示正无穷大。
不必创建一个 Number 实例,可使用 Number.POSITIVE_INFINITY 来访问该静态属性。
-----------
Number.isFinite() 方法用来检测传入的参数是否是一个有穷数。 
Number.isInteger() 方法用来判断给定的参数是否为整数。 
Number.isNaN() 方法确定传递的值是否为NaN,并且检查其类型是否为Number。它是原来的全局isNaN()的更稳妥的版本。 
Number.isSafeInteger() Number.isSafeInteger()方法用来判断传入的参数值是否是一个“安全整数”(safe integer)。 
Number.parseFloat()可以把一个字符串解析成浮点数。该方法与全局的parseFloat()函数相同,并且处于 ECMAScript 6 规范中(用于全局变量的模块化)。 
Number.parseInt()  number.parseInt() 方法依据指定基数 [ 参数 radix 的值],把字符串 [ 参数 string 的值] 解析成整数。
Number.prototype.toPrecision() toPrecision()方法以指定的精度返回该数值对象的字符串表示。 
Number.prototype.toSource()
Number.prototype.toString()
Number.prototype.valueOf()
var numObj = new Number(10);
console.log(typeof numObj); // object
var num = numObj.valueOf();
console.log(num);           // 10
console.log(typeof num);    // number

String

string
str.slice(start,end):提取字符串的某一部分,并放回一个新的字符串,包括start,但是不包括end
参数: 以0开始的位置,可选结束的位置   返回值一个截取的新的字符串
不会改变原来的值
str.subString(start,end)
substring() 方法返回一个字符串在开始索引到结束索引之间的一个子集, 或从开始索引直到字符串的末尾的一个子集。包括start,但是不包括end
参数:开始位置的索引,结束位置的索引 返回值,包含字符串指定部分新的字符串
str.slice(start,end)和str.subString(start,end) 区别
substring与slice的两个参数当为正数时基本一致,除了substring会比较两个参数大小,小的排在前面。
substring的第一个参数为负数时,则自动认为是0,而第二个参数为负数时,由于表示的是结束值,那绝对返回时空字符串了。
slice的第一个参数为负数时,则会将字符串长度与此负数相加(和倒着数一致),而第二个参数为负数时,同样是将字符串长度
与此负数相加(和倒着数一致)。
3 str.concat(str1,str2....)
将一个或多个字符串相连,返回一个新的字符串  参数:一个或多个字符串   返回值: 新的字符串
不改变原来字符串
性能不如+,+=
4 str.split(分隔符, 分隔片段数量)  将字符串对象分割为字符串数组  参数,分隔符,以及要截取分隔片段数量
返回值:以分隔符出现位置分隔而出的array
5 str.indexOf(查找字符串,开始查找位置)  查找特定字符串在str出现第一次的位置,未找到返回-1
参数: 要查找的值,开始查找的位置  返回值,第一次出现的索引,未找到返回-1
不改变原来字符串
6 str.lastIndexOf(查找字符串,开始查找位置) 查找特定字符串在str出现最后一次的位置,未找到返回-1
参数: 要查找的值,开始查找的位置  返回值,最后一次出现的索引,未找到返回-1
不改变原来字符串
7 str.charAt(index)  从一个字符串中返回index位置的字符
参数 index 一个介于0和字符串长度减1之间的整数。   返回值: index位置的字符串
不改变原字符
8 str.match(正则)  在字符串中满足正则的元素组成的数组
参数: 筛选的正则   返回值: 满足正则的字符串的字符串
9 str.search(正则)  在str 搜索符合正则的元素第一个元素的索引
参数:正则  返回值:返回字符串满足正则匹配的第一次出现的索引,否则返回-1
10 trimStart(),trimEnd() :ES2019对字符串实例新增了trimStart()和trimEnd()这两个方法。它们的行为与trim()一致,trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。
参数:    返回值 新字符串
不会改变原字符串
11 at() 方法接受一个整数作为参数,返回参数指定位置的字符,支持负索引(即倒数的位置)。
const sentence = 'The quick brown fox jumps over the lazy dog.';
console.log(sentence.at(3)); // ''

Array

返回新数组: slice concat filter

不会改变原数组 的方法有 slice concat + findindex(indexof lastindexof) + 所有数组循环方法(some every forEach filter map reduce) Array.from

循环中可以跳出循环的有 (标准for循环 for in for of 三个只能break停止,不能return) every some(return)

不能跳出循环的右: forEach filter map reduce

for in 遍历对象和数组 ,会遍历任意其他自定义添加的属性 , for...of: Arrays(数组), Strings(字符串), Maps(映射), Sets(集合)等可迭代的数据结构(任何具有 Symbol.iterator 属性的元素都是可迭代的。), 使用 break, continue 和 return

for in 循环数组 返回 字符串形式index,不一定按次序访问元素(不建议)

for in 循环对象 返回 字符串的obj的key

使用 hasOwnProperty 只遍历对象本身属性而不是原型

for of 循环 返回 每个元素value

function fun4(){
var a=[1,2,3,4,5];
Array.prototype.age=13;
for(var i in a){
  if( a.hasOwnProperty( i ) ) {  // 使用 hasOwnProperty 只遍历对象本身属性而不是原型
    console.log("a["+i+"]: "+a[i]);
const array1 = ['a', 'b', 'c'];
const obj = { key1: 1, key2:2}
for (const element in array1) {
  console.log(element);
}    '0' '1' '2'
for (const element in obj) {
  console.log(element);
}    'key1' 'key2'
for (const element of array1) {
  console.log(element);
}    'a' 'b' 'c'

targetArray.push('a') 添加到数组结尾

参数是单个元素,或多个元素

返回值是新数组的数目

改变原数组,可用targetArray来获取添加后的数组 实现: 时间复杂度是O(n) 空间复杂度是0

 Array.prototype.push = function ( ...elements ) {
            var initLength = this.length   // 保存一下最初的长度
            for ( var i = 0; i < elements.length; i++ ) {
                this[ initArrLength + i ] = elements[ i ]   
            return this.length
        }

targetArray.pop() 删除数组最后一个元素

无参数

返回值是删除的最后一个元素。

改变原数组,可用targetArray 来获取删除后的数组

实现:

   Array.prototype.pop = function () {
            var lastItem = this[ this.length-1 ]
            arr.length--; // 这里我通过数组长度减一,自动就减掉数组的最后一项
            return lastItem
        }

targetArray.shift() 删除数组第一个元素

无参数

返回值是删除的第一个元素

改变原数组,可用targetArray 来获取删除后的数组

实现:

Array.prototype.shift = function () {
        var fitstItem = this[ 0 ]
        for ( var i = 0; i < arr.length; i++ ) {
            arr[ i ] = arr[ i + 1 ]
        arr.length--;    // 这里我通过数组长度减一,自动就减掉数组的最后一项
        return fitstItem
    }

targetArray.unshift('a') 将元素添加到数组开头

参数是单个元素或多个元素

返回值是 新数组的数目

改变原数组,可用targetArray来获取添加后的数组。

实现:

Array.prototype.unshift = function ( ...elements ) {
            for ( let i = this.length + ( elements.length - 1 ); i >= 0; i-- ) {
                this[ i ] = this[ i - ( elements.length ) ]
            console.log( this )
            for ( var i = 0; i < elements.length; i++ ) {
                this[ i ] = elements[ i ]
            return this.length
        }

targetArray.sort(fn || null) 对数组进行排序,无参数按照Unicode位点进行排序。

参数是 空或者一个函数

返回值是排序后的数组

改变原来的数组,可用targetArray来获取排序后的数组

数组排序 targetArray.sort((a,b)=>a-b)

添加图片注释,不超过 140 字(可选)

具体源码实现 targetArray.reverse() 对数组进行反转

参数 空

返回值是排序后的数组

改变原来的数组,可用targetArray来获取排序后的数组

实现:

var arr = [1,2,3,4,5];
Array.prototype.myReverse = function (){
  for(var i=0;i<this.length/2; i++){
    var temp =this[i];
    this[i] = this[this.length -1-i];
    this[this.length-1-i] = temp;}
    return this;
arr.myReverse();

targetArray.slice(start,end) 切取数组的某一部分

参数是 可选的开始结束

返回值是截取的数组

并不会改变原数组,可以用tar

如何获取截取后的数组?

实现:

Array.prototype.slice = function(begin, end){
    let result = []
    begin = begin || 0
    end = end || this.length
    for(let i = begin; i< end; i++){
        result.push(this[i])
    return result
}

于是很多前端用 slice 来将伪数组,转化成数组

array = Array.prototye.slice.call(arrayLike)
array = [].slice.call(arrayLike)

ES 6 看不下去这种蹩脚的转化方法,出了一个新的 API

array = Array.from(arrayLike)

专门用来将伪数组转化成真数组。

P.S. 伪数组与真数组的区别就是:伪数组的原型链中没有 Array.prototype,而真数组的原型链中有 Array.prototype。因此伪数组没有 pop、join 等属性。

targetArray.splice(start,num,addEle,....) 是为了解决 获取截取后的数组因为slice为切片,单纯截取的功能,

splice 是删除以及添加功能

参数 开始删除的位置,删除的个数, 添加的元素

返回值是 要删除的元素 数组

会改变原数组,splice后可以用原数组获取删除后的数组

targetArray.concat() 合并数组

参数是多个值 或者多个数组

返回值 是合并后新的数组

不会改变原来数组

targetArray.indexOf() 获取当前元素的索引

参数 是单个元素

返回值 元素在数组中的索引,如果不存在返回-1

不会改变原数组

targetArray.lastIndexOf() 获取当前元素在数组中最后一个出现的位置

参数 单个元素

返回值 元素在数组中的索引,不存在返回-1

不会改变原数组

targetArray.findIndex(fn) 返回满足查找函数第一个索引。

参数 :fn

返回值:符合条件的值得索引

不改变原数组

targetArray.includes() : 判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。

参数:判断的元素

返回值:布尔值

循环

targetArray.every 每个元素都满足需求返回true,只要有一个不满足返回false.

参数:函数(item,index,当前数组targetArray)=>{},this指针

返回值: true或者false 不会改变原数组。

targetArray.some 至少有一个满足就返回true,一个都不满足返回false.

参数:函数(item,index,当前数组targetArray)=>{},this指针

返回值: true或者false 比较 for in , for of , forEach 区别?

forEach: 缺点: 不能中断循环(使用break语句或使用return语句) 返回值 underfind

for…in: 可以遍历对象和数组 ,缺点: 循环不仅会遍历数组元素,还会遍历任意其他自定义添加的属性 break 可以中断,

for...of: for-of 循环不仅仅支持数组的遍历。同样适用于很多类似数组的对象, 字符串的遍历, 使用 break, continue 和 return

targetArray.forEach 循环每一次

参数: 函数(item,index,当前数组targetArray)=>{},this指针

返回值 underfind 不改变值

function Counter() {
    this.sum = 0;
    this.count = 0;
Counter.prototype.add = function(array) {
    array.forEach(function(entry) {
        this.sum += entry;
        ++this.count;
    }, this);
    //console.log(this);
var obj = new Counter();
obj.add([1, 3, 5, 7]);
obj.count; 
// 4 === (1+1+1+1)
obj.sum;
// 16 === (1+3+5+7)

targetArray.filter 筛选出一个新数组,符合条件的数组。

参数:函数(item,index,当前数组targetArray)=>{},this指针

返回值: 返回符合条件组成的数组。 targetArray.find 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined 参数: callack,thisArg

返回值: 返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined

targetArray.map 为一个数组每个元素执行元素,返回一个数组。

参数:函数(item,index,当前数组targetArray)=>{},this指针

返回值 每个执行元素后组成的数组。

targetArray.reduce 数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值

参数 fn ,初始值,fn四个参数:累加器 accumulator 当前值 currentValue 当前索引 currentIndex 数组 Array。

当没有初始值:循环过程如下:

[0, 1, 2, 3, 4].reduce(function(accumulator, currentValue, currentIndex, array){
  return accumulator + currentValue;
});
添加图片注释,不超过 140 字(可选)

当有初始值,执行 5次:

[0, 1, 2, 3, 4].reduce((accumulator, currentValue, currentIndex, array) => { return accumulator + currentValue; }, 10 );
添加图片注释,不超过 140 字(可选)

场景:求和,将二维数组转一维,计算数组中出现次数,数组去重

。。。。。

之前遗忘:

targetArray.join 将一个数组所有元素拼接为字符串

参数:数组元素拼接为字符串后,连接符号,默认为逗号

返回值: 所有数组元素连接的字符串。

var elements = ['Fire', 'Air', 'Water'];
console.log(elements.join());
// expected output: "Fire,Air,Water"

flat 会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

参数:深度

返回值: 一个包含将数组与子数组中所有元素的新数组。

var arr1 = [1, 2, [3, 4]];
arr1.flat(); 
// [1, 2, 3, 4]
var arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]
var arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2);
// [1, 2, 3, 4, 5, 6]
//使用 Infinity 作为深度,展开任意深度的嵌套数组
arr3.flat(Infinity); 
// [1, 2, 3, 4, 5, 6]

fill:方法用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引。

var array1 = [1, 2, 3, 4];
// fill with 0 from position 2 until position 4
console.log(array1.fill(0, 2, 4));
// expected output: [1, 2, 0, 0]
// fill with 5 from position 1
console.log(array1.fill(5, 1));
// expected output: [1, 5, 5, 5]
console.log(array1.fill(6));
// expected output: [6, 6, 6, 6]


reduce使用场景

aotu.io/notes/2016/04/1 exp-team.github.io/blog

字符统计/单词统计同理

let str = 'abcdaabc';
str.split('').reduce((res, cur) => {
    res[cur] ? res[cur] ++ : res[cur] = 1 // 如果cur第一次出现,记为1
    return res;                           // 否则记录数+1
}, {})

求加法(乘法同理)

let arr = [1, 2, 3, 4, 5];
arr.reduce((sum, curr) => sum + curr, 0);       // 得到15

此基础上还可以求平均值

求最大值(最小值同理)

let arr = [23,123,342,12];
let max = arr.reduce((pre,cur,index,arr) => {
  return pre > cur ? pre : cur
});

数组去重

let arr = [1, 2, 3, 4, 4, 1]
let newArr = arr.reduce((pre,cur) => {
    if(!pre.includes(cur)){
      return pre.concat(cur)
    }else{
      return pre
},[])                                     // 得到 [1, 2, 3, 4]

数组维度转换

let arr = [[0, 1], [2, 3], [4, 5]]      // 二维数组
let newArr = arr.reduce((pre,cur) => {
    return pre.concat(cur)              // 合并pre 与 cur, 并返回一个新数组
},[])
console.log(newArr);                    // 一维数组 [0, 1, 2, 3, 4, 5]


数组操作:

1 求最大值 方法:

添加图片注释,不超过 140 字(可选)

2 求数组加法

3 数组扁平化

4 数组去重

Array ES6

Array.from(arrayLike,mapFn) : 用于将类数组对象和可遍历对象转化为数组

arrayLike: Arrays(数组), Strings(字符串), Maps(映射), Sets(集合)

返回值: 新的数组实例, 不改变原数组

Array.from([1, 2, 3], x => x + x);

Array.of(1,2,3) : 创建一个具有可变数量参数的新数组实例

返回值: 新的 Array 实例。 不改变原数组


Canvas

全局函数

最精要解释,助你快速区分和记住encodeURI和encodeURIComponent_u014445339的博客-CSDN博客 两次encodeURI和URLDecode的原理分析_小流-CSDN博客

eval()
uneval()
isFinite()
isNaN()
parseFloat()
parseInt()
decodeURI()
decodeURIComponent()
encodeURI()。
encodeURIComponent()


错误对象。 你知道 JavaScript 中的错误对象有哪些类型吗?

错误对象是一种特殊的基本对象。它们拥有基本的 Error 类型,同时也有多种具体的错误类型。

  • Error
  • AggregateError
  • EvalError
  • InternalError
  • RangeError 创建一个 error 实例,表示错误的原因:数值变量或参数超出其有效范围。
  • ReferenceError 创建一个 error 实例,表示错误的原因:无效引用。
  • SyntaxError 创建一个error实例,表示错误的原因:eval()在解析代码的过程中发生的语法错误。换句话说,当 JS 引擎在解析代码时遇到不符合语言语法的令牌或令牌顺序时,将抛出 SyntaxError
  • TypeError 创建一个error实例,表示错误的原因:变量或参数不属于有效类型。
  • URIError 创建一个error实例,表示错误的原因:给 encodeURI( )或 decodeURl() 传递的参数无效。如果未正确使用全局URI处理功能,则会发生这种情况。

JavaSCript 错误监控如何做有哪些方法?

try-catch:同步错误,可预见性的错误, 回调

window.onerror: 同步异步,语法错误无法捕获,网络错误使用window.addEventListener('error') msg, url, row, col, error

Promise 错误: then第二参数(Promise不影响接下来Promise的执行),catch(当一个Promise发生了异常,剩下的Promise都不在执行) Promise 全局异常捕获事件 unhandledrejection。 window.addEventListener("unhandledrejection"

script error: 劫持 document.createElement,从根源上去为每个动态生成的脚本添加 crossOrigin 字段。

压缩代码定位到脚本异常位置: sourcemap

axios错误: this.axios.interceptors.response.use 第二参数捕获异常


iframe 错误: 如果你的 iframe 页面和你的主站是同域名的话,直接给 iframe 添加 onerror 事件即可。

如果你嵌入的 iframe 页面和你的主站不是同个域名的,但是 iframe 内容不属于第三方,

可以通过与 iframe 通信的方式将异常信息抛给主站接收


1 try-catch 异常处理

捕获 运行时非异步错误,对于语法错误和异步错误无能为力

语法错误: Uncaught SyntaxError: Invalid or unexpected token xxx

try {
  var error = 'error';   // 大写分号
} catch(e) {
  console.log('我感知不到错误');
  console.log(e);
}
添加图片注释,不超过 140 字(可选)

异步错误:

添加图片注释,不超过 140 字(可选)
try {
  setTimeout(() => {
    error        // 异步错误
} catch(e) {
  console.log('我感知不到错误');
  console.log(e);
}

2 window.onerror 异常处理

原理 事件冒泡

window.onerror 捕获异常能力比 try-catch 稍微强点,无论是 异步还是非异步错误 ,onerror 都能捕获到运行时错误

缺点: 语法错误无能为力,不能全局捕获到资源(如图片或脚本)的加载失败(使用window.addEventListener捕获) 无法捕获到网络异常的错误 (网络请求不会冒泡,必须在捕获阶段将其捕捉到)

<script>
    // msg:错误信息(字符串)。
   // url:发生错误的脚本URL(字符串)
   // row:发生错误的行号(数字)
   // col:发生错误的列号(数字)
   // error:Error对象(对象)
window.addEventListener('error', (msg, url, row, col, error) => {
  console.log('我知道 404 错误了');
  console.log(
    msg, url, row, col, error
  return true;
}, true);
</script>
<img src="./404.png" alt="">
无法判断 HTTP 的状态是 404 还是其他比如 500 等等,所以还需要配合服务端日志才进行排查分析才可以。
添加图片注释,不超过 140 字(可选)

onerror 主要是来捕获预料之外的错误,而 try-catch 则是用来在可预见情况下监控特定的错误

对于 onerror 这种全局捕获,最好写在所有 JS 脚本的前面,因为你无法保证你写的代码是否出错,如果写在后面,一旦发生错误的话是不会被 onerror 捕获到的。

3 Promise 错误

Promise 实例抛出异常而你没有用 catch 去捕获的话,onerror 或 try-catch 也无能为力,无法捕捉到错误。

最好添加一个 Promise 全局异常捕获事件 unhandledrejection

window.addEventListener("unhandledrejection", function(e){
  e.preventDefault()
  console.log('我知道 promise 的错误了');
  console.log(e.reason);
  return true;
Promise.reject('promise error');
new Promise((resolve, reject) => {
  reject('promise error');
new Promise((resolve) => {
  resolve();
}).then(() => {
  throw 'promise error'
});
添加图片注释,不超过 140 字(可选)

异常上报:

  1. 通过 Ajax 发送数据
  2. 动态创建 img 标签的形式

Script error: 劫持 document.createElement,从根源上去为每个动态生成的脚本添加 crossOrigin 字段。

document.createElement = (function() {
  const fn = document.createElement.bind(document);
  return function(type) {
    const result = fn(type);
    if(type === 'script') {
      result.crossOrigin = 'anonymous';
    return result;
})();
window.onerror = function (msg, url, row, col, error) {
  console.log('我知道错误了,也知道错误信息');
  console.log({
    msg,  url,  row, col, error
  return true;
$.ajax({