普通视图

发现新文章,点击刷新页面。
今天 — 2026年3月4日首页

Promise由浅入深

作者 willow
2026年3月4日 15:52

是什么

  1. 是异步编程的一种解决方法,它是一个构造函数,由它的实例对象来封装一个异步操作,并且可以获取其成功或失败的结果。构造函数Promise内部自动执行一个执行器函数executor,执行器函数接收两个参数resolve、reject两个回调函数,executor执行器函数是同步回调函数。
  2. 有三个状态:pending、fulfilled、rejected。实例的状态只能由pending转为fulfilled或rejected状态,并且状态一经改变,就没办法再被改变了。
  3. 状态如何改变的呢?通过resolve()将状态改为fulfilled;reject()函数将状态改为rejected;
  4. 回调地狱:回调函数嵌套使用,不利于阅读以及异常处理,Promise的链式调用解决了回调地狱。它实例对象具有then和catch方法,而它们的返回结果依然是一个Promise对象,因此可以使用.符号进行链式调用

Promise构造函数 Promise(excutor){}

  1. excutor函数:同步执行(resolve, reject)=>{}, excutor会在Promise内部立即同步回调
  2. resolve函数:内部定义成功时我们调用的函数 value =>{}
  3. reject函数:内部定义失败时我们调用的函数 reason=>{}

Promise原型上的方法

  1. Promise.prototype.then((onResolved, onRejected)=> {})
    a. onResolved函数:成功的回调函数(value)=>{}
    b. onRejected函数:失败的回调函数(reason)=>{}
    c. 成功或失败的回调函数都会返回一个新的Promise,也就是promise能链式书写的原因
  2. Promise.prototype.catch((onRejected)=> {}):
    onRejected函数:失败的回调函数(reason)=>{}。是then()的语法糖,相当于:then(undefined,onRejected)
  3. Promise.prototype.finally:finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
// 1 Promise构造函数 Promise(excutor){}
const p1 = new Promise((resolve, reject)=>{ 
  // ...异步操作
  resolve(1)
})
// 2 Promise.resolve方法 Promise.reject方法
const p2 = Promise.resolve(2) 
const p3 = Promise.reject(3)  
// 3 原型上的方法 Promise.prototype.then((onResolved, onRejected)=> {})  
// 4 原型上的方法 Promise.prototype.catch((onRejected)=> {})
// onResolved函数:成功的回调函数(value)=>{}; onRejected函数:失败的回调函数(reason)=>{}
p1.then(value=>{console.log(value)})// 结果:1
p2.then(value=>{console.log(value)})// 结果:2
p3.catch(reason=>{console.log('catch', reason)}) // 结果:catch 3

