代码输出题,会这些就够了。

基础知识

把事件循环、函数作用域、原型弄明白就行,做些经典的题目

this指向

  1. apply绑定this改变不了箭头函数this。
  2. 箭头函数不会改变 this 的指向,它会继承外层作用域的 this 值。因此,在箭头函数中,this 的指向由箭头函数定义时的外部环境决定,而不是调用时的情况。

全局上下文中

当在全局作用域中使用 this 时,它将指向全局对象,在浏览器中通常是 window 对象。

函数中

  • 在普通函数中,this 的指向取决于函数的调用方式。(this指向在函数定义时,是确定不了的)
  • 如果函数是作为普通函数调用(不作为对象的方法),this 将指向全局对象或者 undefined(在严格模式下)。
js 复制代码
function sayHello() {
    console.log(this);
}
​
sayHello(); // 在浏览器中,指向 window 对象 window.sayHello 全局上的方法
js 复制代码
const obj = {
  name: 'example',
  myMethod() {
    console.log(this.name);  //运行时确定
  }
};
obj.myMethod(); // 这里的this指向obj,会输出 'example'

对象方法中

对象方法中,this 指向调用该方法的对象

js中作用域分为,全局作用域 和 函数作用域。

js 复制代码
let name = 'name' //let const声明的变量不会成为全局对象,是块级作用域,var才会
const person = {
    name: "Alice",
    greet: function() { //普通函数,this调用时确定
        console.log("Hello, my name is " + this.name);
    },
    greet2: () => { //箭头函数的this  对象处于全局作用域,对象没有作用域
        console.log("Hello, my name is " + this.name);  //this.name 是 ''(空的)
        console.log(this) //window
    }
};
console.log(name) //name
console.log(window.name) // ''
person.greet(); // this 指向 person 对象
person.greet2(); // 
js 复制代码
// 使用 var 声明变量
var varName = 'using var';
console.log('varName 作为 window 的属性:', window.varName); //using var
​
 // 使用 let 声明变量
let letName = 'using let';
console.log('letName 作为 window 的属性:', window.letName); //undefined
​
// 使用 const 声明变量
const constName = 'using const';
console.log('constName 作为 window 的属性:', window.constName); //undefined

构造函数中

在构造函数中,this 指向通过 new 关键字创建的实例对象。

js 复制代码
function Person(name) {
    this.name = name; //运行时确定
}
​
const alice = new Person("Alice"); // 
console.log(alice.name); // Alice

事件处理函数中

在事件处理函数中,this 通常指向触发事件的元素。

js 复制代码
document.getElementById("myButton").addEventListener("click", function() {
    console.log(this); // 指向触发点击事件的按钮元素
});

原型

  1. 所有函数都是由Object new出来
  2. Object自己是Function new出来
  3. Function是由 自己 Function new 出来、Function的prototype是Object new出来
js 复制代码
function Person(name) {
    this.name = name
}
var p2 = new Person('king');//p2._proto_ 指向 Person.prototype  
console.log(p2.__proto__) //Person.prototype
console.log(p2.__proto__.__proto__) //Object.prototype   Person.prototype的原型指向  就到Object
console.log(p2.__proto__.__proto__.__proto__) // null
console.log(p2.__proto__.__proto__.__proto__.__proto__)//null后面没有了,报错
console.log(p2.__proto__.__proto__.__proto__.__proto__.__proto__)//null后面没有了,报错
console.log(p2.constructor)//Person
console.log(p2.prototype)//undefined p2是实例,没有prototype属性  不是报错哦
console.log(Person.constructor)//Function 一个空函数
console.log(Person.prototype)//打印出Person.prototype这个对象里所有的方法和属性
​
//Function---Object--null    Object -Function---Object--null
console.log(Person.prototype.constructor)//Person
console.log(Person.prototype.__proto__)// Object.prototype
console.log(Person.__proto__) //Function.prototype
console.log(Function.prototype.__proto__)//Object.prototype
console.log(Function.__proto__)//Function.prototype
console.log(Object.__proto__)//Function.prototype
console.log(Object.__proto__.__proto__)//Object.prototype
console.log(Object.__proto__.__proto__.__proto__)//null
console.log(Object.prototype.__proto__)//null

作用域

let的特点

  1. 只作用于块级作用域
  2. 不存在变量提升。
  3. 不允许重复声明。
  4. 暂时性死区(TDZ)
js 复制代码
{ //单独用 { }括起来的区域
    let a=10;
    var b=5;
    console.log(a,b) //输出a=10 b=5;
}
console.log(b); //输出 b=5;
console.log(a); //报错 a is not defined

