关键词:重要 知识点
看的是慕课网谢成老师的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数组 怎么判断是不是数组呢?
- xxx instance Array 看看是不是true //false
- 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的话直接报错,这个好理解
接下来开始箭头函数 怎么转化成箭头函数呢
- 去掉function
- 参数和函数体中间加一个=>
// 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 一旦超出取值范围就需要用两个字节来表示