再学es6

关键词:重要 知识点

看的是慕课网谢成老师的es6课程

第一章 课程介绍&环境搭建

1.1babel介绍&npm源切换

我们的es5可以很好的在浏览器运行,但是其的es6等可能会不兼容,所以我们要用babel把代码转换

在这里插入图片描述

一般情况下babel工具会配置在webpack构建工具里

这样就可以很方便的把新的语法打包编译成es5的语法 从而被浏览器识别

npm:node package manager

但是npm安装包太慢了 这时候可以安装nrm

NRM : npm registry manager 可以快速的在npm的源之间切换 可以切换到最快的源进行安装
安装:
Windows : npm install -g nrm
Mac : sudo npm install -g nrm

在这里插入图片描述

这样我们就可以看到可用的源 那我们怎么知道哪个源比较快呢
nrm test npm
nrm test taobao

那测试出来哪个快 怎么使用呢 可以用nrm use xxx

这里也给出了添加定制源的方法
在这里插入图片描述

1.2构建开发环境

我们该怎么找包呢?去网站npmjs.com

我们可以搜到老师的包

清屏的命令:cls

怎么判断下没下好老师的包呢

在这里插入图片描述

在这里插入图片描述

补充:查看目录下的文件是dir

初始化项目:

在这里插入图片描述

然后就是

npm install(安装项目依赖的包)

npm run start

在这里插入图片描述

我刚刚装包的时候不应该用–save的 应该用-g 这样就可以再任何地方初始化这个项目了(应该吧)

现在就可以用vscode打开了

在这里插入图片描述

在这里插入图片描述

src这里的代码会被webpack打包编译

f12 进到source 老师说这样是配置成功的

在这里插入图片描述

但是我的好像是没有成功诶还是原来的样子

在这里插入图片描述

接下来是static
static是不会被webpack打包编译的 会被原封不动的复制到生产环境中

经过webpack打包编译的代码里面js的作用域和正常情况下看到的作用域会有一些差别

接下来是gitignore这里的东西是不会被上传到github上的

复习

在这里插入图片描述

在这里插入图片描述

第二章 ECMAScript2015(ES6)之必备知识

2.1,2.2新的命名方式let

es6(2015)里的let:

在这里插入图片描述

然后我们就可以开始写代码来回答上面的知识点了 --写在static下面,不会被webpack打包编译

我们先可以在2-1.js里面随便写一点东西(比如console.log(12)之类的) 然后在index.html里面引入 引入成功的话就可以写代码了

在这里插入图片描述

正式开始

a.不属于顶层对象window

var 是什么呢 的英文单词是variable 可变的

var a=5
console.log(a);
b=6
console.log(b);

这样a和b都能输出,那么一个有var 一个没有,它们有什么区别吗

var a=5 其中a是一个变量 变量有全局和局部之分 这里就是个全局变量
b=6 这个是对属性进行赋值 它是在window下面定义一个属性叫 做b
怎么看出来的呢 有一个delete 它的作用是用来删除对象的属性的并不能用来删除变量
这样我们就可以实验一下

在这里插入图片描述

发现b被删除了(红色报错的意思是b未定义) 这就证明了b是一个对象的属性 而a没有被删除所以不是全局对象window下面的属性

但是问题又来了,我们说a不是window下面的属性,变量就应该是变量,不应该在window下面,我们可以打印一下window.a 发现可以打印出6 这是怎么回事呢

这是作者设计初期的一个问题
设计这门语言的时候把顶层的属性和全局变量进行了挂钩 正常不应该是这样的

比如说我们做项目的时候很复杂 里面变量很多 如果都挂在window下面的话,那么window就会变得越来越大,这个就叫污染全局变量

let可以很好的解决污染全局变量的问题

let c=5
console.log(window.c); //undefined

再来解释一下为什么把代码放到static里面呢

终端输入npm run build 就会多出一个dist文件夹 这就是打包上线的文件夹
在dist文件夹的static文件夹里面有我们写的代码 是原封不动的 可以原汁原味的在浏览器运行,而不是经过webpack打包改动过的(写在src的index.html里面会被改动),webpack会规避很多问题

b.不允许重复声明

var a=0
var a=6
console.log(a);  //正常输出6

换成let的话Identifier 'a' has already been declared

这是很好的,为什么呢,公司很多人开发一个项目,如果一个程序员已经定义了一个变量a,另外一个程序员不知道它定义了a,也定义了一个同名的变量,如果报错了的话就知道了前面已经有人定义过了

c.不存在变量提升

console.log(a);   //undefined
var a=0

如果是let的话就直接报错了

d.暂时性死区

var a
if(true){
    a=5
    var a
}
//这样写没有报错
var a
if(true){
    a=5
    let a
}
//这样写报错

为什么呢,这样会形成一个暂时性死区

那个花括号就形成了作用域,在封闭的作用域内,在声明之前使用变量就会报错

还有些比较隐蔽的暂时性死区

function foo(a=b,b=2) {
    console.log(a,b);
}
foo()

e.块级作用域

for(var i=0;i<2;i++){
    console.log("循环内"+i);
}
console.log("循环外"+i);

循环内0
循环内1
循环外2

如果改成let就变成了i is not defined

if(false){
    var a=5
}
console.log(a);  //undifined

有的人可能会省略括号,这时候let就不能用了,因为没有括号就是没有块级作用域

for(var i=0;i<3;i++){
    setTimeout(function(){
        console.log(i);   //333
    })
}

settimeout是个异步操作,会等到主线程空闲并且时间也到了才执行,
for循环是个同步操作 当执行for循环的时候,定时器里面的内容并没有执行,当log的时候,for循环已经执行完了,这时i的值是循环以后i的值

for循环3次就会有3个settimeout异步操作,每个settimeout执行都是在for循环执行完成以后才去取值

但是现在有这样的需求,不希望是333 希望它的值是012应该怎么做呢

有基础的可能会想到闭包

for(var i=0;i<3;i++){
    (function(j){       //形参
        setTimeout(function(){
            console.log(j);
        })
    })(i)     //实参
}

有一个外部函数还有一个内部函数 内部函数会调用外部函数的变量,这样就保证外部函数的变量不被释放

还有其它的方法

直接改成let就ok了,为什么呢

这是经过babel转换过的

在这里插入图片描述

2.3新的声明方式const

es5里面有个api Object.defineProperty() 它会直接在对象上定义新的属性

Object.defineProperty(window,'PI',{
    value:3.14,
    writable:false
})
console.log(PI);

现在就不用这样写了 直接写const就行了

与let相同,只在块级作用域起作用

而且也不存在变量提升 有暂时性死区

const obj={
    name:"谢成",
    age:28
}
console.table(obj);
obj.school='imooc'
console.table(obj);

这个也是可以的 为什么呢

我们要搞懂这些东西是怎么存储的

我们的基本数据类型是存在栈内存里的(es5基本数据类型 string number boolean null undefined )

在这里插入图片描述

这个const声明的常量不改动是什么意思呢 指的是变量指向的内存地址不能改动

那怎么让对象也像前面基本数据类型一样不变呢 – Object.freeze(obj) 冻结 但只是浅层的 如果对象嵌套对象 那么里面的那个对象

const obj={
    name:"谢成",
    age:28
}
Object.freeze(obj)
console.table(obj);
obj.school='imooc'
console.table(obj);   //name 和age

可以冻结 但只是浅层的只能对最外层进行冻结 如果对象嵌套对象 那么里面的那个对象是可以更改的

2.4解构赋值

