《JavaScript高级程序设计》笔记
基本概念
<noscript>
<noscript>标签:当页面不支持script或禁用了script时会显示<noscript>里面的内容。
<script>中的async和defer
1.`<script src="script.js"></script>`
读到就立即执行。
2.`<script async src="script.js"></script>`
和DOM并行进行(异步)。
2.`<script defer src="script.js"></script>`
和DOM并行进行(异步),但在所有`script.js`的执行解析完后,`DOMContentLoaded`事件触发完成之前。
数据类型
null是空对象指针,所以typeof null返回的是object,
'null'变为null:JSON.parse('null')
防篡改对象
preventExtensions:不能增,能删改
seal:不能增删,能改
freeze:不能增删改
| 对象属性 | 增 | 删 | 改 | 
|---|---|---|---|
| preventExtensions | × | ✓ | ✓ | 
| seal | × | × | ✓ | 
| freeze | × | × | × | 
不可扩展对象preventExtensions
Object.preventExtensions不能增,能删改
var obj = {a:1,b:2};
Object.preventExtensions(obj);
obj.c = 3;
console.log(obj.c); // undefined
delete obj.a;
console.log(obj); // {b: 2} 删除成功
obj.b = 'hello'
console.log(obj); // {b: "hello"} 修改成功
检测是否不可扩展Object.isExtensible(obj)
(false是不可扩展,true是正常对象)
Object.isExtensible(obj);// false
密封的对象seal
Object.seal不能增删,能改
var obj = {a:1,b:2};
Object.seal(obj);
obj.c = 3;
console.log(obj.c); // undefined
delete obj.a;
console.log(obj); // {a:1,b:2} 删除失败
obj.b = 'hello'
console.log(obj); // {a:1,b: "hello"} 修改成功
检测是否密封Object.isSealed(obj)
(false是正常,true是已经密封了)
Object.isSealed(obj);// true
冻结的对象freeze
Object.freeze不能增删改
var obj = {a:1,b:2};
Object.freeze(obj);
obj.c = 3;
console.log(obj.c); // undefined
delete obj.a;
console.log(obj); // {a:1,b:2} 删除失败
obj.b = 'hello'
console.log(obj); // {a:1,b:2} 修改失败
检测是否冻结Object.isFrozen(obj)
(false是正常,true是已经冻结了)
Object.isFrozen(obj);// true
setTimeout
// setTimeout的时间假设设置为1000,不是说1秒后立马会执行,而是尽快执行,把任务添加到了队列中,如果排到它了,就立马执行。
递归
arguments.callee:函数自身
用arguments.callee实现递归
function test(num) {
    console.log(num)
    if(num!==0){
        --num;
        arguments.callee(num)
    }
}
test(3)
3
2
1
但是arguments.callee已经被弃用了,所以可以尝试其它方法。
命名一个function
function test(num) {
    (function fn (){
        console.log(num)
        if(num !== 0) {
            --num;
            fn();
        }
    })();
}
test(3)
return和闭包
直接return
var a = 0;
function fn(){
    var a = 12;
    return a;
}
console.log(fn()); // 12
console.log(a); // 0
return function
var a = 0;
function fn() {
    var a = 12;
    return function(){
        return a
    };
}
console.log(fn()()); // 12
console.log(a); // 0
return 闭包
var a = 0;
function fn() {
    var a = 12;
    return (function(){
        return a
    })();
}
console.log(fn()); // 12
console.log(a); // 0
区别
1.直接return返回的是变量,闭包返回的是执行环境(所以在return function部分就要fn()()这样调用2次)。
2.闭包不是为了让函数外部拿到内部变量,而是为了保护私有变量不被更改。
3.return出来的是一个值(12),不是变量本身(a),此处的return是取得私有变量值的一种方法,跟闭包没有严格关系。
防抖和节流
概念
防抖:(停止后才1次)触发事件后n秒内只执行1次,如果n秒内又触发了事件,则会重新计算时间。
节流:(几秒1次)一定时间内只能执行1次。
应用场景
防抖:
- 搜索框搜索输入,只有用户停止输入时,才发送请求;
 - 手机号、邮箱号验证输入检测;
 - 窗口resize,只需等窗口调整完成后计算大小,防止重复渲染。
 
节流:
- 表单验证时重复点击提交按钮;
 - 滚动加载;
 - 浏览器搜索框联想功能。
 
实现原理
1、防抖
正常情况下,我希望它多久执行,假设邮箱验证正常情况是每隔1秒向后台发送请求,然后用户一直不停的在输入框输入,此时会不断的清除Timeout,直到停止调用方法1秒后才正常去向后台发送请求。
// 防抖【防止多次触发滚动事件】
var time = '';
handleDebounce () {
    console.log('调用')
    // 清除未执行的代码,重置回初始化状态
    if(timer){clearTimeout(timer);} 
    //开始一个新的任务
    timer = setTimeout(()=>{
        console.log('函数防抖');
    }, 1000);
},