事件循环

  1. 微任务会"插队" 如果在微任务中触发新的微任务,这些新微任务会继续加入当前队列,直到队列清空。这可能导致宏任务被长时间阻塞。

  2. 事件循环流程

    1. 执行一个宏任务 (如主代码块、setTimeout回调)。 通常就是整段JS脚本开始。
    2. 执行所有微任务:清空微任务队列。
    3. 渲染更新(浏览器环境下可能触发 UI 渲染)。
    4. 进入下一轮事件循环,执行下一个宏任务。
  3. 当宏任务中触发了微任务,在当前宏任务执行完后,会立即执行微任务队列里的所有微任务,而不是先把所有宏任务执行完。

  4. 执行当前宏任务:JavaScript 引擎会从宏任务队列中取出一个宏任务放到调用栈中执行。

  5. 触发微任务 :在执行当前宏任务的过程中,如果触发了微任务(例如使用 Promise.thenMutationObserver 等),这些微任务会被添加到微任务队列中。

  6. 执行微任务:当当前宏任务执行完毕后,JavaScript 引擎不会立即从宏任务队列中取下一个宏任务,而是会检查微任务队列。如果微任务队列中有任务,会依次执行微任务队列里的所有微任务,直到微任务队列为空。

  7. 继续下一轮循环:微任务队列清空后,JavaScript 引擎才会从宏任务队列中取出下一个宏任务,重复上述过程。(执行一个宏任务后检查微任务是否空了)

题目训练:

字节

1 ByteDance,原型 + this
js 复制代码
let name = "ByteDance"; //
function A() {
    this.name = "123";
}
A.prototype.getA = function () {
    console.log(this);   //运行时才确定
    return this.name + 1;  // '1231'
};
let a = new A();
// console.log(a.getA()); // A {name: '123'}
let funcA = a.getA(); // A {name: '123'}  是a调用原型上的方法。还有就是return不是打印。相当是a调用,不是window调用
funcA(); //Uncaught TypeError: funcA is not a function

结果:先输出 A {name: '123'}、再报错funcA不是函数(因为return 是 '1231')

快手

1 var变量作用域
css 复制代码
var a = 10; 
function b() {
    a = 20; //10被覆盖了,var 和这里的20都是全局变量
    console.log(a); //20
}
b()
2 作用域
js 复制代码
var count = 10;
​
function a(){
    return count + 10; //往外层找
}
​
function b(){
    var count = 20;
    return a(); //这里不是闭包,a函数不是在内部声明
}
​
console.log(b()); //20
3 new原理 + 原型
js 复制代码
function Foo(){
    this.a = 2;
    return {
        a : 3,
        b : 4
    }
}
​
Foo.prototype.a = 6;
Foo.prototype.b = 7;
Foo.prototype.c = 8;
​
let o = new Foo(); //因为Foo返回了一个对象,o变量结果是return的内容。所以输出3 4就是正常访问
​
console.log(o.a);  //3
console.log(o.b);  //4
console.log(o.c);  //undefined   new原理,返回构造函数返回对象的话,实例原型就指向Object,不指向构造函数。
​
​

变成下面结果又不一样(new原理)

js 复制代码
function Foo(){
    this.a = 2;  //这样new出来就可以往原型上找
}
余下不变       
//2 7 8
4 new + 原型(太经典)
js 复制代码
Function.prototype.a =()=>{
    console.log(1)
}
Object.prototype.b =()=>{
    console.log(2)
}
function A(){} 
const a =new A(); 
a.a() //无法执行,不存在
a.b()  //2
A.a()//1
A.b() //2

对于 new 出来的对象 a 的属性,原型链查找的顺序应该是

  1. a 自身
  2. a.__proto__ 相当于 A.prototype (可以"遮住"prototype去想)
  3. A.prototype.__proto__ 相当于 Object.prototype
  4. Object.prototype.__proto__ 这个为 null,原型链查找到头。

对于 function 定义的函数 A 的属性,原型链查找顺序应该是

  1. A 自身
  2. A.__proto__ 相当于 Function.prototype (指向new出来的)
  3. Function.prototype.__proto__ 等于 Object.prototype
  4. Object.prototype.__proto__ 这个为 null,原型链查找到头。

总之几点原则:

  • 所有构造函数的的 prototype 方法的 __proto__ 都指向 Object.prototype (除了 Object.prototype 自身) (所有构造函数都是由Object new出来)
  • Object 是 Function 的实例对象, Object.__proto__ === Function.prototype // true (Object是由Function new出来)
  • Function.prototype 是 Object 的实例对象。Function.prototype.__proto__ === Object.prototype // true