现在有一个数组[4,5,6],想把数组里的每一个值取出来,a,b,c分别对应里面的值

let arr=[1,2,3]
let a=arr[0]
let b=arr[1]
let c=arr[2]
console.log(a,b,c);

是不是感觉太麻烦了 下面这个就是解构赋值了

let arr=[1,2,3]
// let a=arr[0]
// let b=arr[1]
// let c=arr[2]
let [a,b,c]=arr
console.log(a,b,c);
let arr=[1,2,[3,4]]
// let [a,b,c]=arr
// console.log(a,b,c);  //1 2 [ 3, 4 ]
let [a,b,[c,d]]=arr
console.log(a,b,c,d); //1 2 3 4
let arr=[1,2,[3,4]]
let [a,b,[c]]=arr
console.log(a,b,c); //1 2 3 
let arr=[1,2,[3,4]]
let [a,b,c,d]=arr
console.log(a,b,c,d); //1 2 [ 3, 4 ] undefined
let arr=[1,2,[3,4]]
let [a,b,c,d=5]=arr
console.log(a,b,c,d); //1 2 [ 3, 4 ] 5
let arr=[1,2,[3,4],10]
let [a,b,c,d=5]=arr
console.log(a,b,c,d); //1 2 [ 3, 4 ] 10
let user={
    name:'xiecheng',
    age:28
}
let name=user.name
let age=user.age
console.log(name,age);

可以看到上面的步骤也挺复杂的,如何将对象进行解构呢

let user={
    name:'xiecheng',
    age:28
}
let{name,age}=user
console.log(name,age);  //xiecheng 28
let user={
    name:'xiecheng',
    age:28
}
let{age,name}=user
console.log(name,age);  //xiecheng 28

age与name互换了以后为什么输出还是原来的呢 因为它是通过key的名称去对应的 而不是顺序,数组的话是通过数组的下标去对应的,对象的话就是通过键名去对应的

那我不想这样,不想用name还有age怎么办呢

let user={
    name:'xiecheng',
    age:28
}
let{age:uage,name:uname}=user
console.log(uage,uname);  // 28 xiecheng

现在是字符串

let string="2345"
let [a,b,c,d]=string
console.log(a,b,c,d);

关于简写

let {name,age=18}={
    name:"xiecheng"
}
console.log(name,age);//xiecheng 18

惰性

function foo(){
    console.log(123);
}
let [a=foo()]=[1]   //因为是惰性的,所以不会执行foo()
console.log(a);   //1
function foo(){
    console.log(123);
}
let [a=foo()]=[]   //123
function foo([a,b,c]){
    console.log(a,b,c);
}
let arr=[2,3,4]
foo(arr)

在这里插入图片描述

在这里插入图片描述

let json='{"a":"12","b":"45"}'
let {a,b}=JSON.parse(json)
console.log(a,b);

注意最外面的引号是单引号

2.5数组的遍历方式

es5:

a.for & forEach

let arr=[1,2,3,4]
console.log("for:");
for(let i=0;i<arr.length;i++){
    console.log(arr[i]);
}
console.log("forEach:");//forEach有三个参数,这里就用到了一个
arr.forEach(function(elem){
    console.log(elem);
})

它们有什么区别呢 在for循环里面break和continue是可以用的 但在forEach里面不能用

console.log("for:");
for(let i=0;i<arr.length;i++){
    if(arr[i]==3){
        break
    }
    console.log(arr[i]);  //1,2
}
console.log("forEach:");//forEach有三个参数,这里就用到了一个
arr.forEach(function(elem){
    if(arr[i]==3){   //报错
        break
    }
    console.log(elem);
})
console.log("for:");
for(let i=0;i<arr.length;i++){
    if(arr[i]==3){
        continue
    }
    console.log(arr[i]);  //1,2,4
}
console.log("forEach:");//forEach有三个参数,这里就用到了一个
arr.forEach(function(elem){ 
    if(arr[i]==3){
        continue     //报错
    }
    console.log(elem);
})

b.map

forEach只是简单的循环,但是map函数会遍历数组每一个元素,然后根据回调操作,需要什么的返回值,这个返回值会形成一个新的数组

console.log("map:");
let result=arr.map(function(value){
    return value+10
}) 
console.log(result);  //[ 11, 12, 13, 14 ]

c.filter 过滤符合条件的返回

console.log("filter");
let result=arr.filter(function(value){
    return value%2==0
})
console.log(result); //[ 2, 4 ]

d.some 返回布尔值 有没有符合条件的

console.log("some");
let result=arr.some(function(value){
    // console.log(value);
    return value==0
})
console.log(result);  //false

e.every 返回布尔值 是不是每一个都符合

console.log("every");
let result=arr.every(function(value){
    // console.log(value);
    return value==1
})
console.log(result);

f.reduce接收一个函数作为累加器

应用场景1:求和

let arr=[10,11,12,13,14]
console.log("reduce");
let result= arr.reduce(function(pre,cur,index,arr){
    console.log("pre:",pre,"cur:",cur);
    return pre+cur
},0)
console.log(result);
// reduce
// pre: 0 cur: 10
// pre: 10 cur: 11
// pre: 21 cur: 12
// pre: 33 cur: 13
// pre: 46 cur: 14
// 60

场景2:求最大值

let arr=[10,11,12,13,14]
console.log("reduce");
let result= arr.reduce(function(pre,cur,index,arr){
    return Math.max(pre,cur)
})
console.log(result);

场景3:数组去重

let arr=[10,11,12,13,14,13,15]
console.log("reduce");
let result= arr.reduce(function(pre,cur){
    pre.indexOf(cur)==-1&&pre.push(cur)
    return pre
},[])
console.log(result);      //[ 10, 11, 12, 13, 14, 15 ]

g.for in 遍历数组是有问题的

Array.prototype.foo=function(){
    console.log(foo);
}
for(index in arr){
    console.log(index);
}
//0 1 2 3 4 5 6 foo

在这里插入图片描述

es6

a.find 找到第一个符合的元素

let arr=[10,11,12,13,14,13,15]
let result=arr.find(function(value){
    return value%3==0
})
console.log(result);//12

b.findIndex 找到第一个符合的元素的下标

let result=arr.findIndex(function(value){
    return value%3==0
})
console.log(result); //2

c.for of

Array.prototype.foo=function(){
    console.log(foo);
}
for(let item of arr){
    console.log(item); 
}
//10 11 12 13 14 13 15

d.values,keys,entires

Array.prototype.foo=function(){
    console.log(foo);
}
for(let item of arr.keys()){
    console.log(item);
}
//0 1 2 3 4 5 6
Array.prototype.foo=function(){
    console.log(foo);
}
for(let [index,item]of arr.entries()){
    console.log(index,item);
}
// 0 10
// 1 11
// 2 12
// 3 13
// 4 14
// 5 13
// 6 15

2.6数组的扩展

2.6.1 类数组/伪数组

进行dom操作的时候

let divs=document.getElementsByTagName("div")
console.log(divs);    //HTMLCollection

在这里插入图片描述

let divs=document.getElementsByClassName("xx")
console.log(divs);     //HTMLCollection
let divs=document.querySelectorAll(".xx")
console.log(divs);    //NodeList

在这里插入图片描述

我们发现这些集合并不是array数组 怎么判断是不是数组呢?

  1. xxx instance Array 看看是不是true //false
  2. xxx.push(123) 看看能不能插入 //divs.push is not a function

我们虽然能够通过for循环能遍历出来,但是它并不是真正意义上的数组,但是它也有length属性,这种情况它叫做类数组或者伪数组,它不具有数组的一些方法(push等),那么如何把伪数组转化成真正的数组呢