2、节流
假设浏览器一直在不停滚动,我不可能等停止了再请求,也不可能一直请求。
var flag = false;
handleThrottle () {
  console.log('调用')
  if(flag){return}
  flag = false;
  setTimeout(()=>{
    console.log('函数节流');
    flag = true;
  },1000)
}

prototype 和 hasOwnProperty
Array.prototype.arr = function(){console.log('print arr')};
var a = [1,2,3];
a.arr(); // 'print arr'
Array.prototype.hasOwnProperty('arr'); // true
a.hasOwnProperty('arr'); // false
Array.hasOwnProperty('arr'); // false
let和闭包
let劫持作用域
用var时
console.log(str);
var str = 'hello';
打印出undefined。
相当于
var str ;
console.log(str);
str = 'hello';
用var 的话,变量名会提升,但并不会赋值。
用let时
console.log(str);
let str = 'hello';
报错VM67161:1 Uncaught ReferenceError: str is not defined
这里相当于直接console.log('未定义变量名'),此时的let已经劫持了var的作用域。
用闭包作用域解释为什么用let的for循环可以劫持数据。
假设我们想每隔1秒分别打印1、2、3、4、5。
for (var i = 1; i < 6; i++) {
    console.log(i)
    setTimeout(() => {
        console.log('print'+i)
    }, 1000 * i)
}
会打印1、2、3、4、5,然后每隔1秒打印一次'print6'.
因为任务流的关系,console.log(i)会先于setTimeout执行,等for循环的6次console执行完之后,队列里的setTimeout才会依次执行,而这个时候的i已经是6了。
用let可以劫持i的作用域。
for (var i = 1; i < 6; i++) {
    let j = i;
    console.log(j)
    setTimeout(() => {
        console.log('print'+j)
    }, 1000 * j)
}
此时就是先打印1、2、3、4、5,然后每隔1秒打印'print1'、'print2'...'print5'。但是,每次都会有新的j替代原来的j,所以可以直接在for循环里面定义let i = 1;
for (var i = 1; i < 6; i++) {
    console.log(i)
    setTimeout(() => {
        console.log('print'+i)
    }, 1000 * i)
}
function和object
function
var str = (()=> {
    var count = 0;
    function sum () { return ++count; };
    function reduce () {return --count;};
    return {
        sum,
        reduce
    }
})
此时的str是个function
简化下:
var str = (()=> {
    var count = 0;
    return {
        sum : ()=>{return ++count;},
        reduce : ()=>{return --count;}
    }
})();
str.sum(); // 1
此时的str是已经立即执行函数了,返回的是Object,是{sum:f,reduce:f},注意,这里的str是获取不到count的,只有return的数据能获取到。
Object
var obj = {
    count:0,
    sum : ()=>{return ++obj.count;},
    reduce : ()=>{return --obj.count;}
}
obj.sum(); // 1
这里的obj是Object,不同于str的是,它能获取到count,object里面的所有数据都能获取到。
区别
- Function只有return的方法才能获取到(闭包)
 - Function执行后返回的是对象
 
java对象
// 创建类——“人”
public class People{
    int age;
}
// 创建类——“男人”
public class MenPeople extends People {
    
}
// 创建对象
MenPeople xm = new MenPeople();
xm.age = 15;
js对象
var xm = {age:15}
https://www.jianshu.com/p/edf4d665d0df
https://www.cnblogs.com/yanyunpiaomaio/p/11025444.html
JavaScript函数调用及this参数
JS有4种方式调用函数
- 作为一个函数(
function)——fn()直接被调用 - 作为一个方法(
methods)——obj.fn(),关联在对象上调用,实现面向对象编程 - 作为一个构造函数(
constructor)——new Fn(),实例化一个新的对象 - 通过
apply或call方法调用 
对应的this的指向:
- 函数调用:
window或undefined - 方法调用:obj对象
 - 构造函数调用:实例化的对象
 aplly或call:第一个参数
