es6新特性 箭头函数 解构赋值 letconst promise 类和继承 模板字符串 扩展运算符 for of

72 阅读14分钟

image.png

let const

var 声明变量 全局作用域 let 声明变量 可以重新赋值 const 声明常量

深拷贝和浅拷贝的区别

当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,拿人手短,如果B没变,那就是深拷贝,自食其力。

1深拷贝是指针 是值

浅拷贝是地址 比如b=a

当b=a进行拷贝时,其实复制的是a的引用地址,而并非堆里面的值。

  1. 如果是基本数据类型,名字和值都会储存在栈内存中
var a = 1;
b = a; // 栈内存会开辟一个新的内存空间,此时b和a都是相互独立的
b = 2;
console.log(a); // 1

当然,这也算不上深拷贝,因为深拷贝本身只针对较为复杂的object类型数据。

  1. 如果是引用数据类型,名字存在栈内存中,值存在堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值

2 常用实现:

1 扩展运算符

2 JSON.stringify(obj)

3 lodash _.cloneDeep(test)

4 slice

5 手动 obj.a=xxx

js执行机制

js单线程,同一时间只能做一件事,一切的多线程都是通过单线程模拟出来的 事件循环机制:event loop 同步:进入主线程 异步:进入事件表并注册函数 当指定的事情完成之后,事件表会将这个函数移入到任务队列,主线程内任务执行完毕之后,回任务队列读取对应的函数,移入到主线程执行,不断重复

JavaScript是单线程执行的,无法同时执行多段代码。当某一段代码正在执行的时候,所有后续的任务都必须等待,形成一个队列。一旦当前任务执行完毕,再从队列中取出下一个任务,这也常被称为 “阻塞式执行”。所以一次鼠标点击,或是计时器到达时间点,或是Ajax请求完成触发了回调函数,这些事件处理程序或回调函数都不会立即运行,而是立即排队,一旦线程有空闲就执行。假如当前 JavaScript线程正在执行一段很耗时的代码,此时发生了一次鼠标点击,那么事件处理程序就被阻塞,用户也无法立即看到反馈,事件处理程序会被放入任务队列,直到前面的代码结束以后才会开始执行。如果代码中设定了一个 setTimeout,那么浏览器便会在合适的时间,将代码插入任务队列,如果这个时间设为 0,就代表立即插入队列,但不是立即执行,仍然要等待前面代码执行完毕。所以 setTimeout 并不能保证执行的时间,是否及时执行取决于 JavaScript 线程是拥挤还是空闲。

也就是说setTimeout只能保证在指定的时间过后将任务(需要执行的函数)插入队列等候,并不保证这个任务在什么时候执行。执行javascript的线程会在空闲的时候,自行从队列中取出任务然后执行它。javascript通过这种队列机制,给我们制造一个异步执行的假象。

当我们执行以下代码时,结果会按1、3、2的顺序弹出。

setTimeout(function() {
    alert(2);
},0);
alert(3);

这是事件循环机制,因为js是单线程的,是基于事件循环的。而setTimeout函数是异步的,异步的事件会加入一个队列,会等到当前同步的任务执行完毕后,再执行setTimeout队列的任务。所以,通过设置任务在延迟0毫秒后执行,就能改变任务执行的先后顺序,延迟该任务发生,改变它所调用的函数的优先级,使之异步执行。

  1. 下面这段代码,是不会实时获取到输入框里面的内容的。

<input type="text" onkeydown="test(this.value)">  
<div></div>  
<script">  
  function test(val) {  
    document.getElementsByTagName('div')[0].innerHTML = val;  
  }  

可以发现,每按下一个字符时,< div > 中只能显示出之前的内容,无法得到当前的字符。结果如下:

  1. 利用 setTimeout延时为0,我们把取 value 的操作放入队列中,放在更新 value 值以后.
function test(val) { document.getElementsByTagName('div')[0].innerHTML = val; } 1 2 3 4 5 6 能够实时显示输入的内容

虚拟dom和真实dom区别

ts的interface和type区别 如何在里面写方法

type

type (类型别名),顾名思义,类型别名只是给类型起一个新名字。它并不是一个类型,只是一个别名而已

  1. 可以定义基本类型别名,如type StringType = string

  2. 可以声明联合类型,如 type paramType = number | string;

  3. 可以声明元组类型,如type arrType = [string, string, number];

元组 Tuple,元组类型允许标示一个已知元素数量和类型的数组,各元素的类型不必相同。

interface

interface(接口) 是 TS 设计出来用于定义对象类型的,可以对对象的形状进行描述。

都可以定义一个对象或函数

