手记

一起扫荡javascript(三)—— 看看优雅的ES6+

        ES6,相对于老版本的js,有什么新特性?又带来了怎样的优雅呢?我们一起来看看吧。

1、const 和 let,块级作用域

        ES5的时候,一个var走遍天下,带来方便的同时也带来了不少副作用。为什么这么说呢?因为在这之前关于作用域只有全局作用域跟局部作用域,其中window是全局,function是局部,而像对象,if语句,for语句结构都不是一个封闭的作用域,很容易引起变量污染或泄露,这样就有了块级作用域的出现——只要是被大括号{}包起来的都是一个块级作用域,而要使某个变量仅在某块级作用域中生效,需要有某种不同于var的声明方式,而let就这样诞生了。

//1、let:多用于声明块级作用域下的变量(可读可写)

//用let来声明一个变量,你可以试试这样:
if(1){let a = '我是a';}
console.log(a);//报错。a未定义
if(1){var b = '我是b';}
console.log(b);//正常打印

let和var的其他小区别:

for(var i = 0 ; i < 2 ; i ++){}
console.log(i);//从for块域中泄露出来了一个i
for(let k = 0 ; k < 2 ; k ++){}
console.log(k);//报错没声明k,k只在for中有效果,没有变量泄露
console.log(c);//报错,变量要先声明,再使用
let c = '我是c';

        const的效果基本和let一样,只不过当你用const声明量的时候,js在帮你定义这个量的读写权限时,默认是只读,不给写,不给人为改变,这里说下,所谓的只读是既不能修改指针地址,又不能修改指针里面的值,比如说:

const str1 = "hello, how are you!";
str1 = "hello";
console.log(str1); //报错,const声明的常量不能修改

        但是你的困惑又来了,如果我不是基本数据类型呢?而是引用类型呢?比如:

const arr1 = ["3213",21];
arr1[1] = 100;
console.log(arr1);//arr1被改变了

        你会发现,不是说值跟指针都不能修改吗?怎么又可以修改了?豆我玩呢?不,引用类型是这样的,指针是arr1,值呢?不是["3213",21],而是指向装有这个"3213"和21的内存地址,即arr1指针所对应的值(不能改变的这个值)实际上是存了一个类似于*p这样的指针,这个指针只要不改变,那就代表着arr1的值没改变,所以是OK的。而至于这个指针指向的内存的东西改变了,则是被允许的。

2、模板引擎:非常直观易读的一个jsx(html in javascript,不是很准确哈,但意思相近,比我们传统拼接字符串的方式直观太多太多)

const name = "dorsey",
country = "china",
sex = "boy";
console.log("my name is " + name + ", I am a " + sex + " from " + country + "!");
console.log(`my name is ${name}, I am a ${sex} from ${country}!`);
console.log(`                     <div>                       <ul>                           <li>${name}</li>                           <li>${sex}</li>                           <li>${country}</li>                       </ul>                   </div>`);

3、解构赋值:取json时尤为有用

let [x,y,z] = [1,"2132",4];
let {a,b,c} = {a : "dorseyCh",b : "24",c : "男"}
console.log(y);
console.log(a + ", " + b + ", " + c);//注意右侧是可以直接拿一个对象数据替换,无需一个个,一层层去取。

4、拓展运算符(...):

//使用扩展运算符(...)拷贝数组
let test =[1,2,3,4,5,[213,321,321],{name : "dorseyCh",age : "24",sex : "man"}];//[...test1]这种深拷贝数组的方式————只能深度拷贝一层的数据,类似于数组中有数组,这时拷贝到的子数组实际上还是引用
let [...test1] = test;          //[1, 2, 3, 4, 5,[20,321,321],...]
let test2 = test;               //[1, 2, 20, 4, 5,[20,321,321],...]
let test3 = this.DeepCopy(test);//[1, 2, 3, 4, 5,[213,321,321],...]     比较靠谱,另外有引入jQuery的也可以用jQuery的$.extentlet 
test4 = test.slice();           //[1, 2, 3, 4, 5,[20,321,321],...]
test[2] = 20;
test[5][0] = 20;

        拓展运算符...还有很多其他的用处,比如说:

//1、代替concat合并数组
let arr1 = [1,2,3,4,5],
arr2 = ["a","b","c"],
arr3 = ["hello","my","name","is","dorsey"];
console.log(arr1.concat(arr2,arr3));
console.log([...arr1,...arr2,...arr3]);
//2、代替arguments————function(...parmas)
//3、字符串转数组
console.log('hello'.split(""));
console.log([...'hello']);

        需要注意的是[...test]这种拷贝方式是不彻底的深拷贝,仅仅只拷贝了一层,最好的深拷贝方式是先把目标转换成基本数据类型(比如string),因为基本的数据类型赋值是值赋值而非引用赋值,拷贝后再转化成对象即可。

        上面的代码不直观的话,看看下面的附图:

拓展运算符:幂级数 **

        以前我们要实现一个幂指数一般都是调用js的Math对象中的pow函数,而ES6允许我们这么做。

//2.2、自定义随机范围随机经度函数(ES7的幂级数操作符 **,看看是不是比我们的Math.pow()函数好很多,直观很多啊?)
Random (min,max,accuracy) {
    return Math.round((Math.random() * (max - min) + min) * 10 ** accuracy) / 10 ** accuracy;
},

        **这个运算符呢,前面是底数,后面是指数,比如10的2次方,就是 10  **  2, 很直观很简便吧?

5、各类简写

比如:function不见了(注意是要作为对象的属性方法存在时才可以这么用)

又比如:键值对重名时可不写

...

6、set    与    map新的数据集合类型

        先一起看看set数据类型是怎样的吧:

//    普通的数组是这样的: ["a","b","c","d"];
//    而set数据集合是这样的:{"a","b","c","d"};
//    是一种类数组的对象,需要记住的是它是没有下标的,也就是set[0]是访问不到这个"a"的值的
//    而一般的对象是这样的:{"1":"a","2":"b","3":"c","4":"d"};//三者的区别就很明显了吧?

        set是一个类数组,它用size表示长度而非length,它有个最大的特征,它内部的元素不会有重复,既然这样,要做去重时,用个set过一遍就OK啦,再也不用很麻烦的去for,for的嵌套遍历跟比对啦。

//利用ES6新集合数据类型Set做数组去重
removeTheSame (array) {
    return Array.from(new Set(array));
},

       另一个新的集合类型map,不是数组的Array.map哦,不一样,这个是一种键值对映射,似对象而非对象,同样也部署了Iterator接口,map很像对象,但又不是对象,相对于对象的key只能是string类型来说,map内部的key是可以由各种类型充当。看看map数据:

        我们在写map时,大概是这样:

const map = new Map([["name","dorsey"],["age",24]]);
console.log(map);

7、ES6新增的一些方法:

//3.1、ES6新增字符串拓展方法:includes,startsWith,endsWith
const str = "http://www.baidu.com";
console.log(str.includes('o'));             //检查字符串是否包括某段字符
console.log(str.startsWith("http"));       //检查字符串开头
console.log(str.endsWith('com'));         //检查字符串结尾

8、promise,async/await

        首先,任何的新东西都不是凭空想象出来的,一定是有根据的,为什么会有promise?又为什么会有在promise的基础上而来的async/await?

        先看看我们遇到的问题:

        js,我们知道,单线程,异步,是这门语言最大的特点,我们平时最常用的ajax就是一个经典的异步请求,但假如说,我某个请求所需要的处理的数据刚好是需要到另一个请求请求好数据后执行后返回的数据作为基础,什么还有另一个请求是这两个请求数据的基础。。。由于原先传统的方式都是在回调的函数里操作,就使得我们不得不做各种ajax的嵌套,一个嵌套着一个,出现了回调地狱,让代码难以看懂与维护。这样就有了promise,它不是直接在回调里操作,而是先在回调里用一个承诺(比如成功处理的承诺resolve,请求失败拒绝的承诺reject,请求中的pending),只要满足某一个承诺之后,先给你个承诺,不急,再在外面的then函数中做处理。一个个的嵌套就是一个个的then(成功的resolve)和catch(失败的reject),让我们相对比较直观的完成我们的诉求。

        它的基本步骤大概可以是这样子:

        1、通过封装一个函数包装promise的一个ajax。

        2、再在then和catch(未来)里面执行逻辑

        当然了,这里还是用到了jQuery,如果你觉得你既然走到ES6789,不想再导入jQuery库,那就用自己封装一个原生的,或者用ES7的fetch吧,比如封一个原生的:

function getURLData (url) {
    return promise = new Promise( (resolve,reject) => {
    let XHR = new XMLHttpRequest();
    XHR.open('GET',url,true);
    XHR.send();XHR.onreadystatechange = () => {
        if(XHR.readyState === 4){
            XHR.status === 200 ? resolve(XHR.responseText) : reject(new Error(XHR.statusText));
        }
    }
    });
}
getURLData("src/json/checkPointMock.json").then(value => console.log(JSON.parse(value))).catch(error => console.error(error));

        再看看async/await,这个呢,是原生的promise还是不是很直观,而async/await呢,会让你在写或者看异步代码时有同步的那种直观与易读的感觉。看看下面这个:

/*=====async/await:让写异步代码像写同步一样直观=====*/
const asyncPro = async (url) => {
    try {
        let data = JSON.parse(await getURLData(url));
            console.log(data);
    }catch (err){
        console.log(err);
    }
}
asyncPro("src/json/checkPointMock.json");

关于promise和async/await呢,我做下小总结:

1、promise的then虽说解决了回调地狱,但当异步嵌套很多的时候仍然不是那么的直观

2、promise的then调试代码时不能打断点,且promise处理错误的方式是通过catch属性,所以每一个异步都有一个catch,代码看起来会相对较为冗余

3、async/await是基于promise,不是替代,只不过这种的写法让我们看起来更加的直观,调试及查看异常日志什么的更加的方便,promise链对抛出的错误展示不是很直观

4、让写异步代码跟同步代码一样简单

9、模块化

        在node中,每一个文件无论是js,资源文件等等,都是一个个的模块,因为node更多是开发服务器端,需要实现较为复杂及严谨的数据处理,存放,读写。这对于连数据类型都没做多少规定的js语言(起初仅仅是用于做网页脚本做个表单验证什么的)来说,是一个挑战,js语言里var的变量泄露,作用域提升,更使node编程变得更易出错,node为了解决这些问题,也引入了模块的概念,使得文件与文件之间隔绝,不至于相互影响,又通过模块的require的方式作为模块与模块之间的通信。ES6借鉴了这样的方式,只不过呢,不再是第三方的标准(commonJS和AMD),而是官方的标准,那ES6的模块化是怎样的呢?

        es6提供了模块化编程————export, export default, import

        这三个都有什么用呢?

        1、export,顾名思义,就是输出,也就是我们刚刚说的接口,里面包含了每一个我们想要暴露出去的变量。并且这个变量不是想调用就可以调用的,需要通过特定的es6语法(import)做引入才有用

        2、import,一样,输入的意思,相当于require(...);一种引入模块的方式

        3、export default,跟export一样是输出的意思

10、箭头函数

        其实这个差点忘了,虽说经常写,但觉得倒是稍微说说就好了,关于箭头只是function的一种替代形式,它最大的作用呢,则是调整了this指向较为不好理解的作用。类似于call和apply。

        什么意思呢?比方说:

        const fn = () => console.log("1");//这就是一个箭头函数

        function fn () {console.log("1");}//这是原来ES5以前的写法

最后呢感受感受ES6模块化吧:

看看这个index.js

下面这个是被引用的function.js,就是一个对象。

最后就是export输出,类似一个接口,供其他的调用。

好了,关于ES6的大部分都在这,还有class构造函数什么的后续补充,先到这吧。


9人推荐
随时随地看视频
慕课网APP

热门评论

1楼-沙发沙发

查看全部评论