在es5中 array有个方法叫slice 这个方法的作用就是从已有数组里返回选定的元素,会返回一个新的数组

let arr=Array.prototype.slice.call(divs)
console.log(arr);

在这里插入图片描述

而且发现可以使用push了

还有argument也是伪数组

function foo(){
    console.log(arguments);
    console.log(arguments instanceof Array);   //false	
}
foo(1,false ,"fdas")

在这里插入图片描述

虽然有索引但是它也不是真正的数组

如何用es6来转换成真正的数组 --Array.from

2.6.2 Array.from

let arrLike={
    0:"es5",
    1:"es6",
    2:"es7",
    length:3
}
console.log(Array.from(arrLike));

在这里插入图片描述

2.6.3 Array.of

let arr=new Array(1,2)
console.log(arr);  //[ 1, 2 ]
let arr2=new Array(3)
console.log(arr2);  //[ <3 empty items> ]

我们就是希望arr里就有一个参数怎么做呢 --Array.of

let arr2=Array.of(3)
console.log(arr2);  //[ 3 ]
let arr=[1,'abc',true ,[3,5,8],{"age":12}]
let arr2=Array.of(1,'abc',true ,[3,5,8],{"age":12})
console.log(arr);
console.log(arr2);

2.6.4 copyWithin

在这里插入图片描述

2.6.5 fill

let arr=new Array(3).fill(7)
console.log(arr);   //[ 7, 7, 7 ]
let arr=[1,2,3,4,7,8]
arr.fill(5,2,4)
console.log(arr);  //[ 1, 2, 5, 5, 7, 8 ]

2.6.6 includes

let arr=[1,2,3,4,7,8]
console.log(arr.indexOf(9));    //-1
let arr=[1,2,3,4,7,8,NaN]
console.log(arr.indexOf(NaN));   //-1   显然不符合我们的预期

有个要注意的地方:NaN==NaN --false

为了解决上面的问题 用到了includes

let arr=[1,2,3,4,7,8,NaN]
console.log(arr.indexOf(NaN));   //-1
console.log(arr.includes(NaN));   //true

2.7函数的参数

2.7.1函数参数默认值

function foo(x,y) { 
    console.log(x,y);  //hello undefined
}
foo("hello")
function foo(x,y) {
    y=y||"world"
    console.log(x,y);  //hello world
}
foo("hello")
function foo(x,y) {
    y=y||"world"
    console.log(x,y);  //hello world
}
foo("hello",0)
function foo(x,y) {
    y=y||"world"
    console.log(x,y);   //hello nihao
}
foo("hello","nihao")

现在es6里面是可以用默认值的

function foo(x,y="world") {
    console.log(x,y);   //hello nihao
}
foo("hello")

而且0也可以传进去

function foo(x,y="world") {
    console.log(x,y);   //hello 0
}
foo("hello",0)
  • 有哪些好处呢
    • 代码简洁
    • 阅读函数的时候,能知道哪个是必须传的,哪个可以省略
    • 利于优化

有一个小点:

function foo(x="world") {
    let x=2   //Identifier 'x' has already been declared
}
foo()

x已经被声明过了,说明它是默认被声明的

还有一个是参数的名称是不能重名的

这里有个细节

function foo(x,y="hello",z) {
    console.log(x,y,z);
}
foo(1,2,3)
//y已经有默认值了,如何才能不用传递y 用默认值呢呢
// foo(1,,3)//语法错误
//可以把y放到最后面
function foo(x,z,y="hello") {
    console.log(x,y,z);
}
foo(1,3)

2.7.2与解构赋值结合

function foo({x,y=100}) {
    console.log(x,y);
}
foo({
    x:1   //1 100
})
// foo()   //Cannot destructure property 'x' of 'undefined' as it is undefined.
foo({})   //undefined 100

举一个案例 ajax

function ajax(url,{
    body='',
    method='GET',
    headers={}
}={}){
    console.log(method);  //post
}
ajax("http://www.imooc.com",{method:'POST'})

2.7.3length属性

返回没有指定默认值的参数的个数

function foo(x,y,z){
    console.log(x,y,z);
}
console.log(foo.length);   //3
function foo(x,y,z=3){
    console.log(x,y,z);
}
console.log(foo.length);   //2

2.7.4作用域

let x=1
function foo(x,y=x){
    console.log(y);  //是等于全局x的值还是等于传入的值呢   --2
}
foo(2)

当调用函数的时候,,参数就会形成单独的作用域,x是当前作用域里的x,x=2

let x=1
function foo(y=x){
    let x=2
    console.log(y); //1
}
foo()

当函数foo在调用的时候,y=x在参数里面形成了一个单独的作用域,但是在这个作用域里面x并没有定义,它就会沿着作用域链往外面找,在当前作用域没找到,在全局变量找到了,就会把x=1的值给y

2.7.5函数的name属性

function foo(){}
console.log(foo.name);  //foo
console.log((new Function).name); //anonymous

function foo(x,y){
    console.log(this,x,y);
}
foo.bind({name:"xiecheng"})(1,2)  //希望this指向谁就把bind里面的参数传递进去
foo.bind({name:"xiecheng"},1,2)()  //希望this指向谁就把bind里面的参数传递进去
//{ name: 'xiecheng' } 1 2
function foo(x,y){
    console.log(this,x,y);
}
console.log(foo.bind({}).name);  //bound foo
console.log((function(){}).bind({}).name);  //bound

2.8 扩展运算符与rest参数

在这里插入图片描述

2.8.1 扩展运算符

function foo(a,b,c){
    console.log(a,b,c); //4 5 8
}
let arr=[4,5,8]
foo(...arr)

现在有两个数组,一个arr1一个arr2现在想把arr2合到arr1上去 该怎么做呢

可以循环push进去

es5里怎么用呢

let arr1=[1,2,3]
let arr2=[5,6,9]

Array.prototype.push.apply(arr1,arr2)
console.log(arr1);

用扩展运算符就很方便了

let arr1=[1,2,3]
let arr2=[5,6,9]
// Array.prototype.push.apply(arr1,arr2)
// console.log(arr1);
arr1.push(...arr2)
console.log(arr1);
let str="imooc"
var arr=[...str]
console.log(arr);  //[ 'i', 'm', 'o', 'o', 'c' ]

2.8.2 rest参数

//求和es5写法
function foo(x,y,z){
    // console.log(arguments);
    let sum=0
    Array.prototype.forEach.call(arguments, function(item){
        sum+=item
    })
    console.log(sum);
}
foo(1,2)
foo(1,2,3)

在es6里面可以把伪数组转换成真正的数组 --Array form

function foo(x,y,z){
    // console.log(arguments);
    let sum=0
    // Array.prototype.forEach.call(arguments, function(item){
    //     sum+=item
    // })
    Array.from(arguments).forEach(function(item){
        sum+=item
    })
    console.log(sum);
}
foo(1,2)
foo(1,2,3)

现在我们的三点运算符登场了

function foo(...args){
    // console.log(args);
    let sum=0
    args.forEach(function(item){
        sum+=item
    })
    console.log(sum);
}
foo(1,2)
foo(1,2,3)

在这里插入图片描述

let [x,...y]=[1,2,3,6]
console.log(x,y);

2.9 箭头函数

//先调用后定义这样不会报错
console.log(sum(4,5));

function sum(x,y){
    return x+y
}
//成功
// console.log(sum(4,5)); 
// function sum(x,y){
//     return x+y
// }
//失败
console.log(sum(4,5)); 
let sum=function(x,y){
    return x+y
}