我们来看一下如何定义函数。

type addType = (num1:number,num2:number) => number

interface addType {
    (num1:number,num2:number):number
}

不同点

1. 声明类型

type作为类型别名,可以给任何类型起别名

interface基本只用来给对象定义类型接口

2. 重复声明

interface可以重复声明,会合并接口类型

type会报错

type 一旦定义就不能再添加新的属性,而 interface 总是可扩展的

image.png

如果是type,重复声明会报错 image.png

3. 类

interface可以使用implements关键字来约束类

一、新增数据类型Symbol。

      概念:

            Symbol代表独一无二的

    Symbol类型的值通过Symbol函数来生成,同时Symbol函数生成的值是唯一的

    Symbol函数何以接收字符串作为参数,但是即使相同参数返回的值也是唯一的

   作用:

    属性私有化

    数据保护

       

//没有参数的情况
let s1 = Symbol();
let s2 = Symbol();

s1 === s2      //false


//有参数的情况
let s1 = Symbol("fun");
let s2 = Symbol("fun");

s1 === s2      //false


let mySymbol = Symbol();
//第一种写法
let a = {};
a[mySymbol] = "Hello!";
//第二种写法
let a = {
     [mySymbol]: "Hello!"  
}
//第三种写法
let a = {};
Object.defineProperty(a,mySymbol,{value: "Hello!"});
//枚举Symbol的key值
Object. getOwnPropertySymbols(obj);
//注意,Symbol作为对象的key值不能被for in进行遍历。

 

二、块级作用域。

  概念:在ES6中,凡事{}包裹的代码都是跨级作用域,凡事在块级作用域中用let、const声明的变量都有一个暂时性死区。

{
    let a = 20;  
}
console.log(a);   //报错

 

三、var、let、const声明变量。

  var

    支持变量声明与解析

    不支持块级作用域

    可以重复声明。

  let

    不支持变量声明与解析

    支持块级作用域

    不可以重复声明

    用let声明的变量或者方法只能在代码块中生效。

{
      let a = 10;
      var b = 20;
}    
console.log(a);     //报错
console.log(b);     //20

  const

    不支持变量声明与解析

    支持块级作用域

    不可以重复声明

    声明变量,一旦声明不可修改

    声明变量必须赋值,不可以像var一样先声明后再定义。  

 

四、解构赋值。

  概念:允许按照一定格式,从对象和数组中提取值。

//数组解构
let [a,b,c] = [1,2,3];
console.log(a,b,c);     //1,2,3

//对象解构,对象解构时key值必须要一一对应。
let {name,age} = {name: "张三",age: 20};
console.log(name,age);    //张三   20

//对象解构+别名
let {name: _name, age: _age} = {name: "张三"age: 20};
console.log(_name,_age);      //张三   20

//多重解构
let {obj:{name},arr:[a,b]} = {obj:{name: "张三"},arr: [10,20]};

 

五、扩展运算符

  概念:将数组或对象转换成参数序列,使用逗号分割的序列。

  作用:

    1、数组、对象的合并

    2、函数剩余参数

    3、替代arguments

//数组合并
let arr1 = [10,20,30];
let arr2 = [40,50,60];
let newArr = [...arr1,...arr2];
console.log(newArr);       //[10,20,30,40,50,60];

//展开数组
console.log(Math.Max(...arr));

//对象合并
let obj1 = {width: 100, height: 100};
let obj2 = {left: 100, top: 100};
let newObj = {...obj1,...obj2};
console.log(newObj); //{width:100,height:100,left:100,top:100};

 

六、字符串模版。

  1、字符串太长需要换行怎么办?

//常规解决办法
var a = "<div>" + 
            "<span>"+num+"</span>"
            "</div>";

//ES6语法
let a = `<div>
             <span>${num}</span>
            </div>`

复制代码

  2、字符串太长怎么办?

let phone = 15844423232
let intro = `my name is chj, my phone id ${phone}`;

  3、includes字符串搜索。

//ES6神器,includes方法,str.insludes(内容),找到了返回true,没找到返回false

let str = "good method!";
str.includes("method");   //true   

  4、判断首尾,startsWith  endsWith

/*
    startsWith判断是否位于头部
    endsWith判断是否位于尾部
    这两个方法是includes的扩展
*/

let str = "how are you?";
str.startsWith("how");     //true
str.endsWith("?")            //true

  5、repeat字符串重复

//str.repeat(n);     将字符串重复n次
let str = "abc";
str.repeat(3);       //abcabcabc

 

七、对象新增方法。

  1、对象的简写