详解:
函数调用
function fn(){
    console.log(this);
}
fn(); // window
严格模式下:
function fn(){
    "use strict"
    console.log(this);
}
fn(); // undefined
方法调用
var obj = {
    fn : function(){
        console.log(this);
    }
};
obj.fn() // 返回obj对象:{fn: ƒ}
构造函数调用
function Cat(x,y){
    this.x = x;
    this.y = y;
    console.log(this);
}
var c = new Cat(1,2);
c // Cat{x:1,y:2} 指向c对象
es6写法
class Point{
    constructor(x,y){
        this.x = x;
        this.y = y;
        console.log(this);
    }
}
var p = new Point(1,2)
p // Point{x:1,y:2} 指向p对象
aplly或call
var name = '张三';
var age = '24';
var obj = {
    name: this.name, // 此处的this指向window
    objAge: this.age, // 此处的this指向window
    fun: function(fm,t){
        console.log(this)
        console.log(this.name+'年龄 '+this.age + ' 来自'+fm+' 去往'+t); // 此处的fm和t就是要传入的参数
    }
}
var pd = {
    name: '彭丹',
    age:18
}
obj.fun.call(pd,'长沙','上海'); // 彭丹 年龄18 来自长沙 去往上海
obj.fun.apply(pd,['长沙','上海']); // 彭丹 年龄18 来自长沙 去往上海
obj.fun.bind(pd,'长沙','上海')(); // 彭丹 年龄18 来自长沙 去往上海
obj.fun.bind(pd,['长沙','上海'])(); // 彭丹 年龄18 来自长沙上海 去往undefined
this打印出来全都是{name: "彭丹", age: 18},就是第一个参数。
函数构造器
与构造函数名字类似,但无太大关系。
普通生成
var p = new Function('x','y','return x+y');
p(2,3)
动态生成
createFunction(){
    let arr = Array.from(arguments);
    var params = arr.splice(0,arr.length-1);
    var body = arr[0];
    return new Function(params,body);
},
test(){
    var sum = this.createFunction('x','y','return x + y');
    var chen = this.createFunction('x','y','return x * y');
    console.log(sum(3,2)) // 5
    console.log(chen(3,2)) // 6
},
函数生成器(generator)
function* test(){
    console.log(1);
    yield;
    console.log(2);
}
let item = test();
item.next();
setTimeout(()=>{
    item.next();
},3000)
1
隔3秒后
2
JavaScript面向对象
封装
生成对象
function Cat(name,color){
    this.name = name;
    this.color = color;
}
var cat1 = new Cat('大猫','黄色');
var cat2 = new Cat('小猫','黑色');
cat1; // Cat {name: "大猫", color: "黄色"} 指向Cat对象
cat2; // Cat {name: "小猫", color: "黑色"} 指向Cat对象
相当于我们平时用的数组中的:
var arr = new Array(3).fill(2);
var brr = new Array(5).fill(8);
arr; // (3) [2, 2, 2] 指向Array对象
brr; // (5) [8, 8, 8, 8, 8] 指向Array对象
只不过我们平时是直接用var arr = [1,2]的形式,和new Array是同一个意思。
对象的构造函数
function Cat(name,color){
    this.name = name;
    this.color = color;
}
这段代码里面的this.name = name就是构造函数,可以直接用es6语法糖的形式写:
es6语法糖class
class Cat{
    constructor(x,y){
        this.x = x;
        this.y = y;
    }
}
var cat1 = new Cat('大猫','黄色');
cat1; // Cat {name: "大猫", color: "黄色"} 指向Cat对象
constructor
所以,cat1实例含有constructor属性指向它(Cat)的构造函数。
cat1.constructor === Cat; // true
相当于我们平时用的数组中的:
[1,2].constructor === Array; // true
其它
[2].constructor(); // []
[2].constructor() === Array.prototype.constructor();
instanceof
JavaScript还提供了instanceof运算符,验证原型对象(Cat)与实例对象(cat1)之间的关系。
cat1 instanceof Cat; // true
相当于我们平时用的数组中的:
[1,2] instanceof Array; // true
原型对象添加方法
直接添加造成的问题
function Cat(name,color){
    this.name = name;
    this.color = color;
    this.type = '猫科动物';
    this.eat = function(){
        console.log('吃鱼')
    }
}
var cat1 = new Cat('大猫','黄色');
var cat2 = new Cat('小猫','黑色');
cat1.eat == cat2.eat; // false
此时eat方法占用了太多内存,并且它们没有指向同一个引用地址,永远不会相等。参考数组的其实是相等的。
[1].push == [2].push; // true
用prototype添加方法
function Cat(name,color){
    this.name = name;
    this.color = color;
}
Cat.prototype.type = '猫科动物';
Cat.prototype.eat = function(){
    console.log('吃鱼')
}
var cat1 = new Cat('大猫','黄色');
var cat2 = new Cat('小猫','黑色');
cat1.eat == cat2.eat; // true,它们是指向同一个内存地址下的方法
(就算不定义Cat的prototype,Cat也自带有prototype属性)
prototype模式的验证方法
判断对象和实例的关系isPrototypeOf
Cat.prototype.isPrototypeOf(cat1); // true
相当于我们平时用的数组中的:
Array.prototype.isPrototypeOf([]); // true
判断是本地属性还是prototype属性
cat1.hasOwnProperty('name'); // true
cat1.hasOwnProperty('type'); // false
in
'name' in cat1; // true
相当于我们平时用的数组中的:
'push' in []; // true
proto
一般情况下,实例对象的__proto__指向原型对象的prototype。
prototype被实例的__proto__指向
__proto__指向构造函数的prototype
__proto__存在于实例和构造函数的原型对象,而不是实例与构造函数。
如:
cat1.__proto__ === Cat.prototype; // true
相当于我们平时用的数组中的:
[].__proto__ === Array.prototype; // true
其它情况
function fn(){};
fn.__proto__ === Function.prototype; // true
把函数当作对象时,生成它的函数就是Function原型对象。
Function原型对象也同样适用此规则:
Function.__proto__ === Function.prototype; // true
Function.prototype.__proto__ == Object.prototype; // true 为了不指向自身的Function.prototype造成循环引用
Object函数也是一个Function函数:
Object.__proto__ === Function.prototype; // true
Object.prototype.__proto__ === null ; // true 为了不指向自身的Object.prototype造成循环引用
Object.prototype.__proto__==null是所有函数的终点
DOM也有原型链
<html>
  <head>
    <title>dom原型测试</title>
  </head>
  <body>
    <div id="test">test dom</div>
    <script type="text/javascript">
      HTMLElement.prototype.hello = function(){
        console.log(this);
      }
      var div = document.getElementById('test');
      div.hello();
    </script>
  </body>