为什么呢 --第一种函数会预定义,虽然是在上面去调用的方法,但是方法会预定义,已经在上面预先定义好了,所以先定义还是先调用都不会有问题

第二种的话 它是把方法赋值给一个变量 用var的话会输出undfined let的话直接报错,这个好理解

接下来开始箭头函数 怎么转化成箭头函数呢

  1. 去掉function
  2. 参数和函数体中间加一个=>
// var sum=(x,y)=>{
//     return x+y
// } 
// console.log(sum(3,4)); 
// 再精简
var sum=(x,y)=>x+y
console.log(sum(3,4)); 

在这里插入图片描述

2.9.1 this指向问题

在这里插入图片描述

那这时候我不想让它指向window我想让它指向按钮怎么做呢

在es5里面一共有三个方法可以改变this指向 --call apply bind 用哪个好呢,用bind比较合适,为什么呢 用bind不会对方法立即执行,它会等到1s的时候再执行,但是用call或apply的话方法会马上执行 想让里面作用域内this指向谁,bind里面的参数就传入this指向的对象

在这里插入图片描述

来学习用箭头函数改变this指向

在这里插入图片描述

这样也是指向按钮的

在箭头函数里面并没有this,箭头函数里面的this其实继承了它外面的一层执行上下文里面的this

2.9.2 不可以当做构造函数

在这里插入图片描述

如果把people改成箭头函数的形式会怎么样呢

在这里插入图片描述

发现里面什么也没有(这是经过webpack打包过的)

如果直接浏览器里输入执行发现直接报错了

在这里插入图片描述

2.9.3 不能使用arguments对象

在这里插入图片描述

这并不是我们期待的 (这个其实也受到了webpack的影响)

直接粘到浏览器的话

在这里插入图片描述

那该怎么做呢 —rest运算符

在这里插入图片描述

2.10 对象的扩展

在这里插入图片描述

在这里插入图片描述

对象的key值是变量

在这里插入图片描述

在这里插入图片描述

用箭头函数是会报错的,要注意对象里面的方法不要用箭头函数,可以es6对象里的方法简写形式

在这里插入图片描述

Object.is()是用来判断两个值是否严格相等,它和三个等号差不多。那又和三个等号有什么区别呢

三个等号的话判断NaN是和NaN不相等的,但是用这个Object.is(NaN,NaN)是返回true的

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

再来复习一下这张图

在这里插入图片描述

在这里插入图片描述

长得一样用==判断也不相等 因为是看地址的

用object.is也是一样是false

扩展运算符与object.assign()

拷贝:

let x={
    a:4,
    b:7
}
let y={...x}
console.log(y);  //{ a: 4, b: 7 }
let x={
    a:4,
    b:7
}
let y={}
Object.assign(y,x)
console.log(y);  //{ a: 4, b: 7 }
let x={
    a:4,
    b:7
}
let y={
    c:5,
    a:1
}
Object.assign(y,x)
console.log(y);  //{ c: 5, a: 4, b: 7 }

in:

let x={
    a:4,
    b:7
}
let y={
    c:5,
    a:1
}
console.log('a' in x);  //true
let arr=[1,3,6,5,8]
console.log(5 in arr);  //false  不是说5在不在数组里 而是下表为5的位置有没有元素

对对象进行遍历

在这里插入图片描述

在这里插入图片描述

2.11,2.12深拷贝与浅拷贝

重要

如何把一个对象复制给另一个对象?

可以用上节课学的object.assign()

但是这个真的能实现对象的拷贝吗?

let target={}
let source={
    a:1,
    b:2
}
Object.assign(target,source)
console.log(target); //{ a: 1, b: 2 }  符合预期
let target={
    a:4
}
let source={
    a:1,
    b:2
}
Object.assign(target,source)   
console.log(target); //{ a: 1, b: 2 }   也符合预期

现在要把source对象变得复杂一点

let target={
    a:{
        b:{
            c:1
        }
    },
    e:4,
    f:5,
    g:6
}
let source={
    a:{
        b:{
            c:1
        }
    },
    e:5,
    f:3
}
Object.assign(target,source)
console.log(target); //{ a: { b: { c: 1 } }, e: 5, f: 3, g: 6 }   这个也能理解

let target={
    a:{
        b:{
            c:1
        },
        e:4,
        f:5,
        g:6
    },
   
}
let source={
    a:{
        b:{
            c:1
        },
        e:5,
        f:3
    },
  
}
Object.assign(target,source)
console.log(target); //
let target={
    a:{
        b:{
            c:1
        },
        e:4,
        f:5,
        g:6
    },
   
}
let source={
    a:{
        b:{
            c:1
        },
        e:5,
        f:3
    },
  
}
Object.assign(target,source)
console.log(target); //{ a: { b: { c: 1 }, e: 5, f: 3 } } 这个就有点诡异了

来解释一下为什么 object.assign在拷贝对象的时候,是有问题的,对于基本数据类型是可以直接替换的,但是对于引用数据类型,它只是把地址直接指过去,因此出现了这种情况,所以在用object.assign在复制比较复杂的结构的时候并不安全,所以用它来拷贝对象的时候只是一个浅拷贝并不是深拷贝

let b=5
let a=b
console.log(a,b);
a=7
console.log(a,b);
// 5 5
// 7 5
let obj1={
    name:"xiecheng",
    age:34
}
let obj2=obj1
console.log(obj1,obj2);    //{ name: 'xiecheng', age: 34 } { name: 'xiecheng', age: 34 }
obj1.age=77
console.log(obj1,obj2);   //{ name: 'xiecheng', age: 77 } { name: 'xiecheng', age: 77 }
obj2.name="haha"
console.log(obj1,obj2);    //{ name: 'haha', age: 77 } { name: 'haha', age: 77 }

这个并不是我们想要的,我们想要深拷贝 它们不能互相干扰

可以用什么呢-- JSON.stringfy() JSON.parse()

json是什么呢 它其实就是一个字符串 只是这个字符串里面表示的是一个对象

‘{“a”:“你好”,“b”:“hello”}’

对象的key是不需要加引号的,但是json的key是要加引号的

JSON.parse()就是把字符串解析出来

JSON.stringfy()就是把它转成字符串

我们就可以通过这个来实现深拷贝 我们可以把它封装成一个函数来实现对象的深拷贝

在我们的函数里面只考虑对象和数组以及基本数据类型的深拷贝

首先要判断类型,用typeof吗 这个不好用,因为判断[] {}都是object

那应该怎么办呢 – Object.prototype.toString.call(data)

let checkType=data=>{
    // console.log(typeof("imooc"));
    // console.log(typeof(123));
    // console.log(typeof({}));
    // console.log(typeof([]));
    // //string number object object   判断不了是数组还是对象,不好用
     
    console.log(Object.prototype.toString.call(data));
}
checkType([])
checkType({})
// [object Array]
// [object Object]

只是我们要 Array还有Object这一块 可以用slice截取

Object.prototype.toString.call(data).slice(8,-1)

let checkType=data=>{
    return Object.prototype.toString.call(data).slice(8,-1);
}

let deepClone=target=>{
    let result
    let targetType=checkType(target)
    if(targetType==="Object"){
        result={}
    }else if(targetType==="Array"){
        result=[]
    }else{
        return target
    }
    for(let i in target){
        let value=target[i] 
         //value就取到了每个属性对应的值   但是对象里面可能又是一个对象或者说对象里面又嵌套了个数组呢 所以还应该判断一下value的类型
        let valueType=checkType(value)
        if(valueType=="Object" || valueType=="Array"){
           result[i]=deepClone(value)   //递归
        }else{
            //基本数据类型
            result[i]=value
        }
    }
    return result
}

