let、const和块级作用域
let
- let没有变量提升、可以重复定义检查,同一个作用域中重复定义就会报错、可以被用于块级作用域
- 使用let形成的块级作用域可以在大部分具有{...}的语句中使用,例如for(){} , do{}while() , while{} 和switch(){}等。
if(true){ //块级作用域
let a=10;
}
//console.log(a); //报错 a is not defined
---------------------------------------------
let b=10;
b=20;
console.log(b) //20 可以被重新赋值
---------------------------------------------
let x1=1;
{
console.log(x1); //1
}
//以上证明 作用域链是存在的
const
- 定义常量,对常量赋值会报错
- const定义对象不能完全锁死对象
- 栈内存中存放的只是该对象的访问地址,在堆内存中为这个值分配空间
- const的原理便是在变量名与内存地址之间建立不可变的绑定,当后面的程序尝试申请新的内存空间时,引擎便会抛出错误。
const person={'name':'aaa'};
person.name='bbb'
console.log(person.name);//不是完全锁死
const a=10; //const必须给一个初值 否则会报错 并且不能重复声明
a=20; //报错 不可修改
console.log(a)
- 完全锁死对象要用Object.freeze();多层属性对象可以利用递归锁死。
const obj1=Object.freeze({
a:1,
b:2
})
内存与变量之间的关系
- Ecmascript在对变量的引用进行读取时,会从该变量当前所对应的内存地址所指向的内存空间中读取内容。而当用户改变变量的值时,引擎会重新从内存中分配一个新的内存空间以存储新的值,并将新的内存地址与变量进行绑定。
- 关于堆内存和栈内存
栈内存存放的是该对象的访问地址,在堆内存中为这个值分配空间
let const的使用原则
- 1.一般情况下,使用const来定义值的存储容器(常量)
- 2.只有在值容器明确被确定将会被改变时才使用let来定义(变量)
- 3.不再使用var
let const与循环语句for...of...
for...of..循环不能直接遍历对象(但是支持return)
const zootop=[
{ name: 'nick', gender: 1,species:'fox'},
{name: 'judy', gender: 0,species: 'Bunny'}
];
for(const {name, species} of Zootopia) {
console. log(`Hi, I am (name), and I am a (species)`)
}
//Hi, I am nick, and I am a fox
//Hi, I am judy, and I am a Bunny
- 数组类型再次被赋予了一个名为entries的方法,它可以返回对应数组中每一个元素与其下标配对的一个新数组
const arr=['a','b','c'];
console.log(arr.entries())//[[0,'a'],[1,'b'],['2','c']];
模板字符串
把变量放在${ }中,字符串放在反引号中,即可渲染,代替字符串拼接
const zootop=[
{ name: 'nick', gender: 1,species:'fox'},
{name: 'judy', gender: 0,species:'Bunny'}
];
for(const [index,{name,species}] of zootop.entries()){
console.log(`${index} i am ${name} `)
}
简单实现:
function templatea(str,arr){
var reg=/\$\{([^\{\}]+)\}/g;//匹配一个${...};
var i=0;
var str=str.replace(reg,function(){
return arr[i++];
})
return str;
}
console.log(templatea('ajlj${aaa}sdkl${bbb}fjs${ccc}d',[111,222,333]))
//ajlj111sdkl222fjs333d
原理:
let aaa=111,bbb=222,ccc=333;
desc`ajlj${aaa}sdkl${bbb}fjs${ccc}d`;
function desc(raw,...Ary){
let res=''
for(let i=0;i<raw.length-1;i++){
res+=raw[i]+Ary[i]
}
res+=raw[raw.length-1];
return res;
}
默认参数 和 拓展运算符
默认参数
function fn(arg='foo'){
console.log(arg);
}
fn();//foo
fn('bar')//bar
类数组转数组
//类数组转数组的一般方式
args=[].slice.call(arguments);
//Array的新方法
args=Array.from(arguments)
//剩余参数
function fn(foo,...rest){
//rest是arguments除去foo的剩余数组[2,3,4,4]
}
fn(1,2,3,4,4)
--------------------
arr1=[...arr2];//数组拷贝
剩余参数注意事项
- 使用剩余参数后,后面不可以再添加任何参数,否则会报错
- 推荐使用 ...arg 代替 arguments
解构赋值
- 只要等号两边的模式相同,左边的变量就会被赋予对应的值 这种写法叫做‘模式匹配’
var [a,b,c]=[12,5,'aaa']
console.log(a,b,c)//12,5,'aaa'
- 跟顺序无关 键值对对应的名字 a,b,c 必须一致 否则解析不出来
var {a,b,c}={b:5,a:10,c:'aa'}
console.log(a,b,c)//10,5,'aa'
- 模式匹配 左右两边的格式一样
var [a,[b,c],d]=[10,[1,2],100]
console.log(a,b,c,d)//10,1,2,100
- 给默认值 可用于函数参数的默认值
var {time=12,a}={time,a:'111'}
console.log(time,a)//12,'111'
- 对象的解构赋值
let {foo,bar}={foo:'aaa',bar:'bbb'};
console.log(foo) //aaa
//对象的解构于数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由他的位置决定;而对象的属性没有次序,变量必须与属性同名
- 匹配的模式与真正的变量
let { foo3: foo2, bar: bar2 } = { foo3: "aaa", bar: "bbb" };
let { foo2: baz2 } = { foo2: "aaa", bar: "bbb" };
console.log(baz2 +'---------baz2') // "aaa"
console.log(foo2 +'---------foo2')//'aaa'
console.log(foo3) // error: foo3 is not defined
//上面代码中,foo3是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo3。
- 属性的简洁表示法,属性名和属性值一样的情况下可以简写
var foo='bar';
var baz={foo};
baz //{foo:'bar'}
//等同于
var baz={boo:foo}
//上面代码表明,ES6允许在对象之中,直接写变量。这时,属性名为变量名,属性值为变量值
function f(x,y){
return {x,y}
}
//等同于
function f(x,y){
return {x:x,y:y}
}
- 方法简写
var o={
method(){
return 'Hello';
}
}
//等同于
var o={
method:function(){
return 'Hello'
}
}
9. 变量值转换(三个杯子调换两杯水的问题)
传统方法
function swap(a,b){
var temp=a;
a=b;
b=temp;
}
现在的方法(利用解构赋值)
[a,b]=[b,a]
注:
- 如果解构不成功,变量的值就等于undefined
- 如果等号右边不是数组(或者严格的说,不是可遍历对象),那么将会报错
箭头函数
- 单行
- 单行可以省略 return,
- 只能包含一条语句
- 返回对象时要用()包裹返回的对象
const fn=foo=>`${foo} world`//意思是return `${foo} world`
相当于
var fn=function (foo){
return foo+' world';
}
- 多行(需要写return,需要 { } 包裹函数体)
foo=>{
return `${foo} world`;
}
- 有无参数
1.如果一个箭头函数无参数传入,则需要一个空括号占位
const fn=()=>{
........
}
2. 如果有多个参数(需要括号)
const fn=(arg1,arg2,...args)=>{
........
}
3. 如果有一个参数(不需要括号)
const fn=foo=>{
return `${foo} world`
}
- this指针
- 箭头函数中this会延伸至上一层作用域中,即上一层的上下文会穿透到内层的箭头函数中
const obj={
a:111,
foo(){
const bar = ()=>this.a;
return bar;
}
}
window.a=222
window.bar=obj.foo();
window.bar();//111 不关心函数执行时前边是谁,this最开始指定谁,是不会再改变的
--------------------------
以上foo函数代码类似于
foo(){
var that=this;
var bar=function(){
return that.a
}
}
//可以说:在当前作用域的上层作用域定义that=this,函数内使用that
- apply和call无法改变this的指向,bind是可以改变this的
- 不要随意在顶层作用域使用this,防止指向window 报错
- 箭头函数中没有arguments,callee和caller对象,想要使用arguments的话,用 ...args代替
const fn=(...args){
console.log(args[0])
}
fn(1,2,3)//1
对象、数组新特性
- 对象支持proto注入:相当于开放原型链,可以改变所属类的原型
继承的又一种方式
obj.__proto__=new Array();//对象obj继承数组的属性和方法;
- 可动态计算的属性名:可以使用一个表达式作为属性名
const obj={};
const key='foo';
obj[key+'abc']='aaa';
- 变量名和属性名相同的可以省略
const userInfor='aaa';
const obj={
userInfor
}
相当于
const obj={
'userInfor':userInfor
}
- Array.from(arg)//类数组转数组
- 对象拷贝
let obj={a:{c:{d:1}}}
let newObj=Object.assign({},obj)
newObj.a==obj.a//true
Object.assign实现了对象的浅拷贝
let newObj2=JSON.parse(JSON.stringify(obj))
newObj2.a==obj.a//false JSON.parse(JSON.stringify(obj))实现了对象的深拷贝
class类
基本定义
- 函数原型实现
function Person(name,age){
this.name=name;//私有属性
this.age=age
}
Person.prototype.sayName=function(){//公有属性
console.log(this.name);
}
var person1=new Person('jack',19);
person1.sayName();//jack
- class实现
class Person{
constructor(name,age){//私有属性
this.name=name;
this.age=age
}
sayName(){//公有属性
console.log(this.name)
}
}
//原本的构造函数被类的constructor方法代替,其余原本需要在constructor中的方法则可以直接定义在class内
- 注意:在类中的方法,都是带有作用域的普通函数,而不是箭头函数,方法第一层所引用的this都是指向当前实例
继承语法extends 和 super(原理是this继承:把父类的私有属性和方法,克隆一份一模一样的作为子类的私有属性)
class Woker extends Person{
constructor(name,age,job){
super(name);//调用父级的构造的属性 , 此处没有继承父类的age属性
this.job=job;//添加自己的属性
}
sayWork(){//添加自己的方法
console.log(this.job+' '+this.name+' '+this.age)
}
}
var p1=new Person('');
var w1=new Worker('mmm',59,"worker");
w1.sayWork()//worker mmm undefined 所以此处没有age属性,即使传值也无效
w1.sayName()//继承了父类的公有属性
- 如果一个子类继承了父类,那么在子类的constructor构造函数中必须使用super函数调用父类的构造函数后才能在子类的constructor构造函数中使用this,否则会报错
Getter、Setter在类中的使用
//还是这个例子
class Person{
constructor(name,age,banzhaun){//私有属性
this.name=name;
this.age=age;
this.banzhaun=banzhaun
}
sayName(){//公有属性
console.log(this.name)
}
get gongqian(){
console.log(10*this.banzhaun+'块钱')
}
}
var person1=new Person('jack',19,100);
person1.gongqian//1000块钱
静态方法
- 如果在一个方法前加上static关键字,就表示该方法不会被实例继承,而是通过类来调用,实例不能调用,属于静态方法
class Person{
constructor(name,age,banzhaun){//私有属性
this.name=name;
this.age=age;
this.banzhaun=banzhaun
}
sayName(){//公有属性
console.log(this.name)
}
get gongqian(){
console.log(10*this.banzhaun+'块钱')
}
static own(){
console.log(this+'这是一个实例不能调用的方法,只有类能调用')
}
}
Person.own()//函数中zhis指向的是类不是实例,没有实例的属性
var person1=new Person('jack',19,100);
person1.gongqian//1000块钱
ES6 module
导出模块和引入模块需要相对应 否则有时会报错
导出页面 多个export 相当于导出一个对象 引入是也需要一个对象解构
导出
export let a=1;
export let b=2;
导入
import {a,b} from 'modulePath'
或者
import * as c from 'modulePath'//*代表所有的,取时候可以用c.a,c.b
导出的是一个对象的情况下
导出
export default {xxx:xxx,xxx:xxx}
导入
import xxx from 'modulePath'
引入模块(import有预解释的功能 )
1. import name from 'module-name'//从module-name引入所有接口对象
2. import * as name from 'module-name'//给引入的所有接口对象起别名
3. import {member1,member2} from 'module-name'//引入部分接口
4. import {member as alias} from 'module-name'//给引入的部分接口起别名
5. import from 'module-name'//不引入接口,只执行内部代码
导出模块
1. export 除了输出变量,还可以输出函数和类
export var sex="boy";
export var echo=function(value){
console.log(value)
}
export class Foo{
...
}
2. 导出的语句必须要有声明和赋值两部分
const foo ='bar';
export foo//报错
3.加 { } 才代表是接口不是值
var sex="boy";
var echo=function(value){
console.log(value)
}
export {sex,echo}
4.让一个值直接成为模块的内容,而无需声明
var sex="boy";
export default sex(sex不能加大括号)
模块的示例
- 1.导出一个对象
// math.js
export default math = {
PI: 3.14,
foo: function(){}
}
// app.js
import math from "./math";
math.PI
- 导出函数、变量、对象
// export Declaration
export function foo(){
console.log('I am not bar.');
}
// export VariableStatement;
export var PI = 3.14;
export var bar = foo; // function expression
// export { ExportsList }
var PI = 3.14;
var foo = function(){};
export { PI, foo };//对象简写(导出对象)
- 引入
// import { ImportsList } from "module-name"
import { PI } from "./math";//引入单个接口
import { PI, foo } from "module-name";//引入两个接口
// import IdentifierName as ImportedBinding
import { foo as bar } from "./math";//引入foo函数并起别名叫bar
bar(); // use alias bar
// import NameSpaceImport
import * as math from "./math";//引入math文件中所有接口
math.PI
math.foo()
- 把引入的接口再导出
// components.js
import Button from './Button';
import Header from './Header';
// export { ExportsList }
// Not ES6 Destructing. Not object property shorthand
export {
Button,
Header
}
// app.js
// import { ImportList }
import { Button, Header } from "./components";
Set和Map
set无序集合(无序,元素不可重复的集合对象)
//Set本身是一个构造函数,用来生成Set数据结构
const s1 = new Set();
- api:
- set.add(value):添加元素到集合内
- set.delete(value):删除集合内指定元素
- set.clear(value):清空集合内元素
- set.forEach(callback):遍历集合内所有元素,并调用callback
- set.has(value):检查集合内是否含有某元素
[2, 3, 5, 4, 5, 2, 2].forEach(x => s1.add(x));
for (let i of s1) {
console.log(i);
}
//上面代码通过add方法向set结构加入成员,结果表明Set结构不会重复添加相同的值。
- 实例
// 例一
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]
// 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5
// 例三
function divs () {
//return [...document.querySelectorAll('div')];
}
//const set = new Set(divs());
set.size // 56
// 类似于
//divs().forEach(div => set.add(div));
set.size // 56
数组去重
2 种去除数组重复成员的方法。
[...new Set(array)];
function dedupe(array) {
return Array.from(new Set(array));
}
- 向Set加入值的时候,不会发生类型转换,所以5和"5"是两个不同的值。Set内部判断两个值是否不同,使用的算法叫做“Same-value equality”,它类似于精确相等运算符(===),主要的区别是NaN等于自身,而精确相等运算符认为NaN不等于自身。
只能添加一个NaN
let set1 = new Set();
let a = NaN;
let b = NaN;
set1.add(a);
set1.add(b);
set1 // Set {NaN}
- 遍历操作
- keys():返回键名的遍历器
- values():返回键值的遍历器
- entries():返回键值对的遍历器
- forEach():使用回调函数遍历每个成员
//由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致。
let set2 = new Set(['red', 'green', 'blue']);
for (let item of set2.keys()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.values()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.entries()) {
console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
Map( 值--对--值 的映射关系 )
- 含义和基本用法
- js的对象,本质上是键值对的集合,但是传统上只能用字符串当做键 这给他的使用的带来很大的限制
- 它类似于对象,也是键值对的集合,但是 键 的范围不限于字符串 各种类型的值(包括对象)都可以当做键。
- 也就是说,Object结构提供了‘字符串---值’的对应,Map提供了‘值---值’的对应,是一种更完善的hash结构形式。如果你需要“键值对”的形式map比Object更合适
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
- 遍历
const map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
console.log([...map.keys()])
// [1, 2, 3]
console.log([...map.values()])
// ['one', 'two', 'three']
console.log([...map.entries()])
// [[1,'one'], [2, 'two'], [3, 'three']]
console.log([...map])
// [[1,'one'], [2, 'two'], [3, 'three']]