js 复制代码
//题目相关:
console.log(a.__proto__); //A,然后A的原型是Object
console.log(A.__proto__); //Object
​
//通识
console.log(Function.__proto__); //Object
js 复制代码
function Person(age) {
    this.age = age;
}
​
let alice = new Person(12)
console.log(alice);
​
​
function Person2(age) {
   return {
    age:100
   }
}
​
let alice2 = new Person2(12)
console.log(alice2);
​
5 经典闭包
css 复制代码
for (var i = 0; i < 5; i++) {
  setTimeout(() => console.log(i), 0);
}

这里使用了 var 关键字来声明变量 ivar 是函数作用域的,而非块级作用域。当 for 循环执行时,setTimeout 会将回调函数放入任务队列中,等主线程任务执行完后再执行。由于 for 循环会快速执行完毕,此时 i 的值已经变为 5。之后任务队列中的回调函数开始执行,每个回调函数引用的都是同一个 i 变量,所以最终输出的都是 5,一共输出 5 次。

css 复制代码
for (var i = 0; i < 5; i++) {
  setTimeout(console.log(i), 0);
}

在这个代码里,setTimeout 的第一个参数应该是一个函数,但这里传递的是 console.log(i) 的执行结果。console.log(i) 会立即执行,输出当前 i 的值,也就是依次输出 01234。而 setTimeout 接收到的是 console.log(i) 的返回值(console.log 返回 undefined),这就相当于 setTimeout(undefined, 0),所以不会有额外的延迟输出。

css 复制代码
for (let i = 0; i < 5; i++) {
  setTimeout(() => console.log(i), 0);
}

滴滴

1 this + new
javascript 复制代码
var id = 'GLOBAL';
var obj = {
  id: 'OBJ',
  a: function(){
    console.log(this.id); //运行时确定
  },
  b: () => {
    console.log(this.id); //处于window环境
  }
};
obj.a();    // 'OBJ'
obj.b();    // 'GLOBAL'  node运行结果是undefined(因为浏览器this是window,node是undefined,全局情况下)
new obj.a()  // undefined  new(obj.a())  new会执行构造函数
new obj.b()  // Uncaught TypeError: obj.b is not a constructor   箭头函数,不能构造
​
javascript 复制代码
var id = "GLOBAL";
var obj = {
  id: "OBJ",
  a: function () {
    console.log(this.id);
  },
  b: () => {
    console.log(this.id);
  },
};
obj.a.prototype.id = "PROTOTYPE";
obj.a(); //OBJ
obj.b(); //GLOBAL
new obj.a(); // "PROTOTYPE";   说明即使没显式明确绑定this的变量,默认找到实例的id属性。
2 变量作用域
js 复制代码
function fighting() {
  a = 1;
  console.log(a);
}
var a = 0;
fighting(); //1
console.log(a); //1 覆盖
​
function fighting2() {
  var a = 1;
  console.log(a);
}
var a = 0;
fighting2(); //1
console.log(a); //0
​

京东

1 经典作用域
js 复制代码
(function(){
    var a = b = 5;  //b没有var声明,a有var声明,a=b
})()
console.log(b); //5
console.log(a); //undefined
​
2 this指向

1、undefined与基本类型(除String)数据(undefined||null||NaN||boolean||number)做加(+)运算,不分先后,结果都为:NaN。 2、undefined与字符串进行加(+)运算,不分先后,从左往右按字符串相加。 3、undefined与数组进行加(+)运算时会调用.toString()方法,不分先后,结果为:''||'1,2'||'1,2,3',并从左往右按字符串相加。 4、undefined与对象进行加(+)运算时会调用.toString()方法,不分先后,结果为:[object Object],并从左往右按字符串相加。 5、undefined与任何类型的数据进行减(-)||乘(*)||除(/)||取模(%)运算,不分先后,结果都为:NaN。

js 复制代码
const num = {
  a: 10,
  add() {
    return this.a + 2;
  },
  reduce: () => this.a - 2,
};
​
console.log(num.add()); //12
console.log(num.reduce()); //undefiend - 2 => NaN
3 promise异步输出题
js 复制代码
const promise = new Promise((resolve, reject) => {
  console.log(1);
  setTimeout(() => {
    console.log("timerstart");
    resolve("success");
    console.log("timerEnd");
  }, 0);
  console.log(2);
});
promise.then((res) => {
  console.log(res);  //没有就等待。不会卡住
});
console.log(4);
//1 2  4  timerstart  timerEnd  success
js 复制代码
​
const promise = new Promise((resolve, reject) => {
  console.log(1);
  resolve("success");
  setTimeout(() => {
    console.log("timerstart");
    console.log("timerEnd");
  }, 0);
  console.log(2);
});
promise.then((res) => {
  console.log(res);  
});
console.log(4);
//1 2  4 success timerstart  timerEnd  