let arr=[1,2,{age:18}]
let arr2=deepClone(arr)
// console.log(arr2);
arr2[2].age=34
console.log(arr);
console.log(arr2);
// [ 1, 2, { age: 18 } ]
// [ 1, 2, { age: 34 } ]
let checkType=data=>{
    return Object.prototype.toString.call(data).slice(8,-1);
}

let deepClone=target=>{
    let result
    let targetType=checkType(target)
    if(targetType==="Object"){
        result={}
    }else if(targetType==="Array"){
        result=[]
    }else{
        return target
    }
    for(let i in target){
        let value=target[i] 
         //value就取到了每个属性对应的值   但是对象里面可能又是一个对象或者说对象里面又嵌套了个数组呢 所以还应该判断一下value的类型
        let valueType=checkType(value)
        if(valueType=="Object" || valueType=="Array"){
           result[i]=deepClone(value)   //递归
        }else{
            //基本数据类型的情况
            result[i]=value
        }
    }
    return result
}

let obj={
    name:"xiecheng",
    hobby:["coding","eating"]
}
let obj2=deepClone(obj)
obj2.hobby[0]="sleeping"
console.log(obj);
console.log(obj2);
// { name: 'xiecheng', hobby: [ 'coding', 'eating' ] }
// { name: 'xiecheng', hobby: [ 'sleeping', 'eating' ] }

第三章 ECMAScript2015(ES6)之新特性

3.1 面向过程与面向对象

把大象装进冰箱需要几步 打开门 放进去 装进去 --面向过程

面向对象的话就要分析有哪些对象,对象有哪些属性和方法

属性:我的名字叫xxx,年龄xx
方法:我会吃饭,我会睡觉

大象对象,冰箱对象

大象:大象的颜色,大象的名字这些都不需要关心,要关心的是体积 判断大象体积比冰箱大还是小,比冰箱小才能装进去
大象有哪些方法需要关心呢 没有

冰箱:关心的也有体积

隐藏对象:一个人或者一双手 作用就是把大象放到里面去

这种分析方式就是面向对象的分析方式 我们关心的不是步骤而是都有哪些对象,对象都有哪些属性和方法

面向对象是一种思想,慢慢领悟吧

js是一种基于对象的语言(object-based)

什么是类 什么是对象

在这里插入图片描述

类还有父类和子类 狗有很多品种,可以是一个类,动物的范围更大,就是个父类了

3.2 es5中的类与继承

3.2.1 类

es5里面没有类的概念,所以就用方法来模拟一个类 名称一般都是大写的

        //类
// function People(){
//     this.age=75
//     this.name="Bob"
// }
// let p1=new People()
// console.log(p1);
//上面是写死的
function People(name,age){
    this.age=age
    this.name=name 
}
let p1=new People("Bob",22)
console.log(p1);
function People(name,age){
    console.log(this);   //指向new出来的实例化对象
    this.age=age
    this.name=name
}
let p1=new People("Bob",22)
console.log(p1);

let p2=new People("zhangsan",21)
console.log(p2);

在这里插入图片描述

function People(name,age){
    // console.log(this);
    this.age=age
    this.name=name
}
let p1=new People("张三",66)
p1.showName()
let p2=new People("李四",8)
p2.showName()
// my name is 张三
// my name is 李四

方法的话我们一般不会把它定义在构造函数里面 为什么呢 因为每次当我们new People的时候就会new一个方法 这样不好

我们可以把它定义在原型下面 这样就可以共用了

function People(name,age){
    this.age=age
    this.name=name
}
People.prototype.showName=function(){
    console.log("my name is "+ this.name);
}
let p1=new People("张三",66)
p1.showName()    //my name is 张三
console.log(p1);

在这里插入图片描述

举个例子 八月十五吃月饼 月饼上有很多花纹 ,当前要制作100块月饼,两种方式,一种制作出100块月饼,一个一个往上面去雕刻花纹,第二种方式,有一个模子,用模子做月饼 prototype就相当于模子 ,只要是从类里面生产出来,都自带原型下面的方法

3.2.2 静态属性和静态方法

let str=new String("imooc")
console.log(str);
let arr=new Array(2,3,4)
console.log(arr);
let obj=new Object({})
obj.name="xiecheng"
console.log(obj);
// [String: 'imooc']
// [ 2, 3, 4 ]
// { name: 'xiecheng' }

console.log(Math.max(6,9));
console.log(Math.random());

我们用Math这个内置对象的时候,并没有去new Math 而是直接调用Math下面的方法

为什么呢 Math是静态方法 --当前的方法和实例化的对象是没有关系的

我们先来看一下静态属性

function People(name,age){
    // 这里的age和name是实例属性  这些值和实例化对象是有关系的
    this.age=age
    this.name=name
}
//静态属性
People.count=1
console.log(People.count);  //1
function People(name,age){
    // 这里的age和name是实例属性  这些值和实例化对象是有关系的
    this.age=age
    this.name=name
    People.count++
}
People.prototype.showName=function(){
    console.log("my name is "+ this.name);
}
//静态属性
People.count=1
console.log(People.count);  //1
let p1=new People("张三",66)
console.log(People.count);  //2

知识点

静态属性是定义在类里面的,实例属性是定义在构造函数里面的

什么是静态方法呢

先看一下实例方法

在这里插入图片描述

现在想定义一个静态方法如何定义呢

function People(name,age){
    // 这里的age和name是实例属性  这些值和实例化对象是有关系的
    this.age=age
    this.name=name
    People.count++
}
People.prototype.showName=function(){
    console.log("my name is "+ this.name);
}
//静态属性
People.count=1
//静态方法
People.countNum=function(){
    console.log("count="+ People.count);
}
let p1=new People("张三",66)
People.countNum();  //2

构造函数里的this指向谁 --指向实例化对象
静态方法里的this指向谁呢 --不是实例化对象,静态方法和实例化对象没有关系的 它指向的是构造函数

function People(name,age){
    // 这里的age和name是实例属性  这些值和实例化对象是有关系的
    this.age=age
    this.name=name
    People.count++
}
//实例方法
People.prototype.showName=function(){
    console.log("my name is "+ this.name);
}
//静态属性
People.count=1
//静态方法
People.countNum=function(){
    console.log("count="+ People.count);
    console.log(this);
}
let p1=new People("张三",66)
People.countNum();  //2

在这里插入图片描述

3.2.3 类的继承

我们定义一个父类Animal 和一个子类 Dog 有一种继承方式叫构造函数继承 --用Animal.call()
call的作用可以用于改变this指向 我们希望Animal里面的this指向Dog里面的实例对象
这个this可以通过Dog传到Animal里去 call里面有几个参数,第一个参数是要把this指向谁 就把要指向的对象放进去
现在是希望把Dog里的this传到Animal里去,我们就把Dog的this传到第一个参数,

A.call(B,x,y)

1`改变函数A的this指向,使之指向B;

2` 把A函数放到B中运行,x和y是A函数的参数。

//父类
function Animal(name){
    this.name=name
}
Animal.prototype.showName=function(){
    console.log("名字是"+ this.name);
}
//子类
function Dog(name){
    Animal.call(this,name)
}
let dog1= new Dog("erha")
console.log(dog1.name);     //erha
function Animal(name){
    this.name=name
}
Animal.prototype.showName=function(){
    console.log("名字是"+ this.name);
}
//子类
function Dog(name,color){
    Animal.call(this,name)
    this.color=color
}
let dog1= new Dog("erha","white")
console.log(dog1.name,dog1.color);     //erha  white