Promise身上的方法

  1. Promise.resolve方法 (value)=>{}:返回一个成功/失败的promise对象,value参数是成功的数据或promise对象
  2. Promise.reject方法 (reason)=>{}:返回一个失败的promise对象,reason:失败的原因
  3. Promise.all方法 (promises)=>{}: 返回一个新的promise,参数是promise数组。只有所有的promise都成功才成功,一个失败了就直接失败了(全部成功,任一失败立即拒绝
  4. Promise.race方法 (promises)=>{}: 返回一个新的promise,第一个完成的Promise的状态就是最终的结果状态(第一个响应的是啥就是啥)
  5. Promise.allSettled方法 (promises)=>{}:返回一个新的promise,状态总是已完成,用来确定一组异步操作是否都结束了(全部返回,不管成功或失败)
  6. Promise.any方法 (promises)=>{}: 返回一个新的promise,状态总是已完成,只要参数实例有一个变成fulfilled状态,包装实例就会变成 fulfilled状态(任一成功,全部失败才拒绝);如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。
// 1 Promise构造函数 Promise(excutor){}
const p1 = new Promise((resolve, reject)=>{ 
  // ...异步操作
  resolve(1)
})
// 2 Promise身上的几个方法使用
const p2 = Promise.resolve(2)  //Promise.resolve方法
const p3 = Promise.reject(3)  //Promise.reject方法
const p4 = Promise.all([p1, p2, p3]) //Promise.all方法
const p5 = Promise.race([p1, p2, p3]) //Promise.race方法
const p6 = Promise.allSettled([p1, p2, p3]) //Promise.allSettled方法
const p7 = Promise.any([p1, p2, p3]) //Promise.any方法
// 对应all race allSettled any的结果
p4.catch(reason=>{console.log('catch', reason)})// all结果:catch 3,全部成功,任一失败立即拒绝
p5.then(value=>{console.log(value)})// race结果:1,第一个响应的是啥就是啥
p6.then(value=>{console.log(value)})
// allSettled结果:[{"status": "fulfilled","value": 1},{"status": "fulfilled","value": 2}
//,{"status": "rejected","reason": 3}],全部返回,不管成功或失败
p7.then(value=>{console.log(value)})// any结果:1 //任一成功,全部失败才拒绝

promise.all与promise.race的区别

  1. 相同点用来完成并行任务, 它接收一个数组,数组的每一项都是一个promise对象
  2. 不同点:Promise.all 关注全部完成,Promise.race 关注最先响应
  3. Promise.all:只有所有的promise都成功才成功,一个失败了就直接失败了。
    a.当数组中所有的promise的状态都达到resolved的时候,all方法的状态就会变成resolved;
    b.有一个状态变成了rejected,那么all方法的状态就会变成rejected。
  4. Promise.race: 第一个完成的Promise的状态就是最终的结果状态。
    a.如果第一个promise对象状态变成resolved状态就是resolved;第一个promise变成rejected,状态就是rejected。

Promise几个关键问题

  1. new Promise(executor) 中的执行器以及 .then、.catch 都是同步的,但是执行器中的异步操作是异步的,.then 和 .catch 中的回调函数也是异步的。
  2. promise.then与promise.catch的区别:catch()是then()的语法糖,相当于:then(undefined,onRejected)
  3. promise怎么串联多个操作任务?通过then的链式调用串连多个同步/异步任务,因为promise的 then() 返回一个新的 promise
  4. promise.then()返回的新的promise状态是谁改变?由then里面回调执行的结果决定。

① 如果抛出异常,新promise变为rejected;
② 如果返回的是非promise,那么状态为已完成,value为返回值;
③ 如果返回的是promise,那么状态由promise的状态决定

  1. 异常穿透:在Promise的链式调用链中,所有的then都没有指定错误的回调,则前面出现的异常会在最后失败的回调中处理。
  2. 值传透:链式调用的参数不是函数时,会发生值穿透,就传入的非函数值忽略,传入的是之前的函数参数。
  3. 如何中断promise链?在回调函数中返回一个pendding状态的promise对象:return new Promise(()=>{})

Promise问题

  1. 无法取消Promise,若没有状态变更,也无法停止 promise 的等待
  2. 不设定 then 或 catch 方法,构造函数(excutor函数)错误,无法捕获
  3. 未完成状态时,无法得知是刚开始,还是即将完成

手写一个简易promise

  1. 定义Promise构造函数:① 定义excutor执行器函数是同步执行;② 声明pending、fulfilled、rejected三个全局状态;③ 定义resolve()修改状态以及异步执行所有成功的回调函数;④ 定义reject()修改状态以及异步执行所有失败的回调函数
  2. 定义Promise原型对象then(),参数是onResolved, onRejected两个回调;① 返回一个新的Promise对象,返回promise的结果由onResolved/onRejected执行结果决定。② 根据全局状态,如果是fulfilled、rejected则异步执行onResolved/onRejected,改变return的promise的状态;③ 如果是pending则push进数组等待被执行。
  3. Promise原型对象catch(),返回一个新的Promise对象
// 1 Promise 构造函数 excutor执行器函数 同步执行
const PENDING = 'pending'
const RESOLVED = 'fulfilled'
const REJECTED = 'rejected'
function Promise(excutor){
   try{// excutor同步执行
      excutor(resolve, reject) 
  }catch(error){// 若执行器异常,promise 对象变为 rejected 状态
      reject(error)
  }
  this.data = null
  this.status = PENDING //状态默认是pending
  this.callbacks = [] //保存的回调数组,结构是[{onResolved(){},onRejected(){}}]
  // 修改状态以及异步执行所有成功的回调函数
  function resolve(value){
      // 状态只能修改一次
      if(this.status !== PENDING) return
      // 修改值与状态
      this.status = RESOLVED;
      this.data = value;
      // 回调数组有值则立马取出执行
      if(this.callbacks.length > 0){
          // 模拟异步执行所有成功的回调函数
          setTimeout(()=>{
              this.callbacks.forEach(callbacksObj => {
                  callbacksObj.onResolved(value)
              });
          })
      }
  }
  // reject同理 修改状态以及异步执行所有失败的回调函数
}
// 2 Promise原型对象then方法 返回一个新的Promise对象
Promise.prototype.then = function(onResolved, onRejected){
  // 返回一个新的Promise对象 返回promise的结果由onResolved/onRejected执行结果决定
   return new Promise((resolve, reject)=>{
     function handle(callback){
      // a. 如果抛出异常,新 promise 变为 rejected, reason为抛出的异常
      // b. 如果返回的是非 promise 的任意值,新promise变为 resolved,value 为返回的值
      // c. 如果返回的是另一个新 promise,此 promise 的结果就会成为新 promise 的结果
        try{
            let result = callback(self.data)
            //3 返回的是另一个新 promise,此 promise 的结果就会成为新 promise 的结果
            if(result instanceof Promise){
                result.then(
                    value => resolve(value), // 当result成功,返回的promise也成功
                    reason => reject(reason) // 当result失败,返回的promise也失败
                )
            }else{
                resolve(result)//2 如果返回的是非 promise 的任意值,promise变为 resolved
            }
        }catch(error){ reject(error) }//1 如果抛出异常 promise 变为 rejected
    }
     // resolved/rejected状态,则异步执行onResolved/onRejected,改变return的promise的状态
     if(self.status === RESOLVED){
        setTimeout(()=>{
          handle(onResolved)
        },0)
     }else if(self.status === REJECTED){ 
       setTimeout(()=>{
          handle(onRejected)
        },0
     }else if(self.status === PENDING){//push进数组等待被执行
       self.callbacks.push({
         onResolved(){ handle(onResolved) }, 
         onRejected(){ handle(onRejected) }
       })
     }    
   })
}
// Promise原型对象 catch 方法 返回一个新的Promise对象
Promise.prototype.catch = function(onRejected){
  return this.then(undefined, onRejected);
}
// Promise函数对象resolve方法,返回一个新的Promise对象
// 返回新promise得结果由value决定,如果value是一个非promise,那么返回成功的promise,value就是这个传入的参数;value是一个promise,那么返回的promise,执行结果取决于传入的promise;
Promise.resolve = function(value){
  return new Promise((resolve, reject)=>{
    if(value instanceof Promise) value.then(resolve,reject)
    else resolve(value);
  })
}
// Promise函数对象reject方法,返回reason
Promise.reject = function(value){ 
  return new Promise((resolve, reject)=>{  reject(value) })
})

其他

  1. 手写Promise函数对象all方法,参数是promise类型的数组。返回一个新的Promise对象,所有promise成功则成功,只有一个失败就失败;
  2. 手写Promise函数对象race方法,参数是promise类型的数组。返回一个新的Promise对象,第一个响应的是啥就是啥。
// 1 all方法参数是promise类型的数组,返回一个新的Promise对象 所有promise成功则成功,只有一个失败就失败
// 循环数组的每个promise实例的响应结果,有一个失败状态就直接reject(reason);
// 没有的话比较数组个数与成功状态的个数,相等了才resolve(values)
Promise.all = function(promises){
    let values = new Array(promises.length)
    let count = 0
    return new Promise((resolve, reject)=>{
        promises.forEach((p, index) => {
            p.then(value=>{
                count++
                values[index] = value;
                if(promises.length === count){// 全部成功,返回所有成功的值的数组
                    resolve(values)
                }
            },reason=>{ // 出现失败就返回失败
                reject(reason)
            })
        })
    })
}
// 2 Promise函数对象race方法,参数是promise类型的数组
// 返回一个新的Promise对象,第一个响应的是啥就是啥
Promise.race = function(value){
    return new Promise((resolve, reject)=>{
        promises.forEach((p, index) => {
            p.then(value=>{
                resolve(value)
            },reason=>{
                reject(reason)
            })
        })
    })
}

链接

尚硅谷Promise教程(promise前端进阶)

Generator与Iterator

作者 willow
2026年3月4日 14:52

Iterator

是什么
  1. 遍历器(Iterator),它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要具备Symbol.iterator属性,就可以被遍历。比如for...of循环遍历某种数据结构时,该循环会自动去寻找Iterator接口。
  2. 作用:1)是为各种数据结构,提供一个统一的、简便的访问接口;2)使得数据结构的成员能够按某种次序排列;3)Iterator接口主要供for...of消费。
