Js基础回顾--ES6常用内容
ES6常用内容汇总
1.let const
let、const的特点:具有块级作用域,没有变量提升,不能重复声明,const在必须在声明时赋值。看一个栗子:
var声明的i在全局内有效,setTimeout在循环结束才执行;let声明的i只在当前循环中有效 //输出0,1,2,3,4 for (let i = 0; i < 5; i++) { setTimeout(() => { console.log(i); }, 100); } //输出5,5,5,5,5 for (var i = 0; i < 5; i++) { setTimeout(() => { console.log(i); }, 100); }
2.解构赋值
解构赋值是对赋值运算符的扩展,针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。
//-------------数组 //基本 let [a, b, c] = [1, 2, 3]; //a=1,b=2,c=3 //可嵌套 let [a, [[b], c]] = [1, [[2], 3]]; //a=1,b=2,c=3 //剩余运算符 let [a, ...b] = [1, 2, 3]; //a=1,b=[2,3] //-------------对象 //基本 let { foo, bar } = { foo: 'aaa', bar: 'bbb' }; //foo='aaa',bar='bbb' //可嵌套 let obj = {p: ['hello', {y: 'world'}] }; let {p: [x, { y }] } = obj; //x='hello',y='world' //剩余运算符 let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40}; //rest={c:30,d:40}
3 Symbol
ES6 数据类型除了 Number 、 String 、 Boolean 、 Objec t、 null 和 undefined ,还新增了 Symbol,表示独一无二的值。
let sy=Symbol('hello'); let sy1=Symbol('hello'); console.log(sy); //Symbol(hello) console.log(typeof(sy));//symbol console.log(sy===sy1); //false
Symbol的最大作用是作为独一无二的属性名:
//Symbol的最大作用就是做独一无二的属性名,不能使用点运算符,而是[]; //注意Symbol类型的属性名不能用for in,Object.keys(obj),Object.getOwnPropertyName(obj)获取 let name=Symbol('name'); let obj={[name]:"jack"} obj.name="111" console.log(obj[name]);//jack console.log(Object.getOwnPropertyNames(obj));//[] console.log(Object.keys(obj));//[] console.log(Object.getOwnPropertySymbols(obj));//[Symbol(name)]
4 Map和Set
Map存放键值对(key唯一),Set存放唯一值,都可以通过size属性获取元素个数。
//----------------------------------------------------map let myMap=new Map(); let student1={name:'jack',age:22}; //添加元素 myMap.set(0,"zero"); myMap.set(1,"one"); myMap.set(student1,"这是一个学生的信息"); myMap.set("mykey","myvalue"); console.log(myMap.size); //4,获取元素个数 //遍历map myMap.forEach(function(key,value){ console.log(key+"---"+value); }) console.log(myMap.get(student1));//这是一个学生的信息 console.log(myMap.has("mykey")); //true,判断mykey是否存在 //----------------------------------------------------set let mySet=new Set(); //添加元素 mySet.add(student1); mySet.add(1); mySet.add(1); mySet.add(0); console.log(mySet.size); //3,因为1虽然添加了两次但是只保留一个相同元素 console.log(mySet.has(student1))//true,判断student1是否存在 //Set实现数组去重 var set=new Set([1,1,2]) var arr=Array.from(set2)
5 数值新增
//二进制和八进制 console.log(0b11===3); //ob、oB表示二进制数 console.log(0o11===9); //0o、0O表示八进制数 //浮点数精度丢失问题,Number.EPSILON是一个常量,表示 1 与大于 1 的最小浮点数之间的差。 console.log(0.1+0.2===0.3);//false console.log(0.1+0.2-0.3<Number.EPSILON);//true
6 对象新增
1.属性表达式 ES6允许用表达式作为属性名,但是一定要将表达式放在方括号内
//属性表达式 let person = { ["say"+"hi"]:function(){ console.log('hello'); } } person.sayhi()//hello
2.扩展运算符(...),可以用于拷贝、合并对象
//拷贝对象 let person = {name: "Amy", age: 15}; let someone = { ...person }; someone; //{name: "Amy", age: 15} //合并对象 let age = {age: 15}; let name = {name: "Amy"}; let person = {...age, ...name}; person; //{age: 15, name: "Amy"} //同名属性,后边值会覆盖前边的值 let person = {name: "Amy", age: 15}; let someone = {name: "Mike", age: 17, ...person}; someone; //{name: "Amy", age: 15}
3.新方法Object.assign(target, source1, ···) 可用于用于拷贝属性
let target = {a: 1}; let object2 = {b: 2}; let object3 = {c: 3}; Object.assign(target,object2,object3); // 第一个参数是目标对象,后面的参数是源对象 target; // {a: 1, b: 2, c: 3} //assign的属性拷贝属于浅拷贝 let sourceObj = { a: { b: 1}}; let targetObj = {c: 3}; Object.assign(targetObj, sourceObj); targetObj.a.b = 2; sourceObj.a.b; // 2
7 函数新增
ES6中的函数改变有箭头函数(类似lambda表达式,和C#基本一样)、默认参数、不定参数
//默认参数 function foo(name,age=22){ console.log(name+","+age); } foo('jack');//jack,22 foo('tom',25);//tom,25 //不定参数,vals是一个数组 function getMax(...vars){ console.log(vars.length) } getMax(1,2); //2 getMax(1,2,3,4);//4
8 迭代器和for...of
迭代器是用于遍历数据结构元素的指针,它是通过一个键为Symbol.iterator 的方法来实现.。可迭代的数据结构有Array,String,Map,Set,arguments。
let items=['one','twe','three'] //获取迭代器 let it=items[Symbol.iterator]() console.log(it.next())//{value: "one", done: false} console.log(it.next())//{value: "twe", done: false} console.log(it.next())//{value: "three", done: false} console.log(it.next())//{value: undefined, done: true}
for...of的栗子
//Map遍历 let myMap=new Map(); myMap.set(0,'zero'); myMap.set(1,'one'); myMap.set(2,'twe'); myMap.set(3,'three'); for (const [key,value] of myMap) { console.log(key+'--'+value); } //Set遍历 let mySet=new Set(); mySet.add('zero'); mySet.add('one'); mySet.add('twe'); mySet.add('three'); for (const item of mySet) { console.log(item) }
9 Generator
Generator 函数可以通过 yield 关键字,把函数的执行流挂起。Generator在调用的时候不会执行函数,而是返回一个Iterator对象,调用Iterator的next()方法才会真正执行,一个简单栗子:
function *foo() { console.log(1) yield 'one'; console.log(2) yield 'twe'; console.log(3) return 'three'; } let it = foo(); console.log(it.next()); //1,{value: "one", done: false} console.log(it.next()); //2,{value: "twe", done: false} console.log(it.next()); //3,{value: "three", done: true}
Generator函数提供了消息双向传输的机制:next()的数目要比yield多一个,第一个next()方法启动Generator(生成器),并返回第一个yield表达式的值({value:xxx,done:yyy}形式);第二个next(arg)会把参数赋值给第一个yield表达式,同时返回第二个yield表达式的值;第三个next(arg)会把参数赋值给第二个yield表达式,同时返回第三个yield表达式的值,以此类推。如果Generator中没有return的话,隐式包含一个return undefined。
function* bar(num) { let x = num* (yield 'hello'); console.log('x=' + x); let y = (yield x * 3); console.log('y=' + y); return 'world' } var it = bar(5); console.log(it.next());//启动生成器,返回第一个yield的值 {value: 'hello', done: false} console.log(it.next(3));//第二个next把自己的参数赋值给第一个yield,同时返回第二个yield的值 || x=15 {value: 45, done: false} console.log(it.next(4));//第三个next把自己的参数赋值给第二个yield,同时返回第三个yield的值 || y=4 {value: 'world', done: true}
10 Promise
Promise是异步编程的一种解决方案,从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。
Promise对象有以下两个特点:
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
当新建一个Promise对象时,Promise会直接执行:
let p=new Promise((resolve,reject)=>{ setTimeout(() => { console.log("异步任务执行完成"); resolve("返回的结果数据") }, 1000); }); //控制台直接打印【异步任务执行完成】
一般我们会把Promise包裹在一个函数中,当需要使用Promise的时候调用这个函数:
let myfunc = () => { let p = new Promise((resolve, reject) => { setTimeout(() => { console.log("异步任务执行完成"); resolve("返回的结果数据") }, 1000); }); return p; } //调用函数 myfunc().then( (result)=> {console.log(result)}, (error)=> {console.log(error)} ); //控制台打印【异步任务执行完成】 //控制台打印【返回的结果数据】
then 方法接收两个函数作为参数,第一个参数是 Promise 执行成功时的回调,第二个参数是 Promise 执行失败时的回调,两个函数只会有一个被调用,then 方法将返回一个 resolved 或 rejected 状态的 Promise 对象用于链式调用。
let myfunc = () => { let p = new Promise((resolve, reject) => { setTimeout(() => { console.log("异步任务执行完成"); resolve ("返回的结果数据") }, 1000); }); return p; } //调用函数 myfunc().then( (result)=> {console.log(result); return("第一个then返回的结果")}, (error)=> {console.log(error)} ).then( (result)=> {console.log(result); return("第二个then返回的结果")}, (error)=> {console.log(error)} ).then( (result)=> {console.log(result);}, (error)=> {console.log(error);} ) //控制台打印【异步任务执行完成】 //控制台打印【返回的结果数据】 //控制台打印【第一个then返回的结果】 //控制台打印【第二个then返回的结果】
Promise.all([promises]),Promise.race([promises])用于流程控制,Promise.all表示所有的promise都执行完成后执行回调;Promise.race表示任意一个promise执行完成(无论是否成功,只要完成就行)就会执行回调
let myfunc1 = () => { let p = new Promise((resolve, reject) => { setTimeout(() => { console.log("异步任务执行完成1"); resolve("返回的结果数据") }, 1000); }); return p; } let myfunc2 = () => { let p = new Promise((resolve, reject) => { setTimeout(() => { console.log("异步任务执行完成2"); resolve("返回的结果数据2") }, 2000); }); return p; } let myfunc3 = () => { let p = new Promise((resolve, reject) => { setTimeout(() => { console.log("异步任务执行完成3"); resolve("返回的结果数据3") }, 3000); }); return p; } //all方法, 所有的Promise都执行成功后再执行then, 如果有任意一个没有执行成功就会报错 Promise.all([myfunc1(), myfunc2(), myfunc3()]) .then( (results) => { console.log(results); }) //results:["返回的结果数据", "返回的结果数据2", "返回的结果数据3"] //race方法,有一个Promise执行完成(无论成功还是失败),就会执行then Promise.race([myfunc1(), myfunc2(), myfunc3()]) .then( (result) => { console.log('success' + result) }, (error) => { console.log('error' + error) } ) //result:返回的结果数据
11 class
在ES6中,class (类)作为对象的模板被引入,先看一个ES5中定义一个构造函数的方式:
//ES5构造函数 function Person(name, age) { this.name = name; this.age = age; this.say = function () { console.log(`我是${this.name},我今年${this.age}岁了`); } } //添加/重置原形对象中的方法 Person.prototype.sayhi = function () { console.log(`${this.name}:hello!`) } //实例化一个Person let person = new Person('jack', 22); person.say(); //我是jack,我今年22岁了 person.sayhi(); //jack:hello!
ES6中的class 的本质还是 function,它可以看作一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法。
let Person = class { constructor(name, age) { this.name = name; this.age = age; } say() { console.log(`我是${this.name},我今年${this.age}岁了`); } } //添加/重置原形对象中的方法 Object.assign(Person.prototype, { sayhi() { console.log(`${this.name}:hello!`) } }) //实例化一个Person let person = new Person('jack', 22); person.say(); //我是jack,我今年22岁了 person.sayhi(); //jack:hello! //class只是一个语法糖,原型、原型链和es5中没什么区别 console.log(person.__proto__.constructor==Person);//true console.log(person.__proto__==Person.prototype)//true
class的封装和继承,使用过C#/Java的话,这个毫无难点,看一个栗子即可:
let Person = class { constructor(name, age) { this.name = name; this.age = age; } say() { console.log(`我是${this.name},我今年${this.age}岁了`); } } //Worker继承Person类 class Worker extends Person { constructor(name, age, job) { super(name, age) this.job = job } getwork() { console.log(`我的职业是${this.job}`); } }
let worker = new Worker('tom', 25, '司机'); worker.say() //我是tom,我今年25岁了 worker.getwork() //我的职业是司机 //看一下使用继承后清晰的原型链 console.log(worker.__proto__ == Worker.prototype) //true console.log(worker.__proto__.__proto__ == Person.prototype) //true console.log(worker.__proto__.__proto__.__proto__ == Object.prototype) //true console.log(worker.__proto__.__proto__.__proto__.__proto__ == null) //true,因为Object.prototype.__proto__==null,这是原型链的终点
12 async await
async 是 ES7 才有的与异步操作有关的关键字,async方法返回一个Promise对象,可以使用then添加回调,看一个简单的栗子:
async function sayhiAsync() { return "hello"; } //async函数返回一个Promise对象 console.log(sayhiAsync()) //Promise {<resolved>: "hello"} //通过then添加回调 sayhiAsync().then((result) => { console.log(`sayhiAsync的回调,sayhiAsync的结果:${result}`) }) //打印:【sayhiAsync的回调,sayhiAsync的结果:hello】
介绍await前,我们先看一个栗子:
let myfunc1 = () => { let p = new Promise((resolve, reject) => { setTimeout(() => { console.log("异步任务执行完成1"); resolve("返回的结果数据") }, 3000); }); return p; } let myfunc2 = () => { let p = new Promise((resolve, reject) => { setTimeout(() => { console.log("异步任务执行完成2"); resolve("返回的结果数据2") }, 2000); }); return p; } let myfunc3 = () => { let p = new Promise((resolve, reject) => { setTimeout(() => { console.log("异步任务执行完成3"); resolve("返回的结果数据3") }, 1000); }); return p; } async function foo() { myfunc1(); myfunc2(); myfunc3(); console.log('耗时任务都执行完了,foo方法执行完成') } foo()
执行的结果是:
这是因为JavaScript 是一门单线程语言,异步操作都会放到事件循环队列里面,等待主执行栈来执行的,Js中并没有专门的异步执行线程。如果我们想在Js中实现类似于C#,Java中的同步执行,就可以使用await了。async 函数执行时,如果遇到 await 就会先暂停执行 ,等到触发的异步操作完成后,恢复 async 函数的执行并返回解析值。修改上边的代码来实现同步执行的效果,只需要异步任务前边加上await即可:
let myfunc1 = () => { let p = new Promise((resolve, reject) => { setTimeout(() => { console.log("异步任务执行完成1"); resolve("返回的结果数据") }, 3000); }); return p; } let myfunc2 = () => { let p = new Promise((resolve, reject) => { setTimeout(() => { console.log("异步任务执行完成2"); resolve("返回的结果数据2") }, 2000); }); return p; } let myfunc3 = () => { let p = new Promise((resolve, reject) => { setTimeout(() => { console.log("异步任务执行完成3"); resolve("返回的结果数据3") }, 1000); }); return p; } async function foo() { await myfunc1(); await myfunc2(); await myfunc3(); console.log('耗时任务都执行完了,foo方法执行完成') } foo()
执行结果如下
13 模块化
ES6 引入了模块化,其设计思想是在编译时就能确定模块的依赖关系,以及输入和输出的变量。ES6 的模块化分为导出(export) @与导入(import)两个模块。注意:每个模块都有自己的上下文,模块内声明的变量都是局部变量,不会污染全局作用域。一个简单的export/import栗子,test1.js导出,test2.js导入:
//----------------------------------test1.js //变量 let myname='jack'; let myage=25; //function let myfunc=function(){ console.log(`我叫${myname},今年${myage}岁了`); } //class let myClass=class{ constructor(a,b){ this.a=a; this.b=b; } sum(){ console.log( this.a+this.b); } } //导出 export{myname,myage,myfunc,myClass} //--------------------------------------test2.js //导入 import{myname,myage,myfunc,myClass} from "./test1.js"; console.log(myname); //jack console.log(myage); //25 myfunc() //我叫jack,今年25岁了 let c = new myClass(1, 2); c.sum() //3
有时候会在导出或者导入时需要更改变量、方法、类的名字(如为了避免重名),可以使用as进行重命名
/*-----export时重命名-----*/ //test1.js let oldname= "jack"; export { oldname as newname} //test2.js import { newname} from "./test1.js"; console.log(newname);// jack /*-----import时重命名-----*/ //test1.js let oldname= "jack"; export { oldname} //test2.js import { oldname as newname} from "./test1.js"; console.log(newname);// jack
export default命令
在一个文件或模块中,export、import 可以有多个,export default 仅有一个。export方式导出时,必须使用import {}来接收,export default向外暴露的成员可以使用任意变量接收
//test1.js导出 let myname='jack'; export default myname //test2.js导入,不用使用import{myname}形式 import a from "./test1.js"; console.log(a);//jack
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
2018-09-13 Entity Framework入门教程(17)---记录和拦截数据库命令
2018-09-13 Entity Framework入门教程(16)---Enum
2018-09-13 Entity Framework入门教程(15)---DbContext追踪实体状态改变
2018-09-13 Entity Framework入门教程(14)---DbFirst下的存储过程
2018-09-13 Entity Framework入门教程(13)---EF中的高并发
2018-09-13 Entity Framework入门教程(12)--- EF进行批量添加/删除