</html>
Object.create实现类继承和克隆对象
Object.create实现类继承
先看不用Object.create来实现继承
function Pd(){
}
Pd.prototype = Array.prototype;
Pd.prototype.constructor = Pd;
var pdd = new Pd();
pdd.push(3);
console.log(pdd); // Pd [3] __proto__:Array(0)直接就是真正的数组的__proto__
效果:

此时打印Array.prototype.constructor会发现变成了undefined,已经改动了原生的Array.
用Object.create实现继承
function Pd(){
}
Pd.prototype = Object.create(Array.prototype);
Pd.prototype.constructor = Pd;
var pdd = new Pd();
pdd.push(3);
console.log(pdd); // Pd [3] __proto__:Array[__proto__:Array(0)]就是__proto__里面包含真正的数组的__proto__
效果:

区别
写法:
Pd.prototype = Array.prototype;和Pd.prototype = Object.create(Array.prototype);
返回值:
Pd [3] __proto__:Array(0)直接就是真正的数组的__proto__;Pd [3] __proto__:Array[__proto__:Array(0)]就是__proto__里面包含真正的数组的__proto__。
用Object.create实现继承自己的类并带参数
function Cat(name,color){
    this.name = name;
    this.color = color;
}
var cat1 = new Cat('大猫','黄色');
function Pd(name,color){
    Cat.call(this,name,color);
}
Pd.prototype = Object.create(Cat.prototype);
Pd.prototype.constructor = Pd;
var pdd = new Pd('小猫','白色');
console.log(cat1,pdd); // Cat {name: "大猫", color: "黄色"} Pd {name: "小猫", color: "白色"}
用原生写法实现继承自己的类并带参数
function Cat(name,color){
    this.name = name;
    this.color = color;
}
var cat1 = new Cat('大猫','黄色');
function Pd(name,color){
  Cat.call(this,name,color);
}
Pd.prototype = Cat.prototype;
Pd.prototype.constructor = Pd;
var pdd = new Pd('小猫','白色');
console.log(cat1,pdd); // Cat {name: "大猫", color: "黄色"} Pd {name: "小猫", color: "白色"}
用Object.create克隆对象
var obj1 = {a:2,b:{name:'小明'}};
var obj2 = Object.create(obj1);
console.log(obj2); // {}
obj2.a = 3;
obj2.b.name = '小红';
console.log(obj1); // {a:2,b:{name:'小红'}};
结论:obj1对象中的一级对象a:2并没有受影响,但二级对象b已经受影响。所以Object.create克隆的对象也只能实现一级对象的深拷贝。
obj2的具体值:

extends继承
class Cat{
    constructor(){
        console.log('cat');
    }
}
class Child extends Cat{
};
var cat = new Cat();
var child = new Child();
继承所有参数:
class Cat{
    constructor(name){
        this.name = name;
    }
}
class Child extends Cat{
    constructor(name){
        super(name);
    }
};
var cat = new Cat('1');
var child = new Child('2');
console.log(cat,child); // Cat {name: "1"} Child {name: "2"}
new.target方法判断是否父类
class Cat{
    constructor(){
        console.log(new.target);
        if (new.target === Cat) {
            console.log('父类');
        } else {
            console.log('子类');
        }
    }
}
class Child extends Cat{
    constructor(){
        super();
    }
};
var cat = new Cat();
var child = new Child();
new Array()和[]比较
性能
var startTime=new Date().getTime();
var a2 = new Object();
for(var i = 0;i<10000000;i++){
    a2[i] = [];
}
var endTime=new Date().getTime();
console.log('[]输出耗时:',endTime-startTime);
var startTime2=new Date().getTime();
var a = new Object();
for(var i = 0;i<10000000;i++){
    a[i] = new Array();
}
var endTime2=new Date().getTime();
console.log('new Array()输出耗时:',endTime2-startTime2);
结果:
[]输出耗时: 304
new Array()输出耗时: 600
每次结果不同,但大约都是new Array()是[]的两倍,时间越大,差距越大。
(最好用时间差相减来计算时间,用console.time可能会有先后的问题导致不准确。)
写法
[]是字面量,JSON格式的语法是引擎直接解释的;
new Array()需要调用Array的构造器。
JavaScript相等操作符(==)
两组操作符
相等:==(先转换再比较)
全等:===(仅比较不转换)
相等(==)规则
Boolean规则:Boolean(val):如果有一个操作数是Boolean值,则在比较前先将其转换为数值——false为0,true为1。
String&Number规则:Number(string):如果一个是String,一个是Number,则先将String转为Number。
Object规则:valueOf(obj):如果有一个是对象,则调用valueOf方法(数组调toString()方法)。