let a = 10;
let obj = {a};
//等价于
let obj = {a: 10};
//当key值和value值一样的时候我们可以写一个。

  2、Object.is

//判断两个对象是否指向同一个内存地址
let obj1 = {a: 1, b: 2};
let obj2 = obj1;
Object.is(obj1, obj2);   //true

  3、Object.assign

//合并对象
let obj1 = {name: '曹海杰', age: 20};
let obj2 = {sex: '男'};
let newObj = Object.assign(obj1, obj2);   
console.log(newObj);     //{name: '曹海杰', age: 20, sex: '男'};

 

八、数组中新增的方法。

  1、Array.of()

//将一组值转换为数组
let arr = Array.of(1,2,3,4);
console.log(arr);     //[1,2,3,4];

  2、Array.from()

//将伪数组转换为数组。
let aLi = Array.from(document.getElementsByTagName("li"));
console.log(aLi instanceof Array);    //instanceof判断某对象是否属于某类的实例。

  3、Array.find()

//通过条件查找数据,返回第一个符合条件的数据。
let arr = [1,2,3,4];
let n = arr.find(function(item, index, array) {
    return item > 3;
})
console.log(n);   //4

  4、Array.findIndex()

//查找数组中符合条件的数据的下标,如果没有找到则返回undefined
let arr = [1,2,3,4];
let n = arr.findIndex(function(item, index , array) {
     return item > 3; 
})    
console.log(n);

  5、Array.fill();

//对数组进行填充,语法:arr.fill('内容',开始下标,结束下标);
let arr = [1,2,3,4];
arr.fill('qwe', 1, 3);
console.log(arr);       //[1, 'qwe', 'qwe', 4];

 

九、for of   and    for in

/*  
    1、for of 是ES6的,for in 是ES5的
    2、for of 遍历的是值,for in遍历的是键
    3、for of 不能遍历对象,for in 既可以遍历对象也可以遍历数组
    4、for of 遍历数组的时候,如果有未定义的项,遍历出来的是undefined,for in 则遍历不到
    5、for of 不能遍历到原型上定义的属性(自定义属性),for in 可以遍历到
    6、for of 的兼容性不是很好,移动端安卓微信浏览器不支持,Safari支持
*/
Array.prototype.hehe = '呵呵';
let arr = [1,2,3, ,4];
for (let item of arr) {
     console.log(item);      //1,2,3,undefined,4
}

for (let prop in arr) {
     console.log(prop);      //0,1,2,4,hehe  
}

let obj = {
     name: 'chj',
     age: 20
}    

for (let item of obj) {      //报错
     console.log(item);      
}

for (let prop in obj) {
     console.log(prop);      //name age
}    

 

十、函数

  1、函数参数默认值

//ES6之前函数怎么设置默认值
function fn(x) {
     let x= x || 10;  
}

//ES6函数默认值,等价于上面的写法,如果没有传递参数,就使用默认值10

function fn(x=10) {

}

  2、剩余参数

//fn函数中a接收实参1,...rest接收剩余参数为一个数组。
funciton fn(a, ...rest) {
    console.log(...rest);       //[2,3,4,5];
}
fn(1,2,3,4,5);

  3、箭头函数

//语法一  function 换成 () => 
let fn = (a) => {
     console.log(a)
}        

//语法二   不写{}默认表示return,当前函数意思是返回a这个值
let fn = a => a
fn(10);

//语法三   不写{}默认表示return,当前函数表示返回一个对象
let fn = a => ({a:1});

/*
    箭头函数特点:
        1、this指向离自己最近的外层作用域的对象
        2、不能当作构造函数使用(箭头函数是匿名函数,没有函数名字所以没有办法new)
        3、没有arguments这个参数(ES6已经取消arguments这个参数了)
        4、不能当作generator函数
*/

 

十一、Set集合

/*
    Set: 集合
        1、类似于数组,但成员的值是唯一的,没有重复的值,并且是无序的
        2、Set是一个构造函数
        3、Set每次执行完毕后都会返回一个Set,因此可以进行链式调用
*/
let s = new Set();
//添加  add()
s.add("a").add("b");
console.log(s);    //Set(2)  {"a","b"};

//删除  返回值是一个布尔值
s.delete("a") ;   //true

//判断元素是不是Set的成员,返回值是一个布尔值
s.has("a");   //true

//清除所有    没有返回值
s.clear();

//返回所有键名
s.keys();

//返回所有value值
s.values();

//返回所有键值对
s.entries();

//可以通过for of 遍历每一个值
for(let item of s) {
    console.log(s);
}    

 

十二、Map字典类型结构

