Promise
首先来说一下应该明白Promise是做什么的,JS是一门典型的异步单线程的编程语言,单线程就不说了,异步如何理解呢?异步编程可以理解成在执行指令之后不能马上得到结果,而是继续执行后面的指令,等到特定的事件触发之后才得到想要的结果。
也可以和同步对比理解,同步就是一步步执行指令,而异步在遇到特殊任务(异步任务)时,并不会等待该任务执行完成之后再去执行下一步,而是跳过等待,先去执行下一步任务,等到某个时机该特殊任务(异步任务)执行完成之后再返回来对其进行处理。
比如:
console.log(1);
setTimeout(()=>{
console.log(2)
},1000)
console.log(3);
该段代码不会按照顺序输出1,2,3而是先输出1,3之后再输出2。你可能会说,最后输出2是因为它是延时1s之后才输出的。那么请看以下代码:
console.log(1);
setTimeout(()=>{
console.log(2)
},0)
console.log(3);
以上代码输出顺序依旧是1,3,2。实际上并不是因为延时导致的2最后输出而是因为这就是一个典型的异步任务,它是在同步任务之后才去执行的。
说起异步肯定首先会想到ajax
,因为ajax天生就是异步操作,先看ajax伪代码
var xhr = new XMLHttpRequest();
xhr.open(method,url);
xhr.send();
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
document.getElementById('app').innerHTML = xhr.responseText
}
}
由于ajax
是异步操作,所以我们只能将从服务器获取数据之后再渲染页面的整个过程放在ajax
请求成功之后监听函数onreadystatechange
里,这样做有些糟糕,会导致代码比较乱。
因此有一种操作对于异步编程来说比较优雅就是回调函数。
还拿ajax举例:
//简易封装ajax
function getAppJson(cb){
var xhr = new XMLHttpRequest();
xhr.open(method,url);
xhr.send();
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
cb(responseText)
}
}
}
//回调函数
function initHtml(val){
document.getElementById('app').innerHTML = val
}
// 将initHtml函数作为参数传入getAppJson中并在xhr.onreadystatechange 方法中执行并把后台所得数据作为参数传递给initHtml;
getAppJson(initHtml);
上述代码使用回调函数优雅的解决了ajax异步操作。
上述代码简化之后就是下面这个样子的:
function app(cb){
cb(1)
}
function init(val){
console.log(val) //1
}
app(init)
回调函数是一种很好地异步编程思想。但是有一种被人们称为回调地狱的情况让人们不得不使用Promise
去替代回调函数。
回调地狱就是回调函数里嵌套回调函数,比如:
var sayhello = function (name, callback) {
console.log(name);
callback();
}
sayhello("first", function () {
sayhello("second", function () {
sayhello("third", function () {
console.log("end");
});
});
});
// first second third end
比如当第一个ajax
获取到的id需要作为第二个ajax的请求参数使用这样也会形成回调地狱。
为了解决回调地狱的恐怖,因此呢,Promise便横空出世。
Promise 是异步编程的一种解决方案,比传统的回调函数更合理,更强大。在ES6中被标准化。
Promise的好处就是链式调用(chaining)
Promise的使用
- Promise的状态
Promise的状态表示此时异步执行的状态。Promise一共有三种状态:
- pending:初始状态
- fulfilled:操作成功状态
- rejected:操作失败状态
特点:
Promise三个状态不受外界影响,一旦状态改变,就不会再发生变化,Promise对象的状态改变只有两种可能,从pening变为fulfilled和从pending变为rejected.
- 基本用法
ES6规定,Promise对象是一个构造函数,用来生成Promise实例。
let promise = new Promise((resolve,reject)=>{
//...异步代码
if(/* 异步成功 */){
resolve(value)
}else{
reject(error)
}
})
Promise构造函数接收一个函数作为参数,该函数有两个参数分别为resolve
和reject
。它们是两个函数,由JavaScript引擎提供,不用自己部署。
其中 resolve
的作用是将Promise对象的状态从“未完成”变为“成功”(即从pending变为resolved),在异步操作成功时调用,并将异步操作的结果作为参数传递出去。
reject
函数的作用是将Promise对象的状态从“未完成”变为“失败”(即从pending变为rejected),在异步操作失败时调用,并将异步操作报出的错误作为参数传递出去。
var flag = false;
var p = new Promise((resolve,reject)=>{
if(flag){
resolve(1)
}else{
reject(2)
}
})
p.then((val)=>{
console.log(val,'then')
}).catch((val)=>{
console.log(val,'catch') //2 "catch"
})
上述代码以flag模拟异步结果成功或失败,当成功时(flag为true),调用resolve并将结果1作为参数传递出去,Promise的实例化p调用Promise原型上的方法then。
then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为rejected时,调用 then 的 onrejected 方法,默认一般都只写一个参数。
当执行resolve时可以在then的函数参数中接收从resolve中传递过来的参数
当执行reject时可以在catch的函数参数中接收从reject中传递的参数。
由于Promise.prototype.then(onFulfilled, onRejected)
和Promise.prototype.catch(onRejected)
都会返回一个新的Promise对象,所以可以实现链式调用。
比如:用Promise实现两个数相加,再拿两个相加的数的结果减去一个数
function Add(a,b){
return new Promise((resolve,reject)=>{
resolve(a+b)
})
}
function Min(c){
return new Promise((resolve,reject)=>{
resolve(c-1)
})
}
Add(1,2).then((val)=>{
return Min(val)
})
.then((val)=>{
console.log(val)
})
以上可以很直观的看出Promise的链式调用。
Promise原型上还有一个常用方法叫finally
.
var flag = false;
var p = new Promise((resolve,reject)=>{
if(flag){
resolve(1)
}else{
reject(2)
}
})
p.then((val)=>{
console.log(val,'then')
}).catch((val)=>{
console.log(val,'catch')
}).finally(()=>{
console.log(12345)
})
finally
方法接收一个参数是一个函数,该函数无参数,该方法表示无论最终Promise对象执行resolve
还是reject
. finally
都会执行,它表示Promise对象执行结束了。
- Promise 方法
Promise.all(iterable)
const p = Promise.all([p1,p2,p3])
let p1 = new Promise((resolve,reject)=>{
resolve(1)
})
let p2 = new Promise((resolve,reject)=>{
resolve(2)
})
let p3 = new Promise((resolve,reject)=>{
resolve(3)
})
Promise.all([p1,p2,p3]).then((val)=>{
console.log(val) //[1,2,3]
})
Promise.all()方法接收一个数组作为参数,参数也可以不是数组,但必须具有迭代器接口,且返回的每个成员都是Promise实例。
p的状态有p1,p2,p3决定,分成两种情况,
(1)当p1,p2,p3的状态都为fulfilled,p的状态才会变成fulfilled,此时p1,p2,p3的返回值组成一个数组,传递给p的then方法第一个函数参数的参数。
(2)当p1,p2,p3中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的catch方法函数参数的参数。
如下:
let p1 = new Promise((resolve,reject)=>{
resolve(1)
})
let p2 = new Promise((resolve,reject)=>{
reject(2)
})
let p3 = new Promise((resolve,reject)=>{
resolve(3)
})
Promise.all([p1,p2,p3]).then((val)=>{
console.log(val)
}).catch((err)=>{
console.log(err) //2
})
总结:只有Promise.all()中所有的参数状态都是fulfilled,p的状态即为成功,当其中有一个rejected,p的状态即为失败。
Promise.race()
Promise.race(iterable)
Promise.race()方法同样将多个Promise实例包装成一个新的Promise实例,当iterable参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。
let p1 = new Promise((resolve,reject)=>{
resolve(1)
})
let p2 = new Promise((resolve,reject)=>{
resolve(2)
})
let p3 = new Promise((resolve,reject)=>{
resolve(3)
})
Promise.race([p1,p2,p3]).then((val)=>{
console.log(val) //1
}).catch((err)=>{
console.log(err)
})
由于p1排在第一个因此率先执行,所以当p1执行完成之后就直接停止执行p2,p3。
总结:race顾名思义比赛,执行最快的那个首先执行被返回,此时Promise状态已被确定。
Promise.allSettled()
Promise.allSettled(iterable);
类似于Promise.all()方法接受一个可迭代对象。不同的是它将所有的执行结果都会返回回来,不会像Promise.all()一样造成短路,成功时,它会返回成功的状态和返回值,失败时,会返回失败状态和失败原因。也就是说,当每个Promise执行完成之后不管成功与否都会拿到最终的所有结果。
let flag = true;
let p = new Promise((resolve, reject)=>{
if(!flag){resolve('p')}else{reject('err')}
})
let p1 = new Promise((resolve, reject)=>{
if(flag){resolve('p1')}else{reject('err')}
})
let p2 = new Promise((resolve, reject)=>{
if(flag){resolve('p2')}else{reject('err')}
})
Promise.allSettled([p,p1,p2]).then((data)=>{
console.log(data)
})
结果:
0: {status: "rejected", reason: "err"}
1: {status: "fulfilled", value: "p1"}
2: {status: "fulfilled", value: "p2"}
可参考: Promise 中的三兄弟 .all(), .race(), .allSettled()
Promise.resolve(value)
将一个普通的value转换成Promise对象。
返回一个状态由给定value决定的Promise对象。如果该值是thenable(即,带有then方法的对象),返回的Promise对象的最终状态由then方法执行决定;否则的话(该value为空,基本类型或者不带then方法的对象),返回的Promise对象状态为fulfilled,并且将该value传递给对应的then方法。
Promise.resolve("foo")
//等价于
new Promise(resolve=>resolve('foo'))
最后再来看一个Promise封装的Ajax:
function getJson(){
return new Promise((resolve,reject)=>{
var xhr = new XMLHttpRequest();
xhr.open(method,url);
xhr.send();
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
resolve(xhr.responseText)
}
}
})
}
getJson.then((data)=>{
document.getElementById("app").innerHTML = data;
})
面试题:
Promise.all()中有一个异步抛出异常,最先抛出的异常会被.then的第二个参数或者.catch补捕获。并终止程序。并且获取不到Promise.all()其他正常的结果。那么如何获取成功的请求结果呢?
(1). 在异步请求中定义自己的catch方法,一旦它被rejected,并不会触发Promise.all()的catch方法。
var flag = true;
var p1 = new Promise((resolve,reject)=>{ //p1resolved
if(flag){
resolve("p1")
}else{
reject("p1 err")
}
})
var p2 = new Promise((resolve,reject)=>{ //p2rejected
if(!flag){
resolve("p2")
}else{
reject("p2 err")
}
}).then((data)=>{
console.log(data,'p2 then')
}).catch((data)=>{
console.log(data,'p2 catch') //p2 err p2 catch
})
var p3 = new Promise((resolve,reject)=>{ //p3resolved
if(flag){
resolve("p3")
}else{
reject("p3 err")
}
})
Promise.all([p1,p2,p3]).
then((data)=>{
console.log(data); // ["p1", undefined, "p3"]
})
.catch((data)=>{
console.log(data)
})
上面代码中,p2会rejected,但是p2有自己的catch方法,该方法返回的是一个新的Promise实例,p2实际指向的是这个实例。该实例执行完catch方法后,也会resolved,导致Promise.all()方法参数里面的三个实例都会resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回到函数。
上面代码Promise.all()的then中输出["p1", undefined, "p3"]
从中可以获取到执行成功的p1结果,p3结果。
如果p2没有自己的catch方法,那么就会调用Promise.all()的catch方法。
(2). 在Promise.all()的所有参数中无论resolved还是rejected都执行resolve.
var flag = true;
var p1 = new Promise((resolve,reject)=>{
if(flag){
resolve("p1")
}else{
reject("p1 err")
}
})
var p2 = new Promise((resolve,reject)=>{
if(!flag){
resolve("p2")
}else{
resolve("error")
}
})
var p3 = new Promise((resolve,reject)=>{
if(flag){
resolve("p3")
}else{
reject("p3 err")
}
})
Promise.all([p1,p2,p3]).
then((data)=>{
console.log(data); // ["p1", "error", "p3"]
})
.catch((data)=>{
console.log(data)
})
这个很好理解,在p2中无论成功失败都走resolve,这样就顺理成章的走到了Promise.all()的then中,此时可以获取到所有的结果,从中获取返回正常的结果。
上面代码结果是["p1", "error", "p3"]
,从中过滤掉失败的就好了。
面试题:利用Promise实现一个Promise.all()方法
思路:将传入数组在Promise实例中遍历执行,将then的结果存入数组,设一个变量来记录调用了几次then,当调用then的次数等于传入数组的length时,那么就在Promise实例的then方法中调用resolve方法,否则就直接将捕获到的错误reject出去。
Promise.myAll = function(arr){
let result = [];
let len = 0;
return new Promise((resolve, reject) => {
arr.forEach((promise) => {
promise.then((res) => {
result.push(res);
len ++;
if(len === arr.length) {
resolve(result);
}
}, (e) => {
reject(e);
})
})
})
}
let a = new Promise((resolve, reject) => {
resolve(1);
});
let b = Promise.resolve(2);
Promise.myAll([a, b]).then((res) => {
console.log(res, 'ok');
}, (err) => {
console.log(err, 'err')
})
参考文章:
MDN Promise
深入理解 ES6 Promise
async/await
async/await和Promise一样目的都是为了异步调用的“扁平化”。async/await是非常好用的语法糖。可以认为是基于Promise的针对异步更优雅的解决方案。
用法:
- async用于申明一个函数是异步的,它的返回值是一个Promise对象。如果在函数中return 一个直接量,async会把这个直接;量通过Promise.resolve()封装成Promise对象。
//在函数前加上async关键字,表明该函数是异步函数
async function testAsync(){
return "async"
}
const result = testAsync();
console.log(result);//Promise {<resolved>: "async"}
result.then((data)=>{
console.log(data) //async
})
- 配合await
await只能出现在async函数中。
await表示等待。等待异步方法执行结束。
async函数返回一个Promise对象,所以await等待async函数的返回值。也可以等待任意表达式的结果。
例如:
function func(){ //普通函数
return "hello world"
}
async function testAsync(){ //async函数
return Promise.resolve("hello async")
}
async function test(){
const result1 = await func();
const result2 = await testAsync();
console.log(result1,result2)
}
test(); // hello world hello async
await等待的值为一个Promise对象,或者其它值
await 是个运算符,用于组成表达式,await表达式的运算结果取决于它等的东西。
如果它等到的不是一个Promise对象,那await表达式的运算结果就是它等到的东西。
如果它等到的是一个Promise对象,那么await就会阻塞后面代码,等着Promise对象状态改变,比如resolve,然后得到resolve的值,作为await表达式的运算结果。
- async/await 写法与Promise比较
//Promise
function setTime(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve("setTime")
},1000)
})
}
setTime().then((data)=>{
console.log(data); // setTime 1s之后会输出
})
//async/await
function setTime(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve("setTime")
},1000)
})
}
async function test(){
const result = await setTime(); // 该段代码执行结束之前后面的代码是不会执行的。
console.log(1); // 1 1s之后输出
console.log(result) // setTime 1s之后输出
}
test();
怎么说呢,async/await就是一个语法糖,看起来貌似很难,很高深,其实用起来了也就慢慢熟悉了。感觉async看起来更直观一些吧。
思考题:
async function foo() {
console.log('foo')
}
async function bar() {
console.log('bar start')
await foo()
console.log('bar end')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout')
}, 0)
bar();
new Promise(function (resolve) {
console.log('promise executor')
resolve();
}).then(function () {
console.log('promise then')
})
console.log('script end')
展开(spread)运算符和剩余(Rest)运算符
展开运算符用三个点(...
)表示,可以将数组转为逗号分隔的参数序列。化少为多。
var arr = [1,2,3]
console.log(...arr); // 1 2 3
可用于数组合并
var arr1 = [1,2,3]
var arr2 = [4,5,6]
var arr3 = [...arr1, ...arr2];
console.log(arr3); //[1,2,3,4,5,6]
可用于对象合并
var obj1 = {a:1,b:2}
var obj2 = {c:3,d:4}
var obj3 = {...obj1,...obj2}
console.log(obj3)//{a:1,b:2,c:3,d:4}
剩余运算符也是用三个点表示(...
),剩余运算符会将多余元素收集压缩成一个单一的元素。
可以用来表示形参。
function func(a,...args){
console.log(a); //1
console.log(args); //[2,3,4]
}
func(1,2,3,4);
解构赋值:
const [num,...other] = [1,2,3,4,5];
console.log(num);// 1
console.log(other); [2,3,4,5]
纯函数
定义:一个函数的返回结果只依赖他的参数,并且在执行过程中没有副作用,我们就把这个函数叫做纯函数。
由定义可知纯函数有两个重要点:
- 函数的返回结果只依赖它的参数
- 函数执行过程中没有副作用
var a = 1;
function foo (b){
return a+b
}
foo(2) // 3
foo函数不是一个纯函数,因为它返回的结果依赖外部变量a,我们在不知道a的值得情况下,并不能保证foo(2)
的返回值是3。虽然foo函数的代码实现并没有变化,传入的参数并没有变化,但他的返回值却是不可预料的,因为函数依赖的外部值a是不可预料的,它是1,也可能在函数foo执行之前在其他逻辑中被修改。
修改一下函数foo的逻辑:
var a = 1;
function foo (x,b){
return x+b
}
foo(1,2) // 3
现在foo的返回结果只依赖于它的参数x和b,foo(1,2)
的返回结果永远是3,不管外部代码怎么变化,foo(1,2)
永远是3。只要foo代码不改变,只要foo代码不改变,你传入的参数是确定的,那么foo(1,2)
的值永远是可预料的。
这就是纯函数的第一个条件,一个函数的返回结果只依赖于它的参数。
再来看看第二个特点,函数执行过程没有副作用。
一个函数执行过程中对函数外部产生了可观察的变化,那么就说这个函数是有副作用的。
var a = 1;
var obj = {x:2}
function foo (obj,b){
obj.x = b
return obj.x+b
}
foo(obj ,3) // 6
obj // {x:3}
对象obj的属性x默认为2,foo执行后obj.x成为了foo函数的第二个参数,因此obj最终由{x:2}
变为了{x:3}
,因此foo函数的执行对外部的obj产生了影响,它产生了副作用,因为它修改了外部传进来的对象,因此现在它不是一个纯函数。
function foo(b){
const obj = {x:1}
obj.x = 2;
return obj.x + b
}
以上函数foo虽然内部修改了变量obj,但obj是内部变量,外部程序观察不到,修改obj并不会产生外部可观察变化,这个函数没有副作用,因此它是一个纯函数。
除了修改外部的变量,一个函数在执行过程中还有很多方式产生外部可观察的变化,比如说调用DOM API修改页面,或者发送Ajax请求,调用BOM API等,甚至使用console.log()往控制台打印数据也是副作用。
纯函数很严格,除了计算数据以外什么都不能干,计算的时候还不能依赖除了函数参数以外的数据。
总结:一个函数的返回结果只依赖于他的参数,并且在执行过程中没有副作用,那么我们就称这个函数为纯函数。
纯函数的好处就是它非常靠谱,执行一个纯函数,它的执行结果一般都是在你的预料之中,不必担心一个纯函数会干什么坏事,他不会产生不可预料的行为,也不会对外部产生影响,不管何时何地,你给它什么,它就吐出什么。
纯函数也是函数式编程的一个很重要的概念,很多类库,框架也都有使用到,比如redux,react高阶组件因此需要了解其概念。
严格模式
使用:"use strict",可以在整个代码块前加“use strict”使用,也可以只在函数中局部添加“use strict”去使用。
特点:
- 变量必须声明才可使用。
- 创设eval作用域,除了全局作用域,函数作用域外,新增eval作用域。
- with()被禁用,with语句用于设置代码在特定对象中的作用域。
- caller/callee被禁用。
- delete使用在var 声明的变量活挂在window的变量上会报错。
- delete不可删除属性的对象时会报错。
- 对一个对象的只读属性进行赋值会报错。
- 对象有重名属性将报错。
- 函数有重名的参数会报错。
- arguments严格定义为参数,不再与形参绑定。
- 函数中的this不再指向window。
高阶函数
什么是高阶函数:
- 如果一个函数的参数是一个函数(回调函数)
- 如果一个函数的返回值是一个函数,当前这个函数也是一个高阶函数
应用场景:扩展业务代码
function Eat(a,b){ //核心业务代码
console.log(a,b)
}
Function.prototype.before = function(callback){ //高阶函数
return (...args)=>{ //使用rest运算符接收
callback();
this(...args); //使用展开运算符传入
}
}
let beforeEat = Eat.before(function(){ //自己扩展业务代码
console.log("before eat")
})
beforeEat("666","999") //传参
函数柯里化
例子:判断数据类型
- typeof 不能判断对象类型
- constructor 判断该数据是由谁构造出来
- instanceof 判断谁是谁的实例proto
- Object.prototype.toString.call() 缺陷不能细分谁是谁的实例
function isType(value,type){
return Object.prototype.toString.call(value) === `[object ${type}]`
}
//调用
isType([],"Array") // 判断数据 [] 的类型是否似乎数组
isType("","Array") // 判断数据 "" 的类型是否是数组
上述实现有缺陷,就是每次判断数据时都需要重复去指定type类型。
接下来就是一个函数柯里化的简单应用
function isType(type){
return function(value){
return Object.prototype.toString.call(value) === `[object ${type}]`
}
}
const isArray = isType("Array");
const isString = isType("String");
console.log(isArray([])); //true
console.log(isArray("")); //false
console.log(isString("")); //true
//相当于就是这么写
isType("Array")([])
这样是不是比较高大上呢 !!!
那现在来自己封装一个可以实现柯里化函数的方法
首先分析一下,举个例子,看下面代码
function add(a,b,c,d){
return a+b+c+d
}
假如我们定义了一个函数Curry
这个函数可以将普通函数转变成柯里化函数。
正常调用add(1,2,3,4)
,用柯里化的方式应该是这样的Curry(add)(1)(2)(3)(4)
通过我们自己封装的柯里化函数的方法,将原函数传进去之后就可以化多参为单参,然后依次去调用
那么Curry的返回值肯定是个函数,就像这样
function Curry(){
return function(){
//逻辑
}
}
我们需要将我们的目标函数传入Curry中
function Curry(fn){ // fn表示就是add函数
return function(...args){ // args 表示调用之后传入的参数 就像1,2,3,4
}
}
这样整体逻辑了解了,函数的参数是依次传进来的,我们需要在所有参数传递进来之后再去执行函数并返回结果,那么就需要对参数个数进行判断。这里有一个方法需要了解一下,那就是函数的length
属性,比如
function fn(){}
console.log(fn.length) // 0
function fn1(a,b){}
console.log(fn1.length) //2
函数的length属性表示“第一个具有默认值之前的参数个数” 具体可以参考文章 《JS 中函数的 length 属性》
回到分析中,我们需要知道当函数接收的参数个数等于函数的形参个数时,才可以进行执行并返回结果,否则,继续递归进行函数柯里化。
function Curry(fn,arr = []){ // fn表示就是add函数
let len = fn.length // 获取函数fn的形参个数
let argsArr = [] // 用一个数组将fn的参数存起来
return function(...args){ // args 表示调用之后传入的参数 就像1,2,3,4
argsArr = [...arr,...args]
if(argsArr.length < len){ // 当传入参数length小于len时递归柯里化函数
return Curry(fn,argsArr)
}else{ // 当传入参数length等于len时直接执行函数
return fn(...argsArr)
}
}
}
注意点:每次进行递归时,需要将参数携带着,在Curry中接收,然后每次进行拼接。这里大量使用了ES6展开运算符和REST剩余运算符,如果阅读困难,可以查看前文关于展开运算符和剩余运算符。
关于ES6 Class
ES6和ES5继承区别:
ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。
ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。
关于super关键字
class Parent{
constructor(x,y){
this.x = x;
this.y = y;
}
parentY(){
return 12
}
}
class Child extends Parent {
constructor(x,y){
super(x,y)
}
sayParams(){
console.log(this.x, super.parentY()) // 1,12
}
}
new Child(1,2).sayParams()
super
关键字即可以当函数使用,也可以当对象使用。如上代码
super当函数调用时代表父类的构造函数,ES6要求,子类的构造函数必须执行一次super函数。
super作为对象使用时指向父类的原型对象。(由于super指向的时父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的,比如在上面方法sayParams中访问super.y那么就访问不到)
Class的静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前加上static
关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为"静态方法"。
class Foo{
static sayName(){
console.log("name")
}
}
Foo.sayName() // name
new Foo().sayName() // 报错 syaName is not a function
父类的静态方法可以被子类继承
class Foo{
static sayName(){
console.log("name")
}
}
class Child extends Foo{
}
Child.sayName() // name
Class的静态属性和实例属性
静态属性指的是Class本身的属性,即Class.propname,而不是定义在实例对象(this)上的属性。
class Foo{}
Foo.prop = 1
以上代码中prop是类Foo的静态属性。采用直接赋值的形式。还可以这样写:
class Foo{
static prop = 2
}
Foo.prop // 2
new Foo().prop // undefined
直接在类中进行赋值,并在前加上static
关键字。
实例属性就是可以通过new操作符对类实例之后访问的属性,可以用等式直接写入类的定义中
class Foo{
prop = 12
}
new Foo().prop // 12
Foo.prop // undefined
以前定义实例属性只能写在类的constructor方法里面,有了新的写法之后,就可以不写在constructor中了。
手写一个函数实现new的功能
首先我们需要明白new干了什么事情,new一个函数之后,返回了一个对象,该对象能够访问函数的原型。
function myNew(func, ...args){
if(typeof func !== 'function'){
return;
}
let obj = {};
func.call(obj, ...args); // 核心,改变this指向
obj.__proto__ = func.prototype; // 改变原型,让obj可以访问func的原型
return obj;
}
手写一个instanceof.
instanceof主要是判断一个对象是否是某个类的实例。
换句话说就是判断某个对象的原型链上有没有某个某个类的prototype
function isInstance(ins, target){
if(!ins || !target || !ins.__proto__ || !target.prototype){
return false;
}
let current = ins.__proto__;
while(current){
if(current === target.prototype){
return true;
}
current = current.__proto__;
}
return false;
}
使用setTimeout模拟setInteval
let timer = null;
function simulation(func, wait) {
let intval = function () {
func();
timer = setTimeout(intval, wait);
}
timer = setTimeout(intval, wait);
}
simulation(function() {
console.log(1);
}, 1000);
clearTimeout(timer);
参考:https://mp.weixin.qq.com/s/vYHSqv_6ttWLK4qdSS6V1w
待续......
写在最后:文中内容大多为自己平时从各种途径学习总结,文中参考文章大多收录在我的个人博客里,欢迎阅览http://www.tianleilei.cn