问题探讨
[] == []; // false
{} == {}; // false
[] == ![]; // true
{} == !{}; // false
[] == []和{} == {}是因为引用的对象指向不同的指针,所以不会相等。
一、[] == ![]
- 1:逻辑非(
!)的优先级高于相等操作符(==),所以先计算![]的boolean值false,此时比较的是:[] == false; - 2:根据上面提到的boolean规则,则需要把 
false转成0,此时比较的是:[] == 0; - 3:根据上面提到的Object规则,调用空数组的toString方法,即
[].toString()的值为'',此时比较的是:'' == 0; - 4:根据上面提到的String规则,将字符串转为数字,即
Number('')的值为0,此时比较的是:0 == 0。 
简化:
[] == ![] 转化:[] == false 转化: [] == 0 转化'' == 0 转化: 0 == 0。
二、{} == !{}
- 1:先计算
!{}得到false,此时比较的是:{} == false; - 2:调用Booean规则,计算
Boolean({})得到true,此时比较的是true == false。 
简化:
{} == !{} 转化:{} == false 转化:true == false。
MessageChannel
MessageChannel的基本使用
const {port1, port2} = new MessageChannel();
port1.onmessage = function(d) {
    console.log(`port1接收的消息是:${d.data}`);
}
port2.onmessage = function(d) {
    console.log(`port2接收的消息是:${d.data}`);
}
port1.postMessage('port1发送的消息');
port2.postMessage('port2发送的消息');
port1发送的由port2接收,port2发送的由port1接收。
也就是说,传过去的对象,接收到的时候已经不是原来的引用和指针了,这个时候再return出来,就是一个新的对象,所以肯定能实现深拷贝。
使用MessageChannel实现深拷贝
var obj = {id:1,name:{a:'xx'}};
function structuralClone(obj) {
    return new Promise((resolve) => {
        const {port1, port2} = new MessageChannel();
        port2.onmessage = ev => resolve(ev.data);
        port1.postMessage(obj);
    })
}
structuralClone(obj).then(res=>{
    console.log(res);
    var obj3 = res;
    obj3.name.a = 'obj3';
    console.log(obj,obj3);
})
<!-- 用promise是为了好传数据 -->
使用lodash.cloneDeep实现深拷贝
import _ from 'lodash'
var obj = {id:1,name:{a:'xx'},fn:function(){},un:undefined};
var obj2 = _.cloneDeep(obj);
obj2.name.a = 'obj2';
console.log(obj,obj2)


ES6…扩展(spread)/收集(rest)运算符详解
一、扩展运算符
我理解的,用()包起来就是扩展成单个值,用[]包起来就是扩展成数组。
1.代替apply
var test = function(a,b,c){
  console.log(a,b,c);
}
var arr = [1,2,3];
test(...arr); // 1 2 3
用apply的写法:
test.apply(null,arr);
2.代替concat
var arr1 = [1,2,3,4];
var arr2 = [0,...arr1,5,6];
console.log(arr2); // [0, 1, 2, 3, 4, 5, 6]
用concat的写法:
[0].concat(arr1,5,6); // [0, 1, 2, 3, 4, 5, 6]
3.代替split
var str = 'hello';
var arr3 = [...str];
console.log(arr3); // ["h", "e", "l", "l", "o"]
用split的写法:
'hello'.split(''); // ["h", "e", "l", "l", "o"]
二、收集运算符
1.接收不确定个数的形参
此功能和JAVA一样,当形参传入个数不确定时可用在形参上。
var rest2 = function(item, ...arr){
  console.log(item,arr);
}
rest2('hello',2,3,3,4); // hello [2, 3, 3, 4]
2.配合解构时使用
var [a,...temp] = [1,2,3,4];
console.log(a,temp); // 1 [2, 3, 4]
arguments参数的3种转数组方法
方法1:Array.prototype.slice.apply
方法2:Array.from
方法3:[...arguments]
var test3 = function(){
    console.log(arguments);
    var list1 = Array.prototype.slice.apply(arguments);
    console.log(list1);
    var list2 = Array.from(arguments);
    console.log(list2);
    var list3 = [...arguments];
    console.log(list3);
}
test3(1,2,3,4);