/*
    1、字典:用来存储不重复key的hash结构,不同于Set集合,字典使用[键,值]的形式来存储
    2、Map执行完毕后都会返回一个Map,可以进行链式调用
    3、特点:普通对象只能通过字符串来当作key值,但是Map可以使用任何值来当作key值
*/
//创建Map对象
let map = new Map({
     ["a", 1],
     ["b", 2]
})   
console.log(map);     //Map(2)   {"a" => 1, "b" => 2}

//获取map长度
map.size

//添加数组
map.set("c",3)

//获取map值
map.get("a")

//删除数据 删除成功返回true
map.delete("a")

//检测map中是否含有某个值  返回布尔值
map.has("a")

//清除所有数据
map.clear();

//获取所有key值
map.keys();

//获取所有value值
map.values();

//获取所有键值对
map.entries();

//遍历map对象     index在前item在后(数组中item在前)
map.forEach((index, item) => {
      console.log(index, item); 
})

 

十三、Proxy介绍

  概念:Proixy是ES6中新增的一个特性。

  作用:在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须想通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写,很类似于设计模式中的代理模式。

  基本用法:

let p = new Proxy(target, handler);

  参数:

    target: 用Proxy包装被代理的对象(可以是任意类型的对象,包括原生数组、函数、甚至是另一个代理)

    handler:是一个对象,其声明了代理target的一些操作,其属性是当执行一个操作时定义代理的行为的函数

  特点:

    1、可以劫持整个对象,并返回一个新对象

    2、有13中劫持操作

    3、handler代理的一些常用方法:

      get    读取

      set     修改

      has     判断对象是否有该属性

      construct     构造函数

      apply           当目标对象是一个函数的时候

      deletePrototy      用于拦截delete操作

 

十四、get/set方法

let target = {
     name: "chj",
     age: 20,
     sex: "男"
}    
let p  = new Proxy(target, {
       get(obj, attr) {
            console.log("属性被访问了");
       },
       set(obj, attr, value) {
            console.log("属性被设置了");
       }
})
p.name;
p.name = "小王";

  get函数:当访问target对象身上的一些属性的时候就会触发get函数,get函数接收两个参数

    参数一:代理的对象,也就是target。

    参数二:访问的属性。

  set函数:当设置target对象身上的一些属性的时候就会触发set函数,set参数接收三个参数

    参数一:代理的对象

    参数二:设置对象的属性

    参数三:设置对象的属性的值

  使用场景:

    1、虚拟场景

let target = {
     firstName: "chj",
     lastName: "ls"
}

let p = new Proxy(target, {
        get(obj, attr) {
               if(attr == "fullName") {
                      retrun [obj.firstName, obj.lastName].join(" ");
               }
               return obj[attr]; 
        },
        set(obj, attr, value) {
               if(attr == "fullName") {
                       let fullNameInfo = value.split(" ");
                       obj.firstName = fullNameInfo[0];
                       obj.lastName = fullNameInfo[1];
               }else {
                       obj[attr] = value;
               }
        }
})

console.log(fullName);   //chj
p.fullName = "小 甜甜";
console.log(p.firstName);     //小
console.log(p.lastName);     //甜甜

  2、私有属性

//把_开头的变量都认为私有变量
let target = {
     name: "张三",
     age: 20,
     _sex: "女"
}    

let p = new Proxy(target, {
       get(obj, attr) {
              if(attr.startWith("_")) {
                    console.log("私有属性不被允许访问");
                    return false; 
              }
              return obj[attr];
       },
       set(obj, attr, value) {
              if(attr.startWith("_")) {
                    console.log("私有属性不允许设置");
                    retrun false;
              }
              obj[attr] = value;
       },
       has(obj, attr) {
              if(atrr.startWith("_")) {
                    return false;
              }
              retrun (attr in obj);
       }
})

 

十五、函数拦截

  apply:当目标对象是一个函数,且他被调用时,就是被apply方法拦截

    参数:apply(target, context, arguments) {}

      target:目标对象

      context:目标对象的上下文(this)

      arguments:目标对象的参数数组

  construct:用于拦截new命令,意思就是你在new目标对象的时候,会走construct() {}

    参数:construct(target, arguments) {}

      target:目标对象

      arguments:构造函数的参数对象

function fn(a, b) {
       lat handler = {
             apply: function(target, context, args) {
                    console.log(target, context, args)
                    return args[0];
             },

             construct: function(target, args) {
                    return {value: args[1]};
             }
       }
};

let p = new Proxy(fn, handler);

console.log(p(1, 2))           //1
console.log(new p(1, 2))    //{value: 2}