调用Iterator接口的场合
  • 解构赋值,对数组和Set结构进行解构赋值时,会默认调用Symbol.iterator方法。let [first, ...rest] = set
  • 还有扩展运算符[...arr]for...ofArray.from()Map()Set()
// 当使用扩展运算符(...)或者对数组和 Set 结构进行解构赋值时,
// 或者使用for of去遍历某一个数据结构的时候会默认调用Symbol.iterator方法。
// 原生具备iterator接口的数据(可用for of遍历),比如数组 字符串  Set,对象不行
let arr = [1, 2, 3, 4];
let str = "kaixin";
let set = new Set(["q","w","e","r","t","y"])
for(let i of str){
    console.log(i); // k a i x i n
}
console.log(...arr) // 1 2 3 4
console.log(...str) // k a i x i n
console.log(...set) //q w e r t y
工作原理
  1. 创建一个指针对象,指向数据结构的起始位置。
  2. 第一次调用next方法,指针自动指向数据结构的第一个成员
  3. 接下来不断调用next方法,指针会一直往后移动,直到指向最后一个成员
  4. 每调用next方法返回的是一个包含value(当前成员的值)和done(尔)的对象,value表示当前成员的值,done对应的布尔值表示当前的数据的结构是否遍历结束。
  5. 当遍历结束的时候返回的value值是undefined,done值为true