那可以调用showName方法吗 —不行 它只能继承父类的属性,不能继承方法

那么如何继承方法呢 把Dog的原型指向Animal的实例化对象 还有个小问题Dog原型的构造函数constructor需要再指回Dog (这里把继承属性的代码都注释了)

//父类
function Animal(name){
    this.name=name
}
Animal.prototype.showName=function(){
    console.log("名字是"+ this.name);
}
//子类
function Dog(name,color){
    //继承属性
    // Animal.call(this,name)
    this.color=color
}
//继承方法
//把Dog的原型指向Animal的实例化对象  
Dog.prototype=new Animal()
//Dog原型的构造函数constructor需要再指回Dog 
Dog.prototype.constructor=Dog
let dog1= new Dog("erha","white")
// console.log(dog1.name,dog1.color);     //erha  white
dog1.showName()    //名字是undefined  因为那个call被注释了 但是说明已经起作用了因为输出了‘名字是’ 把上面的注释去掉就好了
//下面这两句叫做原型继承
//把Dog的原型指向Animal的实例化对象  
Dog.prototype=new Animal()
//Dog原型的构造函数constructor需要再指回Dog 
Dog.prototype.constructor=Dog

我们发现构造函数继承只能继承属性,而原型继承只能继承方法 如果把这两种组合起来就既可以继承属性又可以继承方法了,这种方式叫做组合式继承

3.3 es6中的继承

在这里插入图片描述

在es6里面有个新的关键字class 语法糖

class People{
    constructor(name,age){
        this.name=name,
        this.age=age
    }
    showName(){
        console.log(this.name);
    }
}
let p1=new People("zhangsan",17)
p1.showName()   //张三

在这里插入图片描述

打印p1出来发现方法是在prototype里的语法糖 方便很多

那 如何在es6实现继承呢

class People{
    constructor(name,age){
        this.name=name,
        this.age=age
    }
    showName(){
        console.log(this.name);
    }
}
class Coder extends People{
    constructor(name,age,company){
        super(name,age)
        this.company=company
    }
}
let coder1=new Coder("Bob",18,"imooc")
console.log(coder1.name,coder1.company);   //Bob imooc
coder1.showName()   //Bob

子类的方法也可以使用

class People{
    constructor(name,age){
        this.name=name,
        this.age=age
    }
    showName(){
        console.log(this.name);
    }
}
class Coder extends People{
    constructor(name,age,company){
        super(name,age)
        this.company=company
    }
    showCompany(){
        console.log(this.company);
    }
}
let coder1=new Coder("Bob",18,"imooc")
console.log(coder1.name,coder1.company);   //Bob imooc
coder1.showName()   //Bob
coder1.showCompany()   //imooc

现在我们来看看es6里的新的方式

之前我们的实例属性都是定义在constructor构造函数里面的,而且属性是定义在this.上面的,在es6里面我们也可以把属性定义在类的最顶层 什么是最顶层呢

class People{
	xxxxxx          //这个就是我们说的最顶层
    constructor(name,age){
        this.name=name,
        this.age=age
    }
    showName(){
        console.log(this.name);
    }
}

那如何在最顶层定义属性呢

在class内部定义属性的话使用get和set关键字定义的

class People{
    constructor(company){
        this.company=company
    }
    //虽然sex是个方法 但是我们把它当做一个属性来调用
    get sex(){   
        return "male"
    }
    showCompany(){
        console.log(this.company);
    }
}
//我们想输出当前人的性别怎么做呢
let p1 =new People("imooc")
console.log(p1.sex);  //注意这里sex没加括号

那么这种方法和构造函数里面定义属性有什么区别呢

//我们想输出当前人的性别怎么做呢
let p1 =new People("imooc")
p1.sex="femal"      //并不能改变成femal
console.log(p1.sex);  //注意这里sex没加括号

所以用get这个方法的话说明这个属性是只读的 它是不能去写的

如果想给它赋值怎么办呢 可以用set

   get sex(){   
        return "male"
    }
//这样可以吗  不可以  会形成一个死循环
    set sex(val){
        this.sex=val
    }

为什么会形成一个死循环呢 – 只要设置值就会触发set方法 set方法又会设置值 一直循环下去

所以不能直接给set属性设置值 我们应该引入一个另外一个新的属性

class People{
    constructor(company){
        this.company=company
        this._sex=-1       //变动
    }
    //虽然sex是个方法 但是我们把它当做一个属性来调用
    get sex(){      //属性
        return this._sex   //变动
    }
    set sex(val){
        this._sex=val      //变动
    }
    showCompany(){
        console.log(this.company);
    }
}
//我们想输出当前人的性别怎么做呢
let p1 =new People("imooc")
p1.sex="male"     //set
console.log(p1.sex);  //注意这里sex没加括号  get
p1.sex="female   
console.log(p1.sex);   //female

那跟以前学的有什么差别呢 代码多写了那么多好像很麻烦

举个例子 给sex设置值的时候 female或者male给它设置成1或者是0 用1表示成male用0表示female,当设置1的时候返回female当设置0的时候返回female, 这就涉及到了一些业务逻辑的操作

class People{
    constructor(company){
        this.company=company
        this._sex=-1  
    }
    //虽然sex是个方法 但是我们把它当做一个属性来调用
    get sex(){   
        if(this._sex===1){
            return "male"
        }else if(this._sex===0){
            return "female"
        }else{
            return "err"
        }
    }
    set sex(val){
        if(val===1||val===0){
            this._sex=val
        }
    }
    showCompany(){
        console.log(this.company);
    }
}
let p1 =new People("imooc")
p1.sex='FDS'
console.log(p1.sex);
p1.sex=1    
console.log(p1.sex);  
p1.sex=0 
console.log(p1.sex);

而且它的子类也能用 这个可以自己试验一下

在上次的es5里面学习了如何定义静态的属性和静态的方法,什么是静态属性和静态方法–它跟实例化对象没有关系,直接定义在类里 那么当前如何定义静态属性和静态方法呢

先来看一下如何定义静态方法:

在es6里有一个新的关键字叫做static --静态的

class People{
    constructor(company){
        this.company=company
        this._sex=-1  
    }
    //虽然sex是个方法 但是我们把它当做一个属性来调用
    get sex(){   
        if(this._sex===1){
            return "male"
        }else if(this._sex===0){
            return "female"
        }else{
            return "err"
        }
    }
    set sex(val){
        if(val===1||val===0){
            this._sex=val
        }
    }
    showCompany(){
        console.log(this.company);
    }
    //静态方法
    static getCount(){
        return 5
    }
}
let p1 =new People("imooc")
console.log(p1.getCount());  //p1.getCount is not a function

报错了 我们应该再类里面直接调用

class People{
    constructor(company){
        this.company=company
        this._sex=-1  
    }
    //虽然sex是个方法 但是我们把它当做一个属性来调用
    get sex(){   
        if(this._sex===1){
            return "male"
        }else if(this._sex===0){
            return "female"
        }else{
            return "err"
        }
    }
    set sex(val){
        if(val===1||val===0){
            this._sex=val
        }
    }
    showCompany(){
        console.log(this.company);
    }
    //静态方法
    static getCount(){
        return 5
    }
}
let p1 =new People("imooc")
// console.log(p1.getCount());  //p1.getCount is not a function
console.log(People.getCount());   //5

那子类能继承这个静态方法吗

