JavaScript中,我们通常说的作用域是函数作用域,使用var声明的变量,无论是在代码的哪个地方声明的,都会提升到当前作用域的最顶部,这种行为叫做变量提升(Hoisting)
也就是说,如果在函数内部声明的变量,都会被提升到该函数开头,而在全局声明的变量,就会提升到全局作用域的顶部。
let和const都能够声明块级作用域,用法和var是类似的,let的特点是不会变量提升,而是被锁在当前块中。
声明常量,一旦声明,不可更改,而且常量必须初始化赋值。
const虽然是常量,不允许修改默认赋值,但如果定义的是对象Object,那么可以修改对象内部的属性值。相同点:const和let都是在当前块内有效,执行到块外会被销毁,也不存在变量提升(TDZ),不能重复声明。
不同点:const不能再赋值,let声明的变量可以重复赋值。
如果在全局作用域使用let或者const声明,当声明的变量本身就是全局属性,比如closed。只会覆盖该全局变量,而不会替换它。
六大原始类型:String、Boolean、Null、Undefined、Number、Symbol(es6新增)。
字符串中的子串识别:
现在,使用模板字面量反撇号``。在实际开发中,这是经常都要用到的方法。
在字符串中使用反撇号,只需要加上转义符。
在模板字面量插入变量的方法:使用${params}直接插入你需要添加到字符串的位置。
函数的默认参数
默认值对arguments对象的影响:它是一个类数组对象,它存在函数内部,它将当前函数的所有参数组成了一个类数组对象。
不定参数的使用:使用...(展开运算符)的参数就是不定参数,它表示一个数组。
不定参数的使用限制:必须放在所有参数的末尾,不能用于对象字面量setter中。
展开运算符(...)
展开运算符的作用是解构数组,然后将每个数组元素作为函数参数。
有了展开运算符,我们操作数组的时候,就可以不再使用apply来指定上下文环境了。
尾调用是指在函数return的时候调用一个新的函数,由于尾调用的实现需要存储到内存中,在一个循环体中,如果存在函数的尾调用,你的内存可能爆满或溢出。
ES6中,引擎会帮你做好尾调用的优化工作,你不需要自己优化,但需要满足下面3个要求:
1、函数不是闭包
2、尾调用是函数最后一条语句
3、尾调用结果作为函数返回
下面的都是不满足的写法:
尾调用实际用途——递归函数优化
1、普通对象
2、特异对象
3、标准对象
4、内建对象
ES6针对对象的语法扩展了一下功能1、属性初始值简写
2、对象方法简写
3、属性名可计算
1、Object.is()
用来解决JavaScript中特殊类型 == 或者 === 异常的情况。
下面是一些异常情况
2、Object.assign()
mixin是一个方法,实现了拷贝一个对象给另外一个对象,返回一个新的对象。
下面是一个mixin方法的实现,这个方法实现的是浅拷贝。将b对象的属性拷贝到了a对象,合并成一个新的对象。
写这样一个mixin方法是不是很烦,而且每个项目都得引入这个方法,现在,ES6给我们提供了一个现成的方法Object.assign()来做mixin的事情。
假设要实现上面的mixin方法,你只需要给Object.assign()传入参数即可。
使用Object.assign(),你就可以不是有继承就能获得另一个对象的所有属性,快捷好用。
重复的对象字面量属性
ES5的严格模式下,如果你的对象中出现了key相同的情况,那么就会抛出错误。而在ES6的严格模式下,不会报错,后面的key会覆盖掉前面相同的key。
增强对象原型
如果你想定义一个对象,你会想到很多方法。
那么,ES6是如何在这么强大的对象上面继续增强功能呢?
1、允许改变对象原型
改变对象原型,是指在对象实例化之后,可以改变对象原型。我们使用 Object.setPrototypeOf() 来改变实例化后的对象原型。
方法的定义
ES6明确了方法的定义。
解构是从对象或者数组中提取出更小元素的过程。
赋值是对解构出来的元素进行重新赋值。
1、对象解构
2、数组解构
3、混合解构
4、解构参数
对象解构
在函数中使用解构赋值
解构是将对象或者数组的元素一个个提取出来,而赋值是给元素赋值,解构赋值的作用就是给对象或者数组的元素赋值。
在调用test()函数的时候,我们给参数设置了默认值3,如果不重新赋值,则打印出3,3,但是进行解构赋值后,将props对象的参数解构赋值给a和b,所以打印结果是{a: 1, b: 2}
下面这个例子定义了a = 3,b = 3两个变量,现在我们想修改这2个变量的值,采用解构赋值的方式可以这样做:定义一个props对象,该对象包含2个属性a和b,然后进行解构赋值,这时就能更新变量a和b的value。
嵌套对象解构
当对象层次较深时,你也可以解构出来。
数组解构比对象解构简单,因为数组只有数组字面量,不需要像对象一个使用key属性。
数组解构
你可以选择性的解构元素,不需要解构的元素就使用逗号代替。
嵌套数组解构
不定元素解构
三个点的解构赋值必须放在所有解构元素的最末尾,否则报错。
Symbol 是一种特殊的、不可变的数据类型,可以作为对象属性的标识符使用。Symbol 对象是一个 symbol primitive data type 的隐式对象包装器。
创建一个Symbol:
使用Symbol:
Symbol有点特殊,在js文件中定义的Symbol,并不能在其他文件直接共享。
ES6提供了一个注册机制,当你注册Symbol之后,就能在全局共享注册表里面的Symbol。Symbol的注册表和对象表很像,都是key、value结构,只不过这个value是Symbol值。 (key, Symbol) 语法:Symbol与类型强制转换
Set
Set是有序列表,含有相互独立的非重复值。
创建Set
看起来像个对象,那么现在我们在控制台打印一个对象,对比一下两者有什么不同。
Set.prototype.size
返回Set对象的值的个数。
Set.prototype.add(value)
在Set对象尾部添加一个元素。返回该Set对象。
Set.prototype.entries()
返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值的[value, value]数组。为了使这个方法和Map对象保持相似, 每个值的键和值相等。
Set.prototype.forEach(callbackFn[, thisArg])
按照插入顺序,为Set对象中的每一个值调用一次callBackFn。如果提供了thisArg参数,回调中的this会是这个参数。
Set.prototype.has(value)
返回一个布尔值,表示该值在Set中存在与否。
Set像数组,又像一个对象,但又不完全是。
迭代Set
Set既然提供了entries和forEach方法,那么他就是可迭代的。
for in迭代的是对象的key,而在Set中的元素没有key,使用for of来遍历:
forEach操作Set:Set本身没有key,而forEach方法中的key被设置成了元素本身。
js面试中,经常会考的一道数组去重题目,就可以使用Set集合的不可重复性来处理。经测试只能去重下面3种类型的数据。
语法:new WeakSet([iterable])
和Set的区别:
1、WeakSet 对象中只能存放对象值, 不能存放原始值, 而 Set 对象都可以.
2、WeakSet 对象中存储的对象值都是被弱引用的, 如果没有其他的变量或属性引用这个对象值, 则这个对象值会被当成垃圾回收掉. 正因为这样, WeakSet 对象是无法被枚举的, 没有办法拿到它包含的所有元素.
使用:
如果说Set像数组,那么Map更像对象。而对象中的key只支持字符串,Map更加强大,支持所有数据类型,不管是数字、字符串、Symbol等。
Map的所有原型方法:
对比Set集合的原型,Map集合的原型多了set()和get()方法,注意set()和Set集合不是一个东西。Map没有add,使用set()添加key,value,在Set集合中,使用add()添加value,没有key。
使用对象做key
Map同样可以使用forEach遍历key、value
有强Map,就有弱鸡Map。
和Set要解决的问题一样,希望不再引用Map的时候自动触发垃圾回收机制。那么,你就需要Weak Map。
Set集合可以用来过滤数组中重复的元素,只能通过has方法检测指定的值是否存在,或者是通过forEach处理每个值。
Weak Set集合存放对象的弱引用,当该对象的其他强引用被清除时,集合中的弱引用也会自动被垃圾回收机制回收,追踪成组的对象是该集合最好的使用方式。
Map集合通过set()添加键值对,通过get()获取键值,各种方法的使用查看文章教程,你可以把它看成是比Object更加强大的对象。
Weak Map集合只支持对象类型的key,所有key都是弱引用,当该对象的其他强引用被清除时,集合中的弱引用也会自动被垃圾回收机制回收,为那些实际使用与生命周期管理分离的对象添加额外信息是非常适合的使用方式。
迭代器是一种特殊对象,每一个迭代器对象都有一个next(),该方法返回一个对象,包括value和done属性。
生成器是函数:用来返回迭代器。
这个函数不是上面ES5中创建迭代器的函数,而是ES6中特有的,一个带有*(星号)的函数,同时你也需要使用到yield。
生成器的yield关键字有个神奇的功能,就是当你执行一次next(),那么只会执行一个yield后面的内容,然后语句终止运行。
yield只可以在生成器函数内部使用,如果在非生成器函数内部使用,则会报错。
函数表达式很简单,就是下面这种写法,也叫匿名函数,不用纠结。
一个对象长这样:
再次默读一遍,迭代器是对象,生成器是返回迭代器的函数。
凡是通过生成器生成的迭代器,都是可以迭代的对象(可迭代对象具有Symbol.iterator属性),也就是可以通过for of将value遍历出来。
上面的例子告诉我们生成器函数返回的迭代器是一个可以迭代的对象。其实我们这里要研究的是Symbol.iterator的用法。Symbol.iterator还可以用来检测一个对象是否可迭代:
typeof obj[Symbol.iterator] === "function"
在ES6中,数组、Set、Map、字符串都是可迭代对象。
默认情况下定义的对象(object)是不可迭代的,但是可以通过Symbol.iterator创建迭代器。
上面提到了,数组、Set、Map都是可迭代对象,即它们内部实现了迭代器,并且提供了3种迭代器函数调用。
1、entries() 返回迭代器:返回键值对
2、values() 返回迭代器:返回键值对的value
3、keys() 返回迭代器:返回键值对的key
虽然上面列举了3种内建的迭代器方法,但是不同集合的类型还有自己默认的迭代器,在for of中,数组和Set的默认迭代器是values(),Map的默认迭代器是entries()。
对象本身不支持迭代,但是我们可以自己添加一个生成器,返回一个key,value的迭代器,然后使用for of循环解构key和value。
迭代器真是无处不在啊,dom节点的迭代器你应该已经用过了。
1、传参
生成器里面有2个yield,当执行第一个next()的时候,返回value为1,然后给第二个next()传入参数10,传递的参数会替代掉上一个next()的yield返回值。在下面的例子中就是first。
2、在迭代器中抛出错误
3、生成器返回语句
生成器中添加return表示退出操作。
4、委托生成器
生成器嵌套生成器
ES6之前,我们使用异步的操作方式是调用函数并执行回调函数。
书上举的例子挺好的,在nodejs中,有一个读取文件的操作,使用的就是回调函数的方式。
那么任务执行器是什么呢?
任务执行器是一个函数,用来循环执行生成器,因为我们知道生成器需要执行N次next()方法,才能运行完,所以我们需要一个自动任务执行器帮我们做这些事情,这就是任务执行器的作用。
下面我们编写一个异步任务执行器。测试一下我们编写的run方法,我们不再需要console.log N个next了,因为run执行器已经帮我们做了循环执行操作:
迭代器是一个对象。
生成器是一个函数,它最终返回迭代器。
任务执行器一个函数(或者也叫生成器的回调函数),帮我们自动执行生成器的内部运算,最终返回迭代器。
ES6实现类非常简单,只需要类声明。推荐 babel在线测试ES6 测试下面的代码。
如果你学过java,那么一定会非常熟悉这种声明类的方式。
和ES5中使用构造函数不同的是,在ES6中,我们将原型的实现写在了类中,但本质上还是一样的,都是需要新建一个类名,然后实现构造函数,再实现原型方法。
私有属性:在class中实现私有属性,只需要在构造方法中定义this.xx = xx。
1、函数声明可以被提升,类声明不能提升。
2、类声明中的代码自动强行运行在严格模式下。
3、类中的所有方法都是不可枚举的,而自定义类型中,可以通过Object.defineProperty()手工指定不可枚举属性。
4、每个类都有一个[[construct]]的方法。
5、只能使用new来调用类的构造函数。
6、不能在类中修改类名。
类有2种表现形式:声明式和表达式。
JavaScript函数是一等公民,类也设计成一等公民。
1、可以将类作为参数传入函数。
2、通过立即调用类构造函数可以创建单例。
类支持在原型上定义访问器属性。
可计算成员是指使用方括号包裹一个表达式,如下面定义了一个变量m,然后使用[m]设置为类A的原型方法。
回顾一下上一章讲的生成器,生成器是一个返回迭代器的函数。在类中,我们也可以使用生成器方法。
静态成员是指在方法名或属性名前面加上static关键字,和普通方法不一样的是,static修饰的方法不能在实例中访问,只能在类中直接访问。
我们在写react的时候,自定义的组件会继承React.Component。
A叫做派生类,在派生类中,如果使用了构造方法,就必须使用super()。
关于super使用的几点要求:
1、只可以在派生类中使用super。派生类是指继承自其它类的新类。
2、在构造函数中访问this之前要调用super(),负责初始化this。
3、如果不想调用super,可以让类的构造函数返回一个对象。
我们可以在继承的类中重写父类的方法。
父类中的静态成员,也可以继承到派生类中。静态成员继承只能通过派生类访问,不能通过派生类的实例访问。
很好理解,就是指父类可以是一个表达式。
有些牛逼的人觉得使用内建的Array不够爽,就希望ECMA提供一种继承内建对象的方法,然后那帮大神们就把这个功能添加到class中了。
new.target通常表示当前的构造函数名。通常我们使用new.target来阻止直接实例化基类,下面是这个例子的实现。
ES5中创建数组的方式:数组字面量、new一个数组。
const arr1 = [];
const arr2 = new Array();
ES6创建数组:Array.of()、Array.from()
ES5中new一个人数组的时候,会存在一个令人困惑的情况。当new一个数字的时候,生成的是一个长度为该数字的数组,当new一个字符串的时候,生成的是该字符串为元素的数组。
这样一来,导致new Array的行为是不可预测的,Array.of()出现为的就是解决这个情况。
使用Array.of()创建的数组传入的参数都是作为数组的元素,而不在是数组长度,这样就避免了使用上的歧义。
如果说Array.of()是创建一个新数组,而Array.from()是将类数组转换成数组。
下面的例子讲的是将arguments转换成数组。arguments是类数组对象,他表示的是当前函数的所有参数,如果函数没有参数,那么arguments就为空。
映射转换:Array.from(arg1, arg2),我们可以给该方法提供2个参数,第二个参数作为第一个参数的转换。看个简单例子你就懂了。
Array.from还可以设置第三个参数,指定this。
Array.from()转换可迭代对象:这个用法只需要一个例子,数组去重。
ES6给数组添加了几个新方法:find()、findIndex()、fill()、copyWithin()。
1、find():传入一个回调函数,找到数组中符合当前搜索规则的第一个元素,返回它,并且终止搜索。
2、findIndex():传入一个回调函数,找到数组中符合当前搜索规则的第一个元素,返回它的下标,终止搜索。
3、fill():用新元素替换掉数组内的元素,可以指定替换下标范围。
4、copyWithin():选择数组的某个下标,从该位置开始复制数组元素,默认从0开始复制。也可以指定要复制的元素范围。
我们在写前端代码时,经常会对dom做事件处理操作,比如点击、激活焦点、失去焦点等;再比如我们用ajax请求数据,使用回调函数获取返回值。这些都属于异步编程。
JavaScript引擎中,只有一个主线程,当执行JavaScript代码块时,不允许其他代码块执行,而事件机制和回调机制的代码块会被添加到任务队列(或者叫做堆栈)中,当符合某个触发回调或者事件的时候,就会执行该事件或者回调函数。事件模型: 浏览器初次渲染DOM的时候,我们会给一些DOM绑定事件函数,只有当触发了这些DOM事件函数,才会执行他们。
事件模型: 浏览器初次渲染DOM的时候,我们会给一些DOM绑定事件函数,只有当触发了这些DOM事件函数,才会执行他们。
回调模式: nodejs中可能非常常见这种回调模式,但是对于前端来说,ajax的回调是最熟悉不过了。ajax回调有多个状态,当响应成功和失败都有不同的回调函数。
事件函数没有问题,我们用的很爽,问题出在回调函数,尤其是指地狱回调,Promise的出现正是为了避免地狱回调带来的困扰。
推荐你看JavaScript MDN Promise教程,然后再结合本文看,你就能学会使用Promise了。
Promise的中文意思是承诺,也就是说,JavaScript对你许下一个承诺,会在未来某个时刻兑现承诺。
react有生命周期,vue也有生命周期,就连Promise也有生命周期,现在生命周期咋这么流行了。
Promise的生命周期:进行中(pending),已经完成(fulfilled),拒绝(rejected)
Promise被称作异步结果的占位符,它不能直接返回异步函数的执行结果,需要使用then(),当获取异常回调的时候,使用catch()。
这次我们使用axios插件的代码做例子。axios是前端比较热门的http请求插件之一。
1、创建axios实例instance。
2、使用axios实例 + Promise获取返回值。
Promise构造函数只有一个参数,该参数是一个函数,被称作执行器,执行器有2个参数,分别是resolve()和reject(),一个表示成功的回调,一个表示失败的回调。
记住,Promise实例只能通过resolve或者reject函数来返回,并且使用then()或者catch()获取,不能在new Promise里面直接return,这样是获取不到Promise返回值的。
1、我们也可以使用Promise直接resolve(value)。
2、也可以使用reject(value)
3、执行器错误通过catch捕捉。
这个例子中,使用了3个then,第一个then返回 s * s,第二个then捕获到上一个then的返回值,最后一个then直接输出end。这就叫链式调用,很好理解的。我只使用了then(),实际开发中,你还应该加上catch()。
在Promise的构造函数中,除了reject()和resolve()之外,还有2个方法,Promise.all()、Promise.race()。
Promise.all():
前面我们的例子都是只有一个Promise,现在我们使用all()方法包装多个Promise实例。
语法很简单:参数只有一个,可迭代对象,可以是数组,或者Symbol类型等。
Promise.race():
语法和all()一样,但是返回值有所不同,race根据传入的多个Promise实例,只要有一个实例resolve或者reject,就只返回该结果,其他实例不再执行。
还是使用上面的例子,只是我给每个resolve加了一个定时器,最终结果返回的是3,因为第三个Promise最快执行。
派生的意思是定义一个新的Promise对象,继承Promise方法和属性。
Promise本身不是异步的,只有他的then()或者catch()方法才是异步,也可以说Promise的返回值是异步的。通常Promise被使用在node,或者是前端的ajax请求、前端DOM渲染顺序等地方。
比Promise更牛逼的异步方案
在本章你只需要了解有async这个未来的方案,推荐不会的赶紧去网上找资料学,反正我是已经在实际项目中全面开展async了。