//模拟`next`方法返回值的例子
//一个遍历器生成函数,作用就是返回一个遍历器对象
    function makeIterator(arr){
        var index = 0;
        return {
            next: function() {
               if(index<arr.length){
                   return {value:arr[index++],done:false}
               }else{
                   return {value: undefined,done:true}
               }
            }
        }
    }
    var it = makeIterator(['a', 'b']);
    //指针对象的next方法,用来移动指针。
    //开始时,指针指向数组的开始位置。
    //然后,每次调用next方法,指针就会指向数组的下一个成员。
    //第一次调用,指向a;第二次调用,指向b。
    console.log(it.next())//Iterator.html:21 {value: "a", done: false}
    console.log(it.next())//{value: "b", done: false}
    console.log(it.next())//{value: undefined, done: true}
  1. 让对象支持for…of的办法就是手动给对象添加迭代器
// 1 当使用for of去遍历某一个数据结构的时候,首先去找Symbol.iterator,
   //找到了就去遍历,没有找到的话不能遍历。对象使用for of会报错,因为本身没有迭代器。
let obj = { a: 2, b: 3 }
for(let i of obj){
    console.log(i); // Iterator.html:38 Uncaught TypeError: obj is not iterable
}
// 2 让对象支持for…of的办法就是手动给对象添加迭代器
  // a. 实现:返回一个对象有next方法,调用其next函数返回{done, value}
  // b. 思路:index指针记录位置,在返回的函数next里面比较指针与集合的长度,得出done状态,
        //大于则done为true,value为undefined        
obj[Symbol.iterator] = function(){
    const keys = Object.keys(this)
    let index = 0
    return {
        next(){
            let done = index <= len ? false : true;
            let value = !done ? arr[index++] : undefined;
            return { done, value }
        }
    }
}
// 对象可以使用for of了。
for(let i of obj){
    console.log(i); // 2,3
}

Generator

1. 是什么?
  1. Generator 函数一种异步编程解决方案。它有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield语句,定义不同的内部状态。yield可暂停,next方法可启动。每次返回的是yield后的表达式结果
  2. Generator 函数特点

a. 调用Generator函数后该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的遍历器对象。
b. 每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield语句(或return语句)为止。
c. Generator函数是分段执行的,yield语句是暂停执行的标记,而next方法可以恢复执行。

  1. Generator执行分析:yield相当于看视频的时候有事暂停按钮,调用next()就相当于回来后点击一下继续观看。

a. 遇到yield语句,就暂停执行后面的操作。
b. 下一次调用next方法时,再继续往下执行,直到遇到下一个yield语句。
c. 如果没有再遇到新的yield语句,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
d. 如果该函数没有return语句,则返回的对象的value属性值为undefined