class People{
    constructor(company){
        this.company=company
    }
    //静态方法
    static getCount(){
        return 5
    }
}
let p1 =new People("imooc")
// console.log(p1.getCount());  //p1.getCount is not a function
console.log(People.getCount());   //5
class Coder extends People{
    constructor(company){
        super(company)
    }
}
console.log(Coder.getCount());  //5   也可以使用这个静态方法

那静态属性怎么定义呢 – 当前还不支持 用static来定义静态属性

那我们想实现一个静态属性怎么做呢-- 我们可以像es5一样

class People{
    constructor(company){
        this.company=company
    }
    //静态方法
    static getCount(){
        return 5
    }
}
People.count=9   //静态属性
let p1 =new People("imooc")
console.log(People.getCount());   //5
console.log(People.count);       //9
class Coder extends People{
    constructor(company){
        super(company)
    }
}
console.log(Coder.getCount());  //5
console.log(Coder.count);   //9

3.4 新的原始数据类型Symbol

以前有这几个:字符串,对象,Number,布尔,null,undefined

Symbol的出现为我们的原始数据类型又添加了一个,现在是有七种了

let s1=Symbol()  //没有用new哦 因为它不是对象
console.log(s1);  //Symbol()
let s2=Symbol()
console.log(s2); //Symbol()
console.log(s1===s2);  //false

为什么呢 Symbol表示的是独一无二的

所以虽然打印出来s1和s2长得一样,但是它们并不相等

let s1=Symbol('foo')
let s2=Symbol('bar')
console.log(s1); //Symbol(foo)
console.log(s2); //Symbol(bar)
console.log(s1===s2)  //false
const obj={
    name:"mooc"
}
let s=Symbol(obj)
console.log(s);   //Symbol([object Object])

如果是个对象的话 它会自动调用对象的tostring方法,将它转化成字符串

const obj={
    name:"mooc",
    toString(){
        return this.name
    }
}
let s=Symbol(obj)
console.log(s);   //Symbol(mooc)
let s=Symbol()
s.name="mooc"
console.log(s);  //Symbol()   Symbol不是个对象,所以不能拿对象的方式来对待它 所以对象.属性=xx在它身上是不能用的

我们可以把它理解为不能重复的字符串

它还提供了一个api

let s=Symbol("mooc")
console.log(s.description);  //mooc

Symbol.for()

let s1=Symbol.for("foo")  //跟前面的一样的
console.log(s1);
// 那么写for和不写for有什么区别呢
let s2=Symbol.for("foo")
console.log(s2);
console.log(s1===s2);   //true

用Symbol.for()创建的Symbol值其实是相当于定义在全局的环境中 什么是全局环境? 第二次Symbol,for的时候它会去全局找在前面是否声明过 如果有那么下面的就指向上面的那个 所以它俩是同一个

直接用Symbol.()去声明的话 并不会在全局环境中去定义,而是每次都会形成一个新的

用Symbol.for的话不管是不是在全局环境下声明的,它都会跑到全局

function foo(){
    return Symbol.for('foo')
}
let x=foo()
let y=Symbol.for('foo')
console.log(x===y);   //true

还有一个Symbol.keyFor()

const s1=Symbol('foo')
console.log(Symbol.keyFor(s1));//undefined
const s2=Symbol.for('foo')
console.log(Symbol.keyFor(s2));  //foo

这是用来干嘛的呢 它是用来找是否全局被登记过,如果登记过就返回描述

应用场景1

我们来描述一个班级信息 人名可能会重复

const grade={
    张三:{address:'xxx',tel:'123'},
    李四:{address:'yyy',tel:'222'},
    李四:{address:'zzz',tel:'233'},
}
console.log(grade);
//{
//     '张三': { address: 'xxx', tel: '123' },
//     '李四': { address: 'zzz', tel: '233' }
//   }

发现现在只有两个同学了

为什么呢 因为对象里面key必须是唯一的,如果有重复的,那么后定义的就会覆盖掉前面定义的 不满足需求

这个时候就可以用Symbol

const stu1=Symbol('李四')  //独一无二的李四
const stu2=Symbol('李四')
const grade={
        [stu1]:{address:'xxx',tel:'123'},
        [stu2]:{address:'yyy',tel:'222'},
    }
console.log(grade);
// {
//     [Symbol(李四)]: { address: 'xxx', tel: '123' },
//     [Symbol(李四)]: { address: 'yyy', tel: '222' }
//   }

那么怎么获取到两个李四的信息呢

const stu1=Symbol('李四')
const stu2=Symbol('李四')
const grade={
        [stu1]:{address:'xxx',tel:'123'},
        [stu2]:{address:'yyy',tel:'222'},
    }
console.log(grade);
console.log(grade[stu1]);
console.log(grade[stu2]);
// { address: 'xxx', tel: '123' }
// { address: 'yyy', tel: '222' }
const sym=Symbol('imooc')
class User{
    constructor(name){
        this.name=name
        this[sym]='mooc.com'
    }
    getName(){
        console.log(this.name+this[sym]);   //张三mooc.com
    }
}
let user=new User("张三")
user.getName()

但是问题又来了

const sym=Symbol('imooc')
class User{
    constructor(name){
        this.name=name
        this[sym]='mooc.com'
    }
}
let user=new User("张三")
for(key in user){
    console.log(key);   //name
}

这里只有name属性,并没有我们的Symbol属性,所以要注意,通过for in 是无法遍历出Symbol属性的 这个一定程度上能对我们的属性进行隐藏

我们以前还学过一种方法

for(let key of Object.keys(user)){
    console.log(key);   //name
}

发现依然不行

来看下面这个方法: 这个是只能取到Symbol 取不到其他的不是Symbol的实例属性

const sym=Symbol('imooc')
class User{
    constructor(name){
        this.name=name
        this[sym]='mooc.com'
    }
}
let user=new User("张三")
for(let key of Object.getOwnPropertySymbols(user)){
    console.log(key);   //Symbol(imooc)
}

那么有没有方式既能取到普通的属性又能取到Symbol属性呢 --Reflect.ownKeys()

const sym = Symbol('imooc')
class User {
    constructor(name) {
        this.name = name
        this[sym] = 'mooc.com'
    }
}
let user = new User("张三")
// for(let key of Object.getOwnPropertySymbols(user)){
//     console.log(key);   //Symbol(imooc)
// }
for (let key of Reflect.ownKeys(user)) {
    console.log(key);
    // name
    // Symbol(imooc)
}

应用场景2

消除魔术字符串

function getArea(shape){
    let area=0
    switch(shape){
        case 'Triangle':
            area=1
            break
        case 'Circle':
            area=2
            break

    }
    return area
}
console.log(getArea('Triangle'));  //1

这里的Triangle多次出现了,这种代码风格并不好 这种情况叫它魔术字符串

那怎么能消除魔术字符串呢

const shapeType={
    triangle:'Triangle',
    circle:'Circle'
}
function getArea(shape){
    let area=0
    switch(shape){
        case shapeType.triangle:
            area=1
            break
        case shapeType.circle:
            area=2
            break

    }
    return area
}
console.log(getArea(shapeType.triangle));  //1

这样一定程度上就消除了魔术字符串

这个时候其实我们并不是很关系 Triangle和Circle 可以直接把它们替换成Symbol()

const shapeType={
    triangle:Symbol(),
    circle:Symbol()
}
function getArea(shape){
    let area=0
    switch(shape){
        case shapeType.triangle:
            area=1
            break
        case shapeType.circle:
            area=2
            break

    }
    return area
}
console.log(getArea(shapeType.triangle));  //1