其他厂

1 Promise事件循环

次数:5。和 "经典题目"第5题同样。

注意setTimeout已经执行,放在宏任务等待执行。

定时器的处理过程

  1. 注册定时器 :当代码执行到 setTimeout 或者 setInterval 时,浏览器或者 Node.js 环境会启动一个定时器。
  2. 计时结束:一旦定时器设定的时间到达,其回调函数就会被添加到宏任务队列之中。
  3. 事件循环处理:调用栈为空的时候,事件循环会从宏任务队列里取出一个任务,把它放到调用栈里执行。

定时器返回值是个ID,不是对象。(浏览器中)

js 复制代码
new Promise((resolve, reject) => {
    console.log('promise1')
    resolve()
    //1
}).then(() => {
    console.log('promise2')
})
 
console.log('start1')
 
//2
setTimeout(() => {
    console.log('a')
},2000)
 
//3
setTimeout(() => {
    console.log('b')
},1000)
​
 //4
setTimeout(() => {
    //9
    Promise.resolve().then(() => {
        console.log('promiseA')
        //10
    }).then(() => {
        console.log('promiseB')
    })
})
 
//5
Promise.resolve().then(() => {
    console.log('promise88')
    //6
    setTimeout(() => {
        console.log(333)
    })
 //7
}).then(() => {
    console.log('promise2')
    //8
    setTimeout(() => {
        console.log(555)
    })
})
 
console.log('start2')
2 Promise抛出错误执行顺序
js 复制代码
new Promise((resolve,rejected)=>{
    throw new Error("err");
})
 .then((res)=>{
    console.log(1,res);
})
  .catch((err)=>{
    console.log(2,err); //2 Error: err
  })
  .then((res)=>{
    console.log(3,res); //3 undefined
})
  .catch((err)=>{
    console.log(4,err);
});

经典题目

1 Foo var声明、new操作符,函数声明与函数表达式
js 复制代码
function Foo() { //函数声明是提升到全局的
    getName = function () {  //没使用let、var这些,提升为window,覆盖
        console.log(1);
    };
    return this;  //运行时确定
}
​
Foo.getName = function () {     //相当于fn.length,可以这样写。这个是公共的。
    console.log(2);
};
​
Foo.prototype.getName = function () {
    console.log(3);
};
​
var getName = function () { //表达式,一个变量,不是函数。提升到全局,但是运行到这里才赋值。
    console.log(4);
};
​
function getName() {
    console.log(5);
}
​
Foo.getName(); //2
getName(); //4  函数声明大于函数表达式,所以打印4 (函数声明>变量函数声明)
Foo().getName(); //1
getName();  //1  
new Foo.getName(); //2   new(Foo.getName())
new Foo().getName(); //3  因为Foo打了小括号,类似new Person('18'),所以相当于(new Foo()).getName() 实例上的getName
new new Foo().getName(); //3 new((new Foo()).getName())
2 Promise输出题、非常经典
js 复制代码
const first = () =>
  new Promise((resolve, reject) => {
    console.log(3); //3
​
    let p = new Promise((resolve, reject) => {
      console.log(7);
      setTimeout(() => {
        console.log(5);
        resolve(6); //注意resolve考点基本就是第一个改变后不再变、或者要对应相应的Promise(这里是后者)
      }, 0);
      resolve(1);
    });
      
    resolve(2); 
      
    p.then((arg) => {
      console.log(arg);
    });
  });
​
first().then((arg) => {
  console.log(arg);
});
console.log(4);
//最终: 3 7 4 1 2 5
​
  1. 变量Promise也会运行。是个同步代码
js 复制代码
let p = new Promise((resolve, reject) => {
    console.log(7);
    setTimeout(() => {
      console.log(5);
      resolve(6);
    }, 0);
    resolve(1);
  });