默认参数值
假设想要的效果是这样的:
var foo = function(x,y){
    x = x || 10;
    y = y || 20;
    console.log(x+y);
}
foo(1,2); // 3
foo(); // 30
但是也有出错的时候:
foo(0,1); // 11
第一个参数0被解析成了false,而不是数字0进行计算。
用默认参数值
var foo = function(x=10, y=20){
    console.log(x+y);
}
foo(0,1); // 1
解构
var obj = {a:1,b:2,c:3},a,b,c,p;
p = {a,b,c} = obj;
console.log(p === obj); // true
Blob实现下载文件
DOM:
<a id="download" @click="download">下载</a>
JS:
download(){
    var blob = new Blob(['hello world']);
    var url = window.URL.createObjectURL(blob);
    var a = document.getElementById('download');
    a.download = 'helloworld.txt';
    a.href = url;
},
Map
为什么要用Map?因为普通数据结构无法以非字符串为键。
举例:
var m = {};
var x = {id:1}, y = {id:2};
m[x] = 'foo';
m[y] = 'bar';
console.log(m,m[x],m[y]); // {[object Object]: "bar"} "bar" "bar"
对象m中只有一个[object Object],值都是'bar',它无法解析两个对象为键。
使用Map以非字符串为键
var m = new Map();
var x = {id:1}, y = {id:2};
m.set(x , 'foo');
m.set(y , 'bar');
console.log(m);
console.log(m.get(x));
console.log(m.get(y));
console.log(m.get({id:1}));
结果:

delete删除
m.delete(y);
clean清除所有
m.clear();
m.size; // 0
size大小
m.size;
new Map深拷贝
var m2 = m1; // 浅拷贝
var m3 = new Map(m1); // 深拷贝
深拷贝实例:
var mm = new Map();
mm.set('a',{id:1});
var mm2 = new Map(mm);
mm2.set('a', {id:4});
console.log(mm2,mm);
结果:

两个value值都是对象,互不影响。
Map所有的值
方法1:m.values()
方法2:m.entries()
方法1:m.values()
返回一个迭代器,可以用spread扩展运算符(...)或Array.from()转换成数组。
var m = new Map();
var x = {id:1}, y = {id:2};
m.set(x , 'foo');
m.set(y , 'bar');
console.log(m.values()); // MapIterator {"foo", "bar"}
console.log([...m.values()]); // ["foo", "bar"]
console.log(Array.from(m.values())); // ["foo", "bar"]
方法2:m.entries()
var m = new Map();
var x = {id:1}, y = {id:2};
m.set(x , 'foo');
m.set(y , 'bar');
console.log(m.entries()); // MapIterator {{…} => "foo", {…} => "bar"}
console.log([...m.entries()]); // [[{id: 1},'foo'],[{id: 2},'bar']]
console.log([...m.entries()][0][1]); // "foo"
console.log([...m.entries()][1][1]); // "bar"
Map所有的键
keys
var m = new Map();
var x = {id:1}, y = {id:2};
m.set(x , 'foo');
m.set(y , 'bar');
console.log([...m.keys()]); // [{id:1},{id:2}]
has判断是否有该键
var m = new Map();
var x = {id:1}, y = {id:2};
m.set(x , 'foo');
m.set(y , 'bar');
console.log(m.has(y)); // true
WeakMap
区别:
- 
内部内存(特别是GC)的工作方式;
 - 
WeakMap只接受对象为键;所以对象被回收项目也会移除
 
var m = new WeakMap();
var x = {id:1}, y = {id:2};
m.set(x ,y);
console.log(m.has(x)); // true
x = null;
console.log(m.has(x)); // false
Set
Set是一个值的集合,其中的值是唯一的。
API:
新建:new Set()
增:add()
删:delete()
查:has
新建
var s = new Set([0,-0,1,2,NaN,2,3,NaN]);
console.log(s); // Set(5) {0, 1, 2, NaN, 3}
0和-0被认为是同一个值,NaN与NaN也是相等的。
添加(add)
s.add(7);
console.log(s); // Set(6) {0, 1, 2, NaN, 3, 7}
删除(delete和clear)
s.delete(2);
console.log(s); // Set(5) {0, 1, NaN, 3, 7}
s.clear();
console.log(s.size); // 0
查询是否存在(has)
不像Map里面的get能直接取值,这里是查询是否存在该值。
s.has(1); // true
迭代
同Map
s.keys(); // SetIterator {0, 1, NaN, 3, 7}
s.values(); // SetIterator {0, 1, NaN, 3, 7}
s.entries(); // SetIterator {0 => 0, 1 => 1, NaN => NaN, 3 => 3, 7 => 7}
虽然keys()和values()返回的值一样,但它们俩并不相等。
s.keys() == s.values(); // false
WeakSet
和Set的区别:
只能存对象
var ws = new WeakSet([1,2,2,3]); // 无效:Uncaught TypeError: Invalid value used in weak set
WeakSet使用:
var obj1 = {id:1};
var obj2 = {id:2};
var ws = new WeakSet();
ws.add(obj1).add(obj2).add(obj1);
console.log(ws); // [{id:1},{id:2}]
添加了obj1两次,还是去重了。
GC
obj1 = null;
console.log(ws); // [{id:1},{id:2}]
ws.has(obj1); // false
虽然obj1的值看上去还在,但已经取不到了。
delete删除
ws.delete(obj2);
console.log(ws); // [{id:1}]
Array、Map、WeakMap、Set、WeakSet的对比
对比表
| 功能属性 | Array | Map | WeakMap | Set | WeakSet | 
|---|---|---|---|---|---|
| 新建 | [] | 
new Map() | 
new WeakMap() | 
new Set() | 
new WeakSet() | 
| 增 | push | 
m.set(obj,'value') | 
wm.set(obj1,'value') | 
s.add(value) | 
ws.add(obj) | 
| 新建并增加 | [1,2] | 
- | - | new Set([4, 0, 0, 4, 1]) | 
- | 
| 键 | 对象或其它 | 对象或其它 | 只接受对象 | 对象或其它 | 只接受对象 | 
| 删 | slice或splice | 
delete | 
delete | 
delete | 
delete | 
| 清除 | arr = [] | 
clear | 
clear | 
clear | 
clear | 
| 改 | splice | 
- | - | - | - | 
| 查 | includes、indexOf等 | 
get或has | 
get或has | 
has | 
has | 
| 键 | m.keys()下标 | 
m.keys() | 
- | m.keys() | 
- | 
| 值 | m.values()值 | 
m.values() | 
- | m.values() | 
- | 
| 迭代 | entries | 
entries | 
- | entries | 
- | 
| 长度 | length | 
size | 
- | size | 
- | 
Map API:
- size数量
 - set()设置
 - clear()清除
 - delete()删除
 - has()存在
 - get()获取
 - keys()键
 - values()值
 - entries()迭代
 