3.5 新的数据结构 set

set和之前学的数组很相似 数组中的每个值可以是重复的,但是set里面的每一个值都是唯一的 这个就是set的特点

let s=new Set([1,2,3])
console.log(s);  //Set { 1, 2, 3 }
let s=new Set([1,2,3,2,4])
console.log(s);  //Set { 1, 2, 3, 4 }

set都有哪些常见的方法呢

let s=new Set([1,2,3,2,4])
s.add('mooc')
console.log(s);  //Set { 1, 2, 3, 4, 'mooc' }

链式写法

let s=new Set([1,2,3,2,4])
s.add('mooc').add('es')
console.log(s);  //Set { 1, 2, 3, 4, 'mooc', 'es' }
let s=new Set([1,2,3,2,4])
s.add('mooc').add('es')
console.log(s);  //Set { 1, 2, 3, 4, 'mooc', 'es' }
s.delete(2)
console.log(s);  //Set { 1, 3, 4, 'mooc', 'es' }
let s=new Set([1,2,3,2,4])
s.add('mooc').add('es')
console.log(s);  //Set { 1, 2, 3, 4, 'mooc', 'es' }
s.delete(2)
console.log(s);  //Set { 1, 3, 4, 'mooc', 'es' }
s.clear()
console.log(s);  //Set {}
console.log(s.has(6)); 
console.log(s.has(2)); 
//false true

看看s的大小:size 数组里面是length

console.log(s.size); //4

s.forEach(item=>{
    console.log(item);  //1 2 3 4
})

另外的遍历方法:for of 不仅可以遍历像数组一样的 可遍历的对象不止有数组 set也是一个可遍历的对象

for(let item of s){
    console.log(item);  //1 2 3 4
}
for(let item of s.keys()){
    console.log(item);  //1 2 3 4
}
for (let item of s.values()){
    console.log(item);   //1 2 3 4
}
for(let item of s.entries()){
    console.log(item);  //[1,1][2,2][3,3][4,4]
}

通过这几种遍历方式,我们发现set这种结构 他也是有key有value的 但是key和value的值是完全一样的 这是set结构的一个特点

那set在什么时候会用到呢

数组去重操作

let arr=[1,3,4,8,5,6,2,3]
let s=new Set(arr)
console.log(s);   //Set { 1, 3, 4, 8, 5, 6, 2 }

合并去重

let arr1=[1,2,3,5]
let arr2=[1,4,5,6]
//现在想把它俩合并并且把重复的给去掉
let s=new Set([...arr1,...arr2])
console.log(s); //Set { 1, 2, 3, 5, 4, 6 }
let arr1=[1,2,3,5]
let arr2=[1,4,5,6]
//现在想把它俩合并并且把重复的给去掉
let s=new Set([...arr1,...arr2])
console.log(s); //Set { 1, 2, 3, 5, 4, 6 }

在这里插入图片描述

但是我们得到的还是个set我们希望拿到一个数组

console.log([...s]);  //[ 1, 2, 3, 5, 4, 6 ]

在这里插入图片描述

还可以:

console.log(Array.from(s));

数组求交集

我们把两个数组变成两个set

let arr1=[1,2,3,5]
let arr2=[1,4,5,6]
let s1=new Set(arr1)
let s2=new Set(arr2)
let result=new Set(arr1.filter(item=>s2.has(item)))
console.log(result);  //Set { 1, 5 }
//后面用扩展运算符或者array.from把它变成数组就ok

3.6 set下

求差集

let arr1=[1,2,3,5]
let arr2=[1,4,5,6]
let s1=new Set(arr1)
let s2=new Set(arr2)
// let result=new Set(arr1.filter(item=>s2.has(item)))
// console.log(result);  //Set { 1, 5 }
let arr3=new Set(arr1.filter(item=>!s2.has(item)))
let arr4=new Set(arr2.filter(item=>!s1.has(item)))
console.log(arr3);  //Set { 2, 3 }
console.log(arr4);  //Set { 4, 6 }
console.log([...arr3,...arr4]); // [ 2, 3, 4, 6 ]

WeakSet:只能存储对象,不能把数字,字符串这样的类型放进去

let ws=new WeakSet()
// ws.add(1)  //Invalid value used in weak set
ws.add({})
console.log(ws);  //WeakSet { <items unknown> }
let ws=new WeakSet()
// ws.add(1)  //Invalid value used in weak set
ws.add({
    name:'imooc'
})
ws.add({
    age:26
})
console.log(ws); 
//WeakSet {{…}, {…}}
let ws=new WeakSet()
// ws.add(1)  //Invalid value used in weak set
ws.add({
    name:'imooc'
})
ws.add({
    age:26
})
ws.delete({
    name:'imooc'
})
console.log(ws); 
//WeakSet {{…}, {…}}

为什么delete删不了name呢 还是两个对象

来解释一下为什么 虽然长得一样 但是他们是同一个对象吗

js中基本数据类型存在栈内存里面,对于对象这种引用数据类型是存在堆内存里的,虽然这两个对象长得一样,但是他们存在在不同的内存空间 所以添加一个对象,然后删除另外一个对象的时候 我们添加的对象并不会被删除掉

那怎么办呢

这样添加删除的就是同一个对象了

const obj1={
    name:'imooc'
}
const obj2={
    age:26
}
let ws=new WeakSet()
ws.add(obj1)
ws.add(obj2)
ws.delete(obj1)
console.log(ws); 
//WeakSet {{…}}
console.log(ws.has(obj2));  //true

注意它是不能被遍历的

ws.forEach(item => {
    console.log(item);    //ws.forEach is not a function
});

弱引用

在es中 有一个垃圾回收机制,弱引用在垃圾回收机制中是不考虑弱引用的 就是说如果其他对象不再去引用当前对象的时候,那么垃圾回收机制就会自动回收对象所占用的内存空间,他不会考虑当前的对象是否还被weakset去引用

垃圾回收机制: GC 计数的垃圾回收机制 刚定义的时候是0 每引用一次结果就加1 只要不为0 垃圾回收机制就不会回收变量 也就是不会释放当前的内存 这种情况 如果这个值一直被引用着,垃圾回收机制就不会把它回收 就会引发内存泄漏

但是我们的weakset是一种弱引用,它是不会被记入垃圾回收机制的。

那么这个weakset能用在什么场景下呢 他比较适合临时去存放一些对象

WeakSet适合应用于什么场景下呢

适合临时去存放一些对象,跟对象绑定一些相关的信息,比如说当对象销毁以后 weakset里面的引用也会自动的消失,这个就是弱引用

set和WeakSet有哪些区别

首先:WeakSet只能存储对象 set其他类型的也可以

第二:set可以循环遍历 但是WeakSet不可以

第三:WeakSet是弱引用,并不会被垃圾回收机制所考虑,所以说他里面引用的对象消失以后,WeakSet里面的引用也会被自动消失 这就是他们两个的区别

回顾

在这里插入图片描述

3.7 新的数据结构 map(暂无)

3.8 字符串的扩展 上

在这里插入图片描述

unicode是一种字符表示法 它定义了一些主流语言中用到的一些字符 比如中文啊 或者欧洲一些语言啊等等还有一些标点啊 特殊字符啊这些

在es6中加强了对Unicode的一种支持 我们可以采取什么样的方式呢

\uxxxx – \u就表示当前是一个Unicode字符 后面四位表示Unicode的码点 这个码点是有取值范围的 0000-ffff 一旦超出取值范围就需要用两个字节来表示

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值