3 变量作用域、函数变量提升,非常可以可以
js 复制代码
var bar = "aaa";
function a(params) {
    console.log(bar); //应该说没定义好点。也不对,系统知道有,但是不能用。
    let bar;
}
a();//Cannot access 'bar' before initialization
js 复制代码
var bar = "aaa";
function a(params) {
    let bar; //变量声明后若未进行显式赋值,它的初始值会是 undefined。没声明是报错bar is not defined
    console.log(bar); //不是null
​
}
a();//undefined
js 复制代码
var bar = "aaa";
function a(params) {
    console.log(bar);
​
}
a();//'aaa' 会不断往外找。找变量的规则
  1. 先执行 b = 5;,为隐式的全局变量赋值(因为b前面没有使用var、let或const来声明)
  2. 再声明a为局部变量,赋值为b(即5)
  3. 函数执行完毕后,a变量随之消失(因为它是一个局部变量),b变量仍存在于全局作用域中,且值为5。
  4. 内部可以访问外部,外部无法访问内部(作用域)
css 复制代码
(function () {
    var a = b = 5; // var a = (b = 5)。 赋值运算符是右结合的
})();
​
console.log(b); //5
console.log(a); // a is not defined

函数声明 > var变量

函数声明 > 函数表达式 (var函数表达式 和 var变量就是一样)

js 复制代码
function foo() {
    console.log(a);
    var a = 1;
    function a() {}
  }
  foo(); //打印 a这个函数
4 then参数不是函数的情况(值穿透)

then方法接受的参数是函数,而如果传递的并非是一个函数,它实际上会将其解释为then(null),这就会导致前一个Promise的结果会传递下面。

scss 复制代码
Promise.resolve(1)
  .then(2) // 1 => 1。如果 then 接收的 不是一个函数,则内部会被替换为一个*恒等*函数`((x) => x)`,它只是简单地将兑现值向前传递。
  .then(Promise.resolve(3))//不是函数,返回对象
  .then(console.log) //打印传递的val。log是一个函数,本来要传递两个箭头函数的
//1
5 事件循环Promise
js 复制代码
//1
Promise.resolve().then(() => {
    console.log('promise1');
    const timer2 = setTimeout(() => {
      console.log('timer2')
    }, 0)
  });
​
const timer1 = setTimeout(() => { 
    console.log('timer1')
      //2
    Promise.resolve().then(() => {
      console.log('promise2')
    })
  }, 0)
  
  console.log('start');
​
//start、promise1、timer1、promise2、timer2
  1. 第一次的js脚本挑选出第一次的微任务和宏任务,下一次要分到下一次循环。(在微任务、宏任务推入队列要放入下一轮)
  2. 注意resolve()后不是结束了,还要执行。
6 对象的语法、原型、引用
js 复制代码
function F() { }
F.prototype.arr = [1]
F.prototype.b = 1
​
let obj1 = new F()
obj1.arr.push(2) //这样是在原有基础上push,不是新增arr元素。
obj1.b = 2 //这个是基本数据类型。赋值这样是新增元素
​
​
let obj2 = new F()
console.log(obj1.arr, obj1.b)// [1,2] 2   obj1.__proto__.b 是 1
console.log(obj2.arr, obj2.b)  // [1,2]  1
7 ES6默认参数作用域

不用太费心,这个就是...

  1. 记住两点,参数内部的对应参数是利用参数的,可以覆盖;在var没有赋值之前,采用传递的参数。