WeakMap API:
- set()设置
 - delete()删除
 - has()存在
 - get()获取
 - clear()清除(已弃用,但可通过new WeakMap()空对象来置空)
 
Set API:
- size数量
 - add()添加
 - clear()清除
 - delete()删除
 - has()存在
 - keys()键
 - values()值
 - entries()迭代
 
WeakSet API:
- add()添加
 - delete()删除
 - has()存在
 
proxy
浅拷贝:什么也不写
var obj = {
  id:1
};
var p = new Proxy(obj,{});
p.a = 33;
console.log(obj); // {id:1,a:33}
JS运行机制
异步:现在和将来的时间间隙
并行:能够同时发生的事情
并行:比如进程与线程,独立运行并且能同时运行。
fun : function(){
	func1();
	func2();
	http1();
	http2();
}
多进程
每个tab标签页有一个独立的进程(有的可能会合并)
比如:
- Browser进程:主进程;
 - 第三方插件进程;
 - GPU进程;
 - 浏览器渲染进程。
 
单线程
浏览器的渲染进程是多个线程的,是多个,这些线程还是一个一个执行完了才执行下一个,所以JS引擎是单线程的。
比如:
- GUI渲染线程
 - JS引擎线程
 - 事件触发线程
 - 定时触发器线程
 
浏览器渲染流程
沉浸树(render树):
- 1.处理HTML标签构建DOM树;
 - 2.处理CSS标签构建CSSOM树;
 - 3.DOM和CSSOM树被组合形成渲染树(render树);
 - 4.布局render树,计算尺寸、位置;
 - 5.绘制render树,绘制页面像素信息;
 - 6.发给图形处理器(GPU),显示在屏幕上。
 
CSS是否会阻塞dom树渲染?
由上面的流程可知,不知阻塞DOM树,但会阻塞CSSOM树。
事件循环(Event Loop)、宏任务(macrotask)、微任务(microtask)
事件循环(Event Loop):执行完宏任务后,将微任务排队添加任务,执行后再循环检查有没有宏任务……所以整个过程称为事件循环。
宏任务(macrotask):主代码、setTimeout、setInterval
微任务(microtask):promise、process.nextTick
执行顺序:先宏任务--》执行结束后--》再执行所有微任务--》渲染--》下一个宏任务
console.log('start');
setTimeout(function() {
  console.log('1');
}, 10);
new Promise(resolve => {
    console.log('2');
    resolve();
    setTimeout(() => console.log('3'), 10);
}).then(function() {
    console.log('4')
})
console.log('end');
这里的执行顺序就是start-->2-->end-->4-->1-->3
注意:
promise是立即执行的,创建的时候就会执行,不存在将promise推入微任务;
resolve()是表示promise的状态为fullfilled,相当于只是定义了一个有状态的promise,并没有调用它;
promise调用then的前提是promise的状态为fullfilled;
只有promise调用then的时候,then里面的函数才会被推入微任务中。
setTimeout相关
setTimeout并不是由JS引擎计数的,因为单线程会阻塞,会影响计数的准确,因此通过单独线程来计时并触发。
setTiemout最小为4,不满会加成4。
try...catch无法用于异步代码
同步代码
try {
    foo();
} catch (error) {
    console.log('异常是:'+error)
}
此时会由catch捕捉到异常:
异常是:ReferenceError: foo is not defined
异步代码
function foo(){
    setTimeout(()=>{
        bar.arr();
    },100);
};
try {
    foo();
} catch (error) {
    console.log(error)
}
此时无法捕捉,而是浏览器控制台报出未捕捉异常。
Uncaught ReferenceError: bar is not defined
对比图

