ES6中对象新增了哪些扩展?
一、属性的简写
ES6中,当对象键名与对应值名相等的时候,可以进行简写。
方法也能够进行简写。
在函数内作为返回值,也会变得方便很多。
注意:简写的对象方法不能用作构造函数,否则会报错
二、属性名表达式
ES6 允许字面量定义对象时,将表达式放在括号内。
表达式还可以用于定义方法名。
注意,属性名表达式与简洁表示法,不能同时使用,会报错。
注意,属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object]。
三、super关键字
this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象。
四、扩展运算符的应用
注意:解构赋值必须是最后一个参数,否则会报错。
解构赋值是浅拷贝。
对象的扩展运算符等同于使用Object.assign()方法。
五、属性的遍历
ES6 一共有 5 种方法可以遍历对象的属性。
for…in:循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)
Object.keys(obj):返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名
Object.getOwnPropertyNames(obj):回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名
Object.getOwnPropertySymbols(obj):返回一个数组,包含对象自身的所有 Symbol 属性的键名
Reflect.ownKeys(obj):返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举
上述遍历,都遵守同样的属性遍历的次序规则:
首先遍历所有数值键,按照数值升序排列
其次遍历所有字符串键,按照加入时间升序排列
最后遍历所有 Symbol 键,按照加入时间升序排
六、对象新增的方法
关于对象新增的方法,分别有以下:
Object.is()
Object.assign()
Object.getOwnPropertyDescriptors()
Object.setPrototypeOf(),Object.getPrototypeOf()
Object.keys(),Object.values(),Object.entries()
Object.fromEntries()
Object.is()
严格判断两个值是否相等,与严格比较运算符(===)的行为基本一致,不同之处只有两个:一是+0不等于-0,二是NaN等于自身
Object.assign()
Object.assign()方法用于对象的合并,将源对象source的所有可枚举属性,复制到目标对象target
Object.assign()方法的第一个参数是目标对象,后面的参数都是源对象
注意:Object.assign()方法是浅拷贝,遇到同名属性会进行替换
Object.getOwnPropertyDescriptors()
返回指定对象所有自身属性(非继承属性)的描述对象
Object.setPrototypeOf()
Object.setPrototypeOf方法用来设置一个对象的原型对象
Object.getPrototypeOf()
用于读取一个对象的原型对象
Object.keys()
返回自身的(不含继承的)所有可遍历(enumerable)属性的键名的数组
Object.values()
返回自身的(不含继承的)所有可遍历(enumerable)属性的键对应值的数组
Object.entries()
返回一个对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对的数组
Object.fromEntries()
用于将一个键值对数组转为对象
ES6中函数新增了哪些扩展?
一、参数
ES6允许为函数的参数设置默认值
函数的形参是默认声明的,不能使用let或const再次声明
参数默认值可以与解构赋值的默认值结合起来使用
二、属性
函数的length属性
length将返回没有指定默认值的参数个数
rest 参数也不会计入length属性
如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了
name属性
返回该函数的函数名
如果将一个具名函数赋值给一个变量,则 name属性都返回这个具名函数原本的名字
Function构造函数返回的函数实例,name属性的值为anonymous
bind返回的函数,name属性值会加上bound前缀
三、作用域
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域
等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的
下面例子中,y=x会形成一个单独作用域,x没有被定义,所以指向全局变量x
let x = 1;
function f(y = x) {
// 等同于 let y = x
let x = 2;
console.log(y);
}
f() // 1
四、严格模式
只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错
五、箭头函数
使用“箭头”(=>)定义函数
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回
如果返回对象,需要加括号将对象包裹
注意点:
函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象
不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误
不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用rest参数代替
不可以使用yield命令,因此箭头函数不能用作 Generator 函数
怎么理解ES6中 Promise?使用场景有哪些?
Promise ,译为承诺,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大。
链式操作减低了编码难度
代码可读性明显增强
下面我们正式来认识promise:
状态
promise对象仅有三种状态
pending(进行中)
fulfilled(已成功)
rejected(已失败)
特点
对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态。
一旦状态改变(从pending变为fulfilled和从pending变为rejected),就不会再变,任何时候都可以得到这个结果。
流程
二、用法
romise对象是一个构造函数,用来生成Promise实例
const promise = new Promise(function(resolve, reject) {});
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”。
reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”。
实例方法
Promise构建出来的实例存在以下方法:
then()
then()
catch()
finally()
then()
then是实例状态发生改变时的回调函数,第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数
then方法返回的是一个新的Promise实例,也就是promise能链式书写的原因
catch
catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数
Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。
一般来说,使用catch方法代替then()第二个参数
Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应
finally()
finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作
构造函数方法
Promise构造函数存在以下方法:
all()
race()
allSettled()
resolve()
reject()
try()
all()
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例
接受一个数组(迭代对象)作为参数,数组成员都应为Promise实例
实例p的状态由p1、p2、p3决定,分为两种:
只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数
只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数
注意,如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法
race()
Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例
只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变
率先改变的 Promise 实例的返回值则传递给p的回调函数
allSettled()
Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例
只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束
resolve()
将现有对象转为 Promise 对象
参数可以分成四种情况,分别如下:
参数是一个 Promise 实例,promise.resolve将不做任何修改、原封不动地返回这个实例
参数是一个thenable对象,promise.resolve会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then()方法
参数不是具有then()方法的对象,或根本就不是对象,Promise.resolve()会返回一个新的 Promise 对象,状态为resolved
没有参数时,直接返回一个resolved状态的 Promise 对象
reject()
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected
Promise.reject()方法的参数,会原封不动地变成后续方法的参数
三、使用场景
将图片的加载写成一个Promise,一旦加载完成,Promise的状态就发生变化。
通过链式操作,将多个渲染数据分别给个then,让其各司其职。或当下个异步请求依赖上个请求结果的时候,我们也能够通过链式操作友好解决问题。
通过all()实现多个请求合并在一起,汇总所有请求结果,只需设置一个loading即可。
通过race可以设置图片请求超时。
怎么理解ES6中 Generator?使用场景有哪些?
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同
回顾下上文提到的解决异步的手段:
回调函数
promise
那么,上文我们提到promsie已经是一种比较流行的解决异步方案,那么为什么还出现Generator?甚至async/await呢?
Generator函数
执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态
形式上,Generator 函数是一个普通函数,但是有两个特征:
function关键字与函数名之间有一个星号
函数体内部使用yield表达式,定义不同的内部状态
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
Generator 函数会返回一个遍历器对象,即具有Symbol.iterator属性,并且返回给自己。
通过yield关键字可以暂停generator函数返回的遍历器对象的状态。
上述存在三个状态:hello、world、return
通过next方法才会遍历到下一个内部状态,其运行逻辑如下:
遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式
如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
如果该函数没有return语句,则返回的对象的value属性值为undefined
回顾之前展开异步解决的方案:
回调函数
Promise 对象
generator 函数
async/await
这里通过文件读取案例,将几种解决异步的方案进行一个比较:
回调函数
所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,再调用这个函数
fs.readFile('/etc/fstab', function (err, data) {
if (err) throw err;
console.log(data);
fs.readFile('/etc/shells', function (err, data) {
if (err) throw err;
console.log(data);
});
});
Promise
Promise就是为了解决回调地狱而产生的,将回调函数的嵌套,改成链式调用
generator
yield表达式可以暂停函数执行,next方法用于恢复函数执行,这使得Generator函数非常适合将异步任务同步化
async/await
将上面Generator函数改成async/await形式,更为简洁,语义化更强了
区别:
通过上述代码进行分析,将promise、Generator、async/await进行比较:
promise和async/await是专门用于处理异步操作的。
Generator并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署Interator接口…)。
promise编写代码相比Generator、async更为复杂化,且可读性也稍差。
Generator、async需要与promise对象搭配处理异步情况。
async实质是Generator的语法糖,相当于会自动执行Generator函数。
async使用上更为简洁,将异步代码以同步的形式进行编写,是处理异步编程的最终方案。
怎么理解ES6中Proxy?使用场景有哪些?
定义: 用于定义基本操作的自定义行为。
本质: 修改的是程序默认形为,就形同于在编程语言层面上做修改,属于元编程(meta programming)。
元编程(Metaprogramming,又译超编程,是指某类计算机程序的编写,这类计算机程序编写或者操纵其它程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作
Proxy 亦是如此,用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
Proxy为 构造函数,用来生成 Proxy 实例。
var proxy = new Proxy(target, handler)
target表示所要拦截的目标对象(任何类型的对象,包括原生数组,函数,甚至另一个代理))。
handler通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
关于handler拦截属性,有如下:
get(target,propKey,receiver):拦截对象属性的读取
set(target,propKey,value,receiver):拦截对象属性的设置
has(target,propKey):拦截propKey in proxy的操作,返回一个布尔值
deleteProperty(target,propKey):拦截delete proxy[propKey]的操作,返回一个布尔值
ownKeys(target):拦截Object.keys(proxy)、for…in等循环,返回一个数组
getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象
defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc),返回一个布尔值
preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值
getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象
isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值
setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值
apply(target, object, args):拦截 Proxy 实例作为函数调用的操作
construct(target, args):拦截 Proxy 实例作为构造函数调用的操作
Reflect
若需要在Proxy内部调用对象的默认行为,建议使用Reflect,其是ES6中操作对象而提供的新 API
基本特点:
只要Proxy对象具有的代理方法,Reflect对象全部具有,以静态方法的形式存在
修改某些Object方法的返回结果,让其变得更合理(定义不存在属性行为的时候不报错而是返回false)
让Object操作都变成函数行为
Proxy其功能非常类似于设计模式中的代理模式,常用功能如下:
- 拦截和监视外部对对象的访问
- 降低函数或类的复杂度
- 在复杂操作前对操作进行校验或对所需资源进行管理
怎么理解ES6中Module?使用场景有哪些
模块,(Module),是能够单独命名并独立地完成一定功能的程序语句的集合(即程序代码和数据结构的集合体)。
两个基本的特征:外部特征和内部特征
外部特征是指模块跟外部环境联系的接口(即其他模块或程序调用该模块的方式,包括有输入输出参数、引用的全局变量)和模块的功能
内部特征是指模块的内部环境具有的特点(即该模块的局部数据和程序代码)
为什么需要模块化
代码抽象
代码封装
代码复用
依赖管理
如果没有模块化,我们代码会怎样?
变量和方法不容易维护,容易污染全局作用域。
加载资源的方式通过script标签从上到下。
依赖的环境主观逻辑偏重,代码较多就会比较复杂。
大型项目资源难以维护,特别是多人合作的情况下,资源的引入会让人奔溃。
ES6模块内部自动采用了严格模式,这里就不展开严格模式的限制,毕竟这是ES5之前就已经规定好
模块功能主要由两个命令构成:
export:用于规定模块的对外接口
import:用于输入其他
export
一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量模块提供的功能
import
使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块
怎么理解ES6中 Decorator ?使用场景有哪些?
Decorator,即装饰器,从名字上很容易让我们联想到装饰者模式。
简单来讲,装饰者模式就是一种在不改变原类和使用继承的情况下,动态地扩展对象功能的设计理论。
ES6中Decorator功能亦如此,其本质也不是什么高大上的结构,就是一个普通的函数,用于扩展类属性和类方法。
类的装饰
当对类本身进行装饰的时候,能够接受一个参数,即类本身。
类属性的装饰
当对类属性进行装饰的时候,能够接受三个参数:
类的原型对象
需要装饰的属性名
装饰属性名的描述对象
Javascript字符串的常用方法有哪些?
concat
用于将一个或多个字符串拼接成一个新字符串。
slice()
substr()
substring()
这三个方法都返回调用它们的字符串的一个子字符串,而且都接收一或两个参数。
let stringValue = "hello world";
console.log(stringValue.slice(3)); // "lo world"
console.log(stringValue.substring(3)); // "lo world"
console.log(stringValue.substr(3)); // "lo world"
console.log(stringValue.slice(3, 7)); // "lo w"
console.log(stringValue.substring(3,7)); // "lo w"
console.log(stringValue.substr(3, 7)); // "lo worl"
trim()、trimLeft()、trimRight()
删除前、后或前后所有空格符,再返回新的字符串。
repeat()
接收一个整数参数,表示要将字符串复制多少次,然后返回拼接所有副本后的结果。
padStart()、padEnd()
复制字符串,如果小于指定长度,则在相应一边填充字符,直至满足长度条件。
toLowerCase()、 toUpperCase()
大小写转化。
charAt()
返回给定索引位置的字符,由传给方法的整数参数指定。
indexOf()
从字符串开头去搜索传入的字符串,并返回位置(如果没找到,则返回 -1 )。
startWith()、includes()
从字符串中搜索传入的字符串,并返回一个表示是否包含的布尔值。
split
把字符串按照指定的分割符,拆分成数组中的每一项
let str = "12+23+34"
let arr = str.split("+") // [12,23,34]
Javascript 中的类型转换机制
JS 中有六种简单数据类型:undefined、null、boolean、string、number、symbol(BigInt处理stage-4阶段,不考虑),以及引用类型:object。
常见的类型转换有:
强制转换(显示转换)
自动转换(隐式转换)
显示转换,即我们很清楚可以看到这里发生了类型的转变,常见的方法有:
Number():将任意类型的值转化为数值
parseInt():parseInt相比Number,就没那么严格了,parseInt函数逐个解析字符,遇到不能转换的字符就停下来。
String():可以将任意类型的值转化成字符串
Boolean():可以将任意类型的值转为布尔值,转换规则如下:
在隐式转换中,我们可能最大的疑惑是 :何时发生隐式转换?
我们这里可以归纳为两种情况发生隐式转换的场景:
比较运算(==、!=、>、<)、if、while需要布尔值地方
算术运算(+、-、*、/、%)
除了上面的场景,还要求运算符两边的操作数不是同一类型
自动转换为布尔值
在需要布尔值的地方,就会将非布尔值的参数自动转为布尔值,系统内部会调用Boolean函数
可以得出个小结:
undefined
null
false
+0
-0
NaN
“”
除了上面几种会被转化成false,其他都换被转化成true
自动转换成字符串
遇到预期为字符串的地方,就会将非字符串的值自动转为字符串
具体规则是:先将复合类型的值转为原始类型的值,再将原始类型的值转为字符串
常发生在+运算中,一旦存在字符串,则会进行字符串拼接操作
自动转换成数值
除了+有可能把运算子转为字符串,其他运算符都会把运算子自动转成数值
null转为数值时,值为0 。undefined转为数值时,值为NaN。
== 和 ===有什么区别,分别在什么情况使用?
等于操作符用两个等于号( == )表示,如果操作数相等,则会返回 true
前面文章,我们提到在JavaScript中存在隐式转换。等于操作符(==)在比较中会先进行类型转换,再确定操作数是否相等
全等操作符由 3 个等于号( === )表示,只有两个操作数在不转换的前提下相等才返回 true。即类型相同,值也需相同。
undefined 和 null 与自身严格相等。
相等操作符(==)会做类型转换,再进行值的比较,全等运算符不会做类型转换。
null 和 undefined 比较,相等操作符(==)为true,全等为false。
JavaScript中的原型,原型链分别是什么?
JavaScript 常被描述为一种基于原型的语言——每个对象拥有一个原型对象
当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾
准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非实例对象本身
下面举个例子:
函数可以有属性。 每个函数都有一个特殊的属性叫作原型prototype
可以看到,原型对象有一个自有属性constructor,这个属性指向该函数,如下图关系展示
原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法
在对象实例和它的构造器之间建立一个链接(它是__proto__属性,是从构造函数的prototype属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法。
__proto__作为不同对象之间的桥梁,用来指向创建它的构造函数的原型对象的。
总结:
一切对象都是继承自Object对象,Object 对象直接继承根源对象 null
一切的函数对象(包括 Object 对象),都是继承自 Function 对象
Object 对象直接继承自 Function 对象
Function对象的__proto__会指向自己的原型对象,最终还是继承自Object对象