js 复制代码
var x = 0;
function foo( //函数作用域
//参数作用域,相当于声明了
  x,
  y = function () {
    x = 3;  //不会提升全局,测试过
    console.log(x);  
  }
) {
  console.log(x);
  var x = 2;
  console.log(x);
  y();
  console.log(x);
}
​
foo(1);
console.log(x);
// 1 2 3 2 0
js 复制代码
var x = 0
function foo(x, y = function() { x = 3; console.log(x) }) { 
  console.log(x)
  var x = 2  
  y() //y里边的x是第一个参数x,可以被覆盖
  console.log(x)
}
foo()
console.log(x)
//undefined 3 2 0
js 复制代码
var x = 0;
function foo(
    y = function () {
        x = 3; //提升到了外边
        console.log(x);
    }
) {
    console.log(x);
    var x = 2;
    y();
    console.log(x);
}
​
foo(); //undefined 3 2
console.log(x); //3
8 this指向,return语句中的箭头函数、普通函数(非常有意思)
js 复制代码
var name = 'window'
​
var person1 = {
    name: 'person1',
​
    foo4: function () {
        return () => {
            console.log(this.name) //指向foo4的this
        }
    },
    
    foo5: () => {
        return () => {
            console.log(this.name) //位于window作用域
        }
    }
}
person1.foo4()();  //person1
person1.foo5()(); //window
js 复制代码
var name = 'window'
​
var person1 = {
  name: 'person1',
  foo1: function () {
    console.log(this.name) //运行时确定
  },
  foo2: () => console.log(this.name), //位于全局环境
  foo3: function () {
    return function () {
      console.log(this.name)
    }
  },
  foo4: function () {
    return () => {
      console.log(this.name) //指向foo4的this,foo的this运行时才确定
    }
  }
}
​
var person2 = { name: 'person2' }
​
​
// 开始题目:
person1.foo1(); //person1
person1.foo1.call(person2); //person2,改变this指向
​
person1.foo2(); //window
person1.foo2.call(person2); //改变不了 window
​
person1.foo3()(); //默认绑定, 调用foo3返回一个function定义的独立函数, 在对独立函数调用, this指向window
person1.foo3.call(person2)();// 默认绑定, 通过显式绑定调用foo3, 再对foo3返回的函数独立调用, this指向window
person1.foo3().call(person2);//显式绑定, 通过隐式绑定调用foo3, 在对foo3返回的函数显式绑定person2, this指向person2
​
person1.foo4()();//return后先 通过隐式绑定调用 foo4, 调用foo4返回一个箭头函数, 箭头函数的this会向上级寻找, this指向person1
person1.foo4.call(person2)();//通过显式绑定调用foo4, 调用foo4返回一个箭头函数, 箭头函数的this会向上级寻找, this指向person2
person1.foo4().call(person2);//改变不了箭头函数 person1
​
js 复制代码
var myObject = {
    foo: "bar",
    func: function () {
        var self = this; //func的调用者
        console.log(this.foo); //等待调用者
        console.log(self.foo);//等待调用者
        (function () {
            console.log(this.foo);//运行时,确定。window.foo不存在
            console.log(self.foo);//
        }());
    }
};
myObject.func();
//bar bar  undefined   bar

普通函数、箭头函数的this指向

js 复制代码
var obj = {
   say: function() { 
     var f1 = () =>  {
       console.log(this); //say的调用者
     }
     f1();
   },
   pro: {
     getPro:() =>  {
        console.log(this);  //window
     }
   }
}
var o = obj.say;  
o();  //window  不是o,也不是obj,o只是函数,没调用执行。
obj.say();  // obj对象
obj.pro.getPro(); //window
  1. 下边就改了let关键字,只是纠正我的认知。
js 复制代码
let obj = { 
    say: function () {
        var f1 = () => {
            console.log(this);  //say的this,say的this
        };
        f1();
    },
    pro: {
        getPro: () => {
            console.log(this);   //位于全局环境
        },
    },
};
var o = obj.say;
o(); //window   
obj.say(); // obj
obj.pro.getPro(); //window。纠正认识:obj 后边的 { }不是let 块级作用域,let的范围是声明的地方开始
9 promise.then抛出错误(第一个参数)(有趣)

then的成功函数里返回错误,不是抛出错误,是被then的成功函数接受,不被catch接受

javascript 复制代码
Promise.resolve().then(() => {
  return new Error('error!!!') //resolve包装 第一个参数 (有return语句)
}).then(res => {
  console.log("then: ", res) //then:  Error: error!!!
}).catch(err => {
  console.log("catch: ", err) //没输出
})
​
// then:  Error: error!!!
vbnet 复制代码
//改成
throw new Error
那么 catch:  Error: error!!!(是catch那里输出)

在这道题中,错误直接被then的第二个参数捕获了,所以就不会被catch捕获了。then捕获了错误,catch只能等待下一个错误。

javascript 复制代码
Promise.reject('err!!!')
  .then((res) => {
    console.log('success', res)
  }, (err) => {
    console.log('error', err)
  }).catch(err => {
    console.log('catch', err)
  })
// error err!!!
promise中then + catch + 抛出错误
javascript 复制代码
Promise.resolve().then(() => {
    console.log('1'); 
    throw 'Error'; //reject  return 才是resolve
}).then(() => {
    console.log('2'); //
}).catch(() => {
    console.log('3');
    throw 'Error';
}).then(() => {
    console.log('4');
}).catch(() => {
    console.log('5');
}).then(() => {
    console.log('6');
});
​
// 1 3  5 6
10 promise的死循环

.then.catch 返回的值不能是 promise 本身,否则会造成死循环。

arduino 复制代码
const promise = Promise.resolve().then(() => {
  return promise;
})
promise.catch(console.err)
​
//Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
11 原型链上的方法 + new (和 快手 题一样)
javascript 复制代码
var F = function() {}; 
Object.prototype.a = function() {
  console.log('a');
};
Function.prototype.b = function() {
  console.log('b');
}
var f = new F(); //f - F -Object  、  Function- Function() -- Object - null
f.a(); //a
f.b();// 不存在
F.a();// a
F.b()// b
12 call第一个参数不传递的情况