Promise
promise代替callback回调。
promise.all
只能同时调用不受关联的prmise,如果promise2的值受promise1影响,不能用promise.all,可以用async/await
首先假设要依次调用3个promise的代码:
var pro1 = new Promise((resolve,reject) => {
    console.log(1);
    resolve('hello')
})
var pro2 = new Promise((resolve,reject) => {
    console.log(2);
    setTimeout(()=>{
        resolve('world')
    },1000);
})
var pro3 = new Promise((resolve,reject) => {
    console.log(3);
    setTimeout(()=>{
        resolve('pdd')
    },2000);
})
如果不用promise.all来调用的话:
pro1.then((res1)=>{
});
pro2.then((res2)=>{
})
pro3.then((res3)=>{
})
只有不停的用.then才能保证每一步都正确,此时使用promise.all:
Promise.all([pro1,pro2,pro3]).then(val=>{
    console.log(val);
})
promise.race
第一个抛出resolve的promise就是Promise.race获取的值。
这种模式称为门闩模式、promise中称中竞态。
var pro2 = new Promise((resolve,reject) => {
    console.log(2);
    setTimeout(()=>{
        resolve('world')
    },1000);
})
var pro3 = new Promise((resolve,reject) => {
    console.log(3);
    setTimeout(()=>{
        resolve('pdd')
    },2000);
})
Promise.race([pro2,pro3]).then(val=>{
    console.log(val);
})
此时,pro2要花费1秒,pro3要花费2秒,谁先resolve,.then获取的val就是谁的。
async/await
普通函数和async的区别
普通函数:
function testAsync(){
    return 'hello world'
}
testAsync(); // 'hello world'
async函数:
async function testAsync(){
    return 'hello world'
}
testAsync(); // Promise {<fulfilled>: "hello world"}
async返回的是一个promise对象
await
如果不用async/await:
async function testAsync(){
    return new Promise(resolve => {
        setTimeout(()=>resolve('long_time_value'), 1000);
    })
}
testAsync().then(v=>{
    console.log('get',v);
})
1秒后:get long_time_value
如果用的话:
function testAsync(){
    return new Promise(resolve => {
        setTimeout(()=>resolve('long_time_value'), 1000);
    })
}
async function test(){
    const v = await testAsync();
    console.log(v);
}
test();
1秒后:get long_time_value
优势:处理then链
function takeLongTime(n){
    return new Promise(resolve => {
        setTimeout(()=> resolve(n+200), n);
    })
}
function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}
function step2(n) {
    console.log(`step2 with ${n}`);
    return takeLongTime(n);
}
function step3(n) {
    console.log(`step3 with ${n}`);
    return takeLongTime(n);
}
function doIt(){
    console.time("doIt");
    const time1 = 3000;
    step1(time1)
        .then(time2 => step2(time2))
        .then(time3 => step3(time3))
        .then(result => {
            console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}
doIt();
step1 with 3000
VM5329:13 step2 with 3200
VM5329:18 step3 with 3400
VM5329:29 result is 3600
VM5329:30 doIt: 9606.429931640625ms
每一个promise都受上一个promise影响,所以必须一个调完之后再调另外一个。
再看看用async/await更改doIt方法:
async function doIt(){
    console.time("doIt");
    const time1 = 3000;
    const time2 = await step1(time1);
    const time3 = await step2(time2);
    const result = await step3(time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}
doIt();
结果和上一个不停用then链的一样,但是代码要清晰得多,而且没有回调地狱。
export 和 import 和 require
普通使用
constant.js
var constant = {
    edit:"编辑",
    test:'2'
}
var b = {};
export {
    constant,
    b
};
test.vue
import {constant,b} from '@/utils/test';
console.log(constant,b)
全局使用
constant.js
export default {
    list1:[],
    list2:[],
    b:function(){}
}
main.js
improt constant from './utils/test';
Vue.prototype.$constant = constant;
test.vue
this.list = this.$constant.list1;
export和export default的区别
export需要导出多个并需要{},export default只需要一个{}导出全部(没有额外{});import时,export需要导入多个,export default是默认的,只需要给一个名字;
require
require是AMD规范;import是ES6规范。
require是赋值,import是解构。
defaultImg2: require("../../../assets/img/default.png"),
flat()和flatMap()
flat
拉平数组,默认一层,填几就拉平几层嵌套,如果想拉平所有的,用Infinity
[1, 2, [3, [4, 5]]].flat()
// [1, 2, 3, [4, 5]]
[1, 2, [3, [4, 5]]].flat(2)
// [1, 2, 3, 4, 5]
[1, [2, [3]]].flat(Infinity)
// [1, 2, 3]
flatMap
与map类似,不同的是可以拉平数组,但只能拉平一层,不能多层。
[1,[2,[3],4,5],6,[7],8].flatMap(v => v*2)
(5) [2, NaN, 12, 14, 16]
                    
                
                
            
        
浙公网安备 33010602011771号