// yield相当于看视频的时候有事暂停按钮,调用next()就相当于回来后点击一下继续观看。
function* testGenerator(){
    console.log("开始执行")
    yield 'hello';
    console.log("中间")
    yield 'generator';
    yield '!!';
}
let gen = testGenerator();
console.log(gen.next()); 
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
// 开始执行
// {value: 'hello', done: false}
// 中间
// {value: "hello", done: false}
// {value: "generator", done: false}
// {value: "!!", done: false}
// {value: undefined, done: true}
与 Iterator 接口的关系

由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。

// 对象没有iterator接口,用for...of遍历时便会报错。
let obj = { username: 'kobe', age: 39 }
for (let i of obj) {
    console.log(i) //  Uncaught TypeError: obj is not iterable
}
//由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。
let obj = { username: '王', age: 12, sex: '男'}
obj[Symbol.iterator] = function* myTest() {
    yield '王';
    yield 12;
    yield 男;
};
for (let i of obj) {
    console.log(i) // 王 // 12  //男
}
昨天 — 2026年3月3日首页

JavaScript数据类型整理1

作者 willow
2026年3月3日 15:48

有哪些数据类型?

  1. 基本类型:Number String Boolean Null Undefined Symbol BigInt
  2. 引用类型:Object Function Array Map Set
  3. 区别:是数据存储不同

a. 基本类型的值存储在栈中,在栈中存储的是值,它赋值后值相同,但是两个值对应的地址不同,所以a = 1;b=a;a=2;b还是等于1;
b. 引用类型的值存储在堆中,在栈存放的是指向堆内存的指针地址;它赋值时候是将对象的内存地址赋值給另一个对象,也就是说两个对象指向的是同一个堆内存,所以a={name:11};b=a;b.name=22; a也会改变;

null与undefined区别是什么?

  1. ①undefined代表定义未赋值;②nulll定义并赋值了, 只是值为null
  2. typeof null是object
  3. 什么时候给变量赋值为null呢?①初始赋值, 表明将要赋值为对象;②结束前, 让对象成为垃圾对象(被垃圾回收器回收)
  4. null==undefined; null!==undefined

Symbol创建唯一值

  1. 是什么?给对象设置“唯一值"的属性名,对象的属性名可以是数字、字符串、Symbol类型;
  2. 用法?
let a1= symbol('AA'); 
let a2= symbol('AA'); 
let a3 = a1; 
a1 === a2; //false 
a1 === a3 //true
  1. 作用? Symbol.asyncIterator/iterator/hasInstance/toPrimitive/tostringTag 是某些js底层原理的实现机制。基于symbol类型的值,保证行为标识的唯一性

BigInt大数类型

  1. 超过安全数后,进行运算或者访问,结果会不准确
 a. Number.MAX_SAFE_INTEGER: 9007199254740991 //jS中的最大安全数
 b. Number.MIN_SAFE_INTEGER: -9007199254740991 //jS中的最小安全数
  1. 解决方案 a. 服务器返回给客户端的大数,按照"字符串"格式返回;然后客户端把其变为 BigInt,然后按照BigInt进行运算,最后把运算后的BigInt转换为字符串,在传递给服务器即可

数据类型的检测方式有哪些?

  1. typeof:其中数组、对象、null 都会被判断为 object
console.log(typeof 2); // number
console.log(typeof true); // boolean
console.log(typeof "str"); // string
console.log(typeof undefined); // undefined
console.log(typeof function () {}); // function
console.log(typeof null); // object
console.log(typeof {}); // object
console.log(typeof []); // object
  • 数据类型的值在计算机底层是按照64位的二进制值进行存储的,typeof也是按照二进制进行类型检测。
  • 前三位是0认为是对象,然后再去看有没有实现call方法。如果实现了则返回'function',没有实现,则返回'object'。null是64个零,所以typeof null->'object'
  1. instanceof:可以判断对象的类型,不能判断基本类型的;原理是构造函数的prototype属性是否出现在对象原型链的任何位置。