根据ECMAScript262规范规定:call方法如果第一个参数传入的对象调用者是null或者undefined,call方法将把全局对象(浏览器上是window对象)作为this的值。 所以,不管传入null 还是 undefined,其this都是全局对象window。所以,在浏览器上答案是输出 window 对象。

js 复制代码
function a() {
    console.log(this); //运行确定
}
a.call(null); //window
a.call(); //window
a.call(undefined); //window

另外

在严格模式中,null 就是 null,undefined 就是 undefined: 没参数 就是 undefined

js 复制代码
"use strict";
​
function a() {
    console.log(this);
}
a.call(null); // null
a.call(undefined); // undefined
a.call(); // undefined
13 有趣的this输出题(对象调用前已经执行)
js 复制代码
function foo() {
  console.log( this.a ); //this,foo的this,看谁调用foo
}
​
function doFoo() {
  foo();   //结果是 2 因为foo是独立执行的。
}
​
var obj = {
  a: 1,
  doFoo: doFoo
};
​
var a = 2; 
obj.doFoo() //2
14 认识下 finally输出

.finally()一般用的很少,只要记住以下几点就可以了:

  • .finally()方法不管Promise对象最后的状态如何都会执行

  • .finally()方法的回调函数不接受任何的参数 ,也就是说你在.finally()函数中是无法知道Promise最终的状态是resolved还是rejected的,它也不用知道,只是最后执行的函数

  • .finally()最终返回的默认会是一个上一次的Promise对象值,不过如果抛出的是一个异常则返回异常的Promise对象

  • finally本质上是then方法的特例。

  • finally本质上是then方法的特例.所以它也会被按照then方法那样,按个去插入微任务队列中执行.而不是像catch那样.

  • 如果在finally回调中返回一个值,这个值会被忽略,不会影响 Promise 的结果,而是会将上一个 Promise 的结果传递给下一个then

    原因是finally的主要目的是执行一些无论 Promise 结果如何都要执行的清理操作或最终步骤,而不是用来改变 Promise 的结果状态。

js 复制代码
Promise.resolve('1')
  .then(res => {
    console.log(res)
  })
  .finally(() => {
    console.log('finally')
  })
Promise.resolve('2')
  .finally(() => {
    console.log('finally2')
    return '我是finally2返回的值' //  这里会将上一个promise的2返回,除非抛出的是异常才能返回异常的Promise对象
  })
  .then(res => {
    console.log('finally2后面的then函数', res)
  })
/*
1
finally2
finally
finally2后面的then函数 2
*/
15 async事件循环
js 复制代码
async function async1 () {
  console.log('async1 start');
  await new Promise(resolve => {
    console.log('promise1') // promise没有resove,后边then执行不了。不像then方法,没有显式resolve也能下一个then
  })
  console.log('async1 success');
  return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')
​
//srcipt start 、async1 start 、promise1、srcipt end
javascript 复制代码
async function async1 () { //也是同步,但是要等执行
  console.log('async1 start');
  await new Promise(resolve => { //await里的代码也要执行,直到遇到微/宏任务
    console.log('promise1')
    resolve('promise1 resolve')
  }).then(res => console.log(res))//微任务第一个
    
  console.log('async1 success');//await后所有放入包装成promise.then(包括return语句) 微任务第二个
  return 'async1 end'   //相当resolve(xxxx)
}
console.log('srcipt start') 
async1().then(res => console.log(res)) //微任务第三个
console.log('srcipt end')
​
//script start、async1 start、promise1、script end、promise1 resolve、async1 success、async1 end
js 复制代码
async function async1() {
    await Promise.reject('error!!!').catch(e => console.log(e)) //catch也是微任务
    console.log('async1');
    return 'async1 success'  //相当resolve
}
async1().then(res => console.log(res))
console.log('script start')
//script start、error!!!、async1、async1 success
14 all 结合 catch

因为promise.all返回的promise本来是等所有接收的promise成功后才决议状态为成功,但遇到了失败的promise,所以提前就决议失败了。promise遇到失败就不等待全部成功,提前结束

js 复制代码
function runAsync (x) {
  const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
  return p
}
function runReject (x) {
  const p = new Promise((res, rej) => setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x))
  return p
}
Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)]) // 1是成功、all就还不返回
       .then(res => console.log(res)) //成功才执行
       .catch(err => console.log(err))
javascript 复制代码
// 1s后输出
1
3
// 2s后输出
2
Error: 2
// 4s后输出
4