console.log(2 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log("str" instanceof String); // false

console.log([] instanceof Array); // true
console.log(function () {} instanceof Function); // true
console.log({} instanceof Object); // true
  • 先检测构造函数是否拥有 Symbol.hasInstance 方法
  • 如果有这个方法:构造函数[Symbol.hasInstance](实例)返回的值就是结果;
  • 如果没有这个方法,则按照原型链进行查找:按照实例的_proto-一直向上找,直到找到0bject.prototype为止,只要在原型链上出现了“构造函数.prototype”,说明当前实例率属于它,结果返回true;如果没找到,结果就是false;
  1. constructor:判断两种数据的类型,对象实例通过constructor来访问它的构造函数,缺点是如果对象改变过它的原型,那么constructor判断会有问题
console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
console.log(('').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true
  1. Object.prototype.toString.call([value]):使用Object对象的原型方法toString来判断数据类型,属于检测最准确、最全面的方式了,能够区分null、能够检测原始值类型、能够细分对象、即便重构原型对象检测也是准确的
console.log(Object.prototype.toString.call(2));        //'[object Number]'
console.log(Object.prototype.toString.call(true));     //'[object Boolean]'
console.log(Object.prototype.toString.call("str"));    //'[object String]'
console.log(Object.prototype.toString.call([]));       //'[object Array]'
console.log(Object.prototype.toString.call(function () {}));//'[object Function]'
console.log(Object.prototype.toString.call({}));       //'[object Object]'
console.log(Object.prototype.toString.call(undefined));//'[object Undefined]'
console.log(Object.prototype.toString.call(null));     //'[object Null]'
  • 返回结果“[object ?] ?:一般是自己所属的构造函数
  • 首先会看[value]值是否有 Symbol.tostringTag 属性,有这个属性,属性值是啥,检测出来是啥就是啥; Math[Symbol.tostringTag]:'Math' map.prototype[Symbol.tostringTag]:'Map' Promise.prototype[Symbol.tostringTag]:'Promise' Set.prototype[Symbol.tostringTag]:'set'
  • 如果没有这个属性,才一般是按照自己所属的构造函数返回

数组的判断有几种?

  1. Object.prototype.toString.call(): Object.prototype.toString.call([]).slice(8,-1) === "Array"
  2. Array.isArray(): Array.isArray([])
  3. instanceof: [] instanceof Array
  4. Array.prototype.isPrototypeOf:Array.prototype.isPrototypeOf([])
  5. __proto__: [].__proto__ === Array.prototype

typeof NaN是多少?

  1. typeof NaN是number
  2. NaN:指不是一个数字,意思是执行数字运行失败,是失败后返回的结果
  3. NaN === NaN 为false

instanceof操作符的实现原理?

  1. 作用:检测某个对象是否属于某个类型 [] instanceof Array
  2. 原理是判断构造函数的prototype属性是否出现在对象原型链的任何位置;所以重点就是找到递归对象的原型链以及构造函数的prototype
  3. 步骤

a. 获取对象的原型 proto = Object.getPrototypeOf(obj)
b. 获取构造函数的原型对象 prototype = ctor.prototype;
c. 判断构造函数的原型对象是否在对象的原型链上 proto === prototype
d. 如果没有找到就继续在其原型上找 proto = Object.getPrototypeOf(proto);

function myInstanceof(obj, ctor) {
  // 获取对象的原型
  let proto = Object.getPrototypeOf(obj);
  // 获取构造函数的 prototype 对象
  let prototype = ctor.prototype;
  // 判断构造函数的 prototype 对象是否在对象的原型链上
  while (true) {
    if (!proto) return false;
    if (proto === prototype) return true;
    // 如果没有找到,就继续从其原型上找,Object.getPrototypeOf方法用来获取指定对象的原型
    proto = Object.getPrototypeOf(proto);
  }
}

类型转换机制?

  1. 常见的类型转换有:强制转换(显式转换)、自动转换(隐式转换)
  2. 显式转换转化规则常见的方法有:Number() parseInt() parseFloat() String() Boolean()
  3. 隐式转换转化规则?比较运算(==、!=、>、<)if、while;算术运算(+、-、*、/、%)
  4. 其他类型转数字: ①Number([val]):比较严格,只要有一个字符无法转成数值,整个字符串就会被转为NaN; ②parseInt([val], [radix]):没那么严格,遇到不能转换的字符就停下来。
  1. 空字符串为0,有出现非数字结果为NaN
  2. 布尔值转数字,true:1,false:0
  3. Symbol会报错;
  4. null:0; undefined:NaN;
  5. BigInt去除“n”
  6. 对象转数字:①先调用对象原型上的一个函数Symbol.toPrimitive()②如果不存在则调用对象的valueOf获取原始值;③如果获取的不是原始值,再调用对象的toString转为字符串;④再把字符串基于Number方法转换为数字;
Number([10]) //10
// 1 首先检测Symbol.toPrimitive是否存在,如果存在就调用 ƒ [Symbol.toPrimitive]() { [native code] }
// 2 如果是undefined,那么arr.valueOf()获取原始值 
// 3 结果为[10],不是原始值,那么就是arr.toString()转为字符串‘10’;
// 4 最后再把字符串转为数字 Number('10')->10
// Number([10, 20])结果为NaN,分析如下:
//  [10,20].valueOf():[10, 20]-> [10,20].toString():'10,20' -> Number('10,20')->NaN
  1. 其他类型转字符串:①拿字符串包起来String({}) //'[object Object]'; ②“+”出现在两边,其中一边是字符串或者某些对象,会以字符串拼接规则处理
console.log(10+'10') //'1010'
console.log(10+new Number(10)) //20
//①new Number(10)[Symbol.toPrimitive]:undefined;-> ②new Number(10).value0f():10 -> 10+10 = 20
console.log(10+[10]) //'1010' 
//①[10][Symbol.toPrimitive]:undefined;-> ②[10].valueOf().toString():'10',为字符串,所以是拼接 
  1. 其他类型转布尔值: Boolean([val])或者!/!! 除了null undefined '' NaN,其他结果都是true。

ToPrimitive: 用来将值转换为基本类型值

  1. 如果值为对象,ToPrimitive(obj, type);对象默认type为number。
  2. 当type为number时规则:var objToNumber = (value) => Number(value.valueOf().toString());
  3. 当type为string时规则:var objToNumber = (value) => Number(value.toString().valueOf());
  4. 对于 Date 以外的对象,转换为基本类型的大概规则可以概括为一个函数:var objToNumber = (value) => Number(value.valueOf().toString());
var a = { name: "Tom" };
var b = { age: 18 };
a + b; // "[object Object][object Object]"
a.valueOf().toString(); // "[object Object]"
b.valueOf().toString(); // "[object Object]"
a + b; // "[object Object][object Object]"

==操作符的强制类型转换规则?

  1. 首先会判断两者的类型是否一样,一样则比较大小;不一样则进行类型装换;
  2. 两个都为简单类型则有一方为字符串和布尔值都会转换成数值再进行判断;null==undefined是true;
  3. 两个都为引用类型则比较他们是否指向同一对象,比较的是堆内存地址,地址相同则相等
  4. 一个为引用类型,一个为复杂类型则将复杂类型用ToPrimitive转为原始类型再进行判断
  5. 除了以上情况,只要两边类型不一致,剩下的都是转换为数字,然后再进行比较的。
// 一个为引用类型,一个为复杂类型
//对象==字符串 需要将对象转字符串再比较「symbol.toPrimitive->value.toString().valueOf()]
//对象==数字 需要将对象转数字再比较「symbol.toPrimitive->value.valueOf().toString()]
[] == false //true
//只要两边类型不一致,剩下的都是转换为数字 Number([])为0,所以0 == 0为true

为什么 0.1+0.2 ! == 0.3,如何让其相等?

  1. 为什么小数(浮点数)的计算会出现精准度丢失问题?0.1+0.2 // 0.30000000000000004 0.1+0.7 //0.7999999999999999
  • 计算机存储值是以二进制在计算机底层来存的,所以需要先把十进制转为二进制的科学计数法n.toString(2)
  • 整数:(10).toString(2) //'1010'->10一直去除以2,余数组合就是结果;
  • 浮点数:(0.1).toString(2) //'0.0001100110011001100110011001100110011001100110011001101'
  • 某些十进制的浮点数在转二进制可能会无限循环下去,在底层存储最多存64位,舍弃了一些值后值本身就失去了精准值,再转成十进制就有了误差;
  1. 怎么解决精准度问题?使用 toPrecision 凑整;扩大系数法,把小数转成整数后再运算;使用第三方库,如Math.js、BigDecimal.js
//扩大系数法,把小数转成整数后再运算
function add(num1, num2) {
  const num1Digits = (num1.toString().split('.')[1] || '').length;
  const num2Digits = (num2.toString().split('.')[1] || '').length;
  const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits));
  return (num1 * baseNum + num2 * baseNum) / baseNum;
}
❌
❌