相当于

js 复制代码
function runAsync(x) {
  const p = new Promise((resolve) => {
      setTimeout(() => {
          console.log(x);
          resolve(x);
      }, 1000);
  });
  return p;
}
​
function runReject(x) {
  const p = new Promise((_, reject) => {
      setTimeout(() => {
          console.log(x);
          reject(`Error: ${x}`);
      }, 1000 * x);
  });
  return p;
}

Promise.race() 静态方法接受一个 promise 可迭代对象作为输入,并返回一个 Promise。这个返回的 promise 会随着第一个 promise 的敲定而敲定。

js 复制代码
function runAsync(x) {
  const p = new Promise(r =>
    setTimeout(() => r(x, console.log(x)), 1000)
  );
  return p;
}
function runReject(x) {
  const p = new Promise((res, rej) =>
    setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x)
  );
  return p;
}
Promise.race([runReject(0), runAsync(1), runAsync(2), runAsync(3)])
  .then(res => console.log("result: ", res))
  .catch(err => console.log(err));
​
​
//0 、Error: 0、1、2、3(但是 1 2 3 定时器已经执行)
js 复制代码
function runAsync(x) {
    const p = new Promise(r =>
        setTimeout(() => r(x, console.log(x)), 1000)
    );
    return p;
}
function runReject(x) {
    const p = new Promise((res, rej) =>
        setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x)
    );
    return p;
}
​
Promise.race([runReject(0), runAsync(1), runAsync(2), runAsync(3)])
    .then(res => console.log("result: ", res))
    .catch(err => console.log(err)) //catch不返回结果传递
    .then(res => console.log("result: ", res))  //扩展
​
//0 、 Error: 0 、result:  undefined 、 1 、2 、3
20 Promise+定时器+then+finnaly+状态判断(简单有趣)
js 复制代码
const p1 = new Promise((resolve) => {
    setTimeout(() => {
        resolve('resolve3');
        console.log('timer1')
    }, 0)
    resolve('resovle1');//
    resolve('resolve2');
}).then(res => {
    console.log(res)  // resolve1
    setTimeout(() => {
        console.log(p1)  //返回的是finnaly的promise,finally的promise是上一个的then的promise
    }, 1000)
}).finally(res => {
    console.log('finally', res) //then没有传 具体的promise值,视为undefined
})
​
/*
resolve1
finally  undefined
timer1
Promise{<resolved>: undefined}  p1 = 最终结果,但是状态是刚开始确定的
*/
21 函数作用域经典题目
js 复制代码
var a = 10;
(function a() {
    a = 20;
    console.log(a); //输出函数a的内容   
})()

关键点解析:

  1. 这里的函数a是一个具名函数表达式,在其函数作用域内部,变量a实际上是不可修改的
  2. 当我们试图在函数内部对a赋值(a = 20)时,这个操作会被忽略
  3. 当console.log(a)执行时,由于函数内部的a指向函数本身,且这个绑定是不可修改的,所以会打印出函数自身的定义

分析其他选项: A错误:虽然外部定义了var a = 10,但在IIFE内部的a指向函数本身,不会输出10 B错误:a = 20的赋值操作不会生效,因为在函数作用域内a是不可改变的 C错误:由于a指向函数本身,所以不会是undefined

这种行为是因为在函数表达式内部,函数名称标识符是固定指向函数本身的常量,类似于一个不可修改的const声明。这是JavaScript引擎的特殊处理机制,目的是保持函数引用的稳定性。

js 复制代码
var a = 10;
(function b() {
    a = 20;
    console.log(a); //20
})() 
相关推荐
6武71 分钟前
Vue 数据传递流程图指南
前端·javascript·vue.js
jakeswang1 小时前
查询条件与查询数据的ajax拼装
前端·ajax
samuel9181 小时前
axios取消重复请求
前端·javascript·vue.js
三天不学习1 小时前
JiebaAnalyzer 分词模式详解【搜索引擎系列教程】
前端·搜索引擎·jiebaanalyzer
滿1 小时前
Vue 3 中按照某个字段将数组分成多个数组
前端·javascript·vue.js
安分小尧1 小时前
[特殊字符] 使用 Handsontable 构建一个支持 Excel 公式计算的动态表格
前端·javascript·react.js·typescript·excel
好_快1 小时前
Lodash源码阅读-baseClone
前端·javascript·源码阅读
Double Point1 小时前
(三十一) Dart 中的网络请求教程:从知乎日报 API 获取数据
前端
excel1 小时前
webpack 核心编译器 十二 节
前端