js看代码说输出【原型、闭包、this、变量提升、setTimeout、promise、async】

目录

原型

Function与Object

自身找不到才会去原型链上找

new fn()

原型链

constructor

function.length

默认参数:第一个具有默认值之前的参数个数

剩余参数:不算进length

闭包

函数工厂:通过形参传递来创建其他函数的模式

this+闭包

this

bind永久绑定this,apply/call也不能改变

JS预解析/编译(变量提升)

let

暂时性死区

=优先级:从右到左

setTimeout

原因:var变量提升到全局,可以重复声明

解决

A.专用:setTimeout(functionRef, delay, param...)

B.通用

a.立即执行函数IIFE传参

b.let/forEach():每次迭代i是新变量,新块级作用域

输出顺序Event loop

async、await事件轮询执行时机

async隐式返回Promise

await xx;yy=promise(xx).then(yy)微任务

await pending promise

Node中的process.nextTick

process.nextTick执行顺序早于微任务

promise

pending

new promise 初始为pengding,不存在为null的情况

return  error≠throw error,所以不会被捕获错误

then链式调用

连续3个以上的then链式调用

string

str[i]=赋值

str.indexOf('',i):i

arr

float运算

n=n++

缓存原值,自增,用缓存的原值进行运算

从左到右解析,能组成符号就组


原型

Function与Object

var F = function() {};

Object.prototype.a = function() {
  console.log('a');
};

Function.prototype.b = function() {
  console.log('b');
}
 
var f = new F();

f.a(); // a
f.b(); // f.b is not a function

F.a(); // a
F.b(); // b

Function.prototype === Function.__proto__; // true

Function本身也是函数,所以 Function是Function的实例

注:箭头表继承

Object⬅️F(F类的构造函数)⬅️f(f是F类实例)

⬆️

Function

⬆️

F(Function实例)

由函数Function实例F创建的实例f不再是函数Function

自身找不到才会去原型链上找

var a=1;
function fn1(){
  var a=2;
  console.log(this.a+a);
}

var fn3=function(){
  this.a=3;
}
//自身找不到才会去原型链上找,可以理解为替补
fn3.prototype={
  a:4
}
var fn33=new fn3();
fn1.call(fn33)
//5

new fn()

function A() {}
function B(a) {
    this.a = a;
}
function C(a) {
    if (a) {
        this.a = a;
    }
}
A.prototype.a = 1;
B.prototype.a = 1;
C.prototype.a = 1;

console.log(new A().a); //1
console.log(new B().a); //undefined(传入a为undefined)
console.log(new C(2).a);//2
console.log(new C().a); //1

原型链

123['toString']:在数字 123 上使用方括号访问属性

数字本身没有toString方法,则沿着__proto__function Number()prototype上找,找到toString方法,toString方法的length是1

numObj.toString([radix])

console.log(123['toString'].length + 123) // 124
function fun(){
    this.a = 0
    this.b = function(){
        console.log("自己的b:",this.a)
    }
}

fun.prototype = {
    b: function(){
        this.a = 20
        console.log("原型链b:",this.a)
    },
    c: function (){
        this.a = 30
        console.log(this.a)
    }
}

var my_fun = new fun()

my_fun.b()    // 0
my_fun.c()    // 30
function Foo() {
    getName = function (){
        console.log(1)
    }
    return this
}

Foo.getName = function () {
    console.log(2)
}

Foo.prototype.getName = function(){
    console.log(3)
}

Foo.getName()//2

Foo().getName();//1

getName();//1:getName函数变量提升到全局

new Foo.getName()//2 Foo函数有对象有个getName(...2)属性方法
//先对 new Foo() 实例化再对 A.getName() 调用,
//对 new Foo() 实例化调用的 getName() 方法是原型 prototype 上的
new Foo().getName()//3  
// new new Foo().getName() => new B.getName(),
//先对 new Foo() 实例化再对 new B.getName() 实例化,
//new B.getName() 同时也在执行 B.getName() 方法输出的还是实例 B 上的方法也就是原型 prototype 的 getName
new new Foo().getName()//3

constructor

f1,f2中本没有 constructor 但是会从构造函数的 prototype 中查找相当f1.prototype.constructorf2的原型被重新定义了指向基类 object

找不到的,只会往上找,而非往下,所以原型上不存在n

function Fn(){
    var n = 10
    this.m = 20
    this.aa = function() {
        console.log(this.m)
    }
}

Fn.prototype.bb = function () {
    console.log("原型的this.n",this.n)
}

var f1 = new Fn

Fn.prototype = {
    aa: function(){
        console.log(this.m + 10)
    }
}

var f2 = new Fn

//注意区别修改原型Fn.prototype和修改原型的属性Fn.prototype.bb
console.log(f1.constructor)     // ==> function Fn(){...}
console.log(f2.constructor)     // ==> Object() { [native code] }

//原型中
f1.bb()    // n是 undefined
//自己有aa方法,就不去原型链上找了
f1.aa()    // 20
f2.aa()    // 20
//原型链上的aa方法中,原型没有m属性,undefined+10=NaN
f2.__proto__.aa()    // NaN
f2.bb()     //  Uncaught TypeError: f2.bb is not a function

function.length

默认参数:第一个具有默认值之前的参数个数

function fn1 (name) {}

function fn2 (name = '林三心') {}

function fn3 (name, age = 22) {}

function fn4 (name, age = 22, gender) {}

function fn5(name = '林三心', age, gender) { }

console.log(fn1.length) // 1
console.log(fn2.length) // 0
console.log(fn3.length) // 1
console.log(fn4.length) // 1
console.log(fn5.length) // 0

剩余参数:不算进length

function fn1(name, ...args) {}

console.log(fn1.length) // 1

闭包

var ary = [1, 2, 3, 4]
function fn(i){
    return function(n){
        console.log(n+ (i++))
    }
}

var f = fn(10)
f(20)   // 30       (n+10)
//fn(10)被f引用,未被释放
f(20)   // 31       (n+11)

//就算都是fn(10),也只是函数体相同但地址不同的两个函数
fn(10)(20)  // 30 
//fn(10)执行完后就释放了
fn(10)(20)  // 30

// console.log(i)  //   Uncaught ReferenceError: i is not defined

函数工厂:通过形参传递来创建其他函数的模式

function createMultiplier(factor) {
    // 返回一个新函数,这个函数将传入的参数与 factor 相乘
    return function (number) {
        return number * factor;
    };
}

// 创建一个乘以 2 的函数
const double = createMultiplier(2);

// 创建一个乘以 3 的函数
const triple = createMultiplier(3);

// 使用这些函数
console.log(double(5));  // 输出 10,因为 5 * 2 = 10
console.log(triple(5));  // 输出 15,因为 5 * 3 = 15

this+闭包

var num = 10    // 60; 65
var obj = {
    num: 20    
}
//自执行obj.fn= function(n){this.num+=n...}
obj.fn = (function (num){
    this.num = num * 3  // 调用者是window,window.num=20*3
    num++    // 21 
    return function(n){
        this.num += n    // 60 + 5 = 65;20 + 10 =30
        num++   // 21 + 1 = 22;22 + 1 = 23 闭包引用num
        console.log(num)
    }
})(obj.num)

var fn = obj.fn 

fn(5)   // 22  this 指向 window

obj.fn(10)   // 23  this 指向 obj

console.log(num, obj.num)    // 65, 30

this

this.count=1
function func() {
  console.log(++this.count)
}

func.count = 0
func()//2

bind永久绑定this,apply/call也不能改变

obj = {
  func() {
    //箭头函数继承调用func()的this
    const arrowFunc = () => {
      console.log(this._name)
    }

    return arrowFunc
  },

  _name: "obj",
}

//仅赋予函数体内容
func = obj.func
//调用者为全局,全局下没有“_name”属性
func()()//undefined

obj.func()()//obj
//bind绑定但不调用
obj.func.bind({ _name: "newObj" })()()//newObj
//apply绑定并调用
obj.func.apply({ _name: "newObj" })()//newObj
//传入空this
obj.func.bind()()()//undefined
//无论这个新函数如何被调用,this 被永久绑定到 { _name: "bindObj" } 
obj.func.bind({ _name: "bindObj" }).apply({ _name: "applyObj" })()//bindObj


JS预解析/编译(变量提升)

//虽然按顺序创建作用域,不会报错a为声明
function foo() {
  console.log(a);
}

function bar() {
  var a="bar"
  foo(); 
}

bar(); //undefined
var a="window"
var a = 1;
function foo(a, b) {
  console.log(a); // 1
  a = 2;
  arguments[0] = 3;
  var a;
  console.log(a, this.a, b); // 3, 1, undefined
}
foo(a);

let

暂时性死区

function test() {
  var foo = 33;
  if (foo) {// var foo 
    //foo+55是let 的 foo
    let foo = foo + 55; // ReferenceError
  }
}
test();

标识符 n.a 被解析为位于指令(let n)本身的 n 对象的属性 a。因为 n 的声明尚未执行结束,它仍然处于暂时性死区内

function go(n) {
  // n 在此处被定义
  console.log(n); // { a: [1, 2, 3] }

  for (let n of n.a) {
    //          ^ ReferenceError
    console.log(n);
  }
}

go({ a: [1, 2, 3] });

=优先级:从右到左

= 优先级是从右到左的,所以变量提升阶段 b=undefined后,将 c 赋值成 undefined

var b = {
    a,
    c: b
}
console.log(b.c);
//undefined

setTimeout

原因:var变量提升到全局,可以重复声明

for(var i=0;i<2;i++){
    setTimeout(()=>{console.log(i)},1000)//3,3
}
for(var i=0;i<3;i++){
    setTimeout(()=>{console.log(i)},1000)//3,3,3
}
console.log(i)//3

解决

A.专用:setTimeout(functionRef, delay, param...)

// 利用setTimeout的第三个参数,第三个参数将作为setTimeout第一个参数的参数
for (var i = 0; i < 5; i++) {
  setTimeout(function fn(i) {
    console.log(i);
  }, 1000, i); // 第三个参数i,将作为fn的参数
}

B.通用

a.立即执行函数IIFE传参
//输出0,1,2,3,4
//立即执行函数传参,就不会引用var i了
for (var i = 0; i < 5; i++) {
  (function(j) {
    setTimeout(function timer() {
      console.log(j);
    }, 1000);
  })(i);
  //等效于
  setTimeout((function(i){
    return () => console.log(i);
  })(i),1000)
}
b.let/forEach():每次迭代i是新变量,新块级作用域
for(let i=0;i<2;i++){
    setTimeout(()=>{console.log(i)},1000)//0,1
}

输出顺序Event loop

//宏任务队列:[]
//微任务队列:[promise0]
Promise.resolve()
  .then(function() {
    console.log("promise0");
  })
  .then(function() {
    console.log("promise5");
  });
//定时的setTimeout(delay=0)=setImmediate:下个Event Loop执行
//宏任务队列:[timer1]
//微任务队列:[promise0]
setTimeout(() => {
  console.log("timer1");
  
  Promise.resolve().then(function() {
    console.log("promise2");
  });
  Promise.resolve().then(function() {
    console.log("promise4");
  });
}, 0);
//宏任务队列:[timer1,timer2]
//微任务队列:[promise0]
setTimeout(() => {
  console.log("timer2");
  Promise.resolve().then(function() {
    console.log("promise3");
  });
}, 0);
//宏任务队列:[timer1,timer2]
//微任务队列:[promise0,promise1]
Promise.resolve().then(function() {
  console.log("promise1");
});
//执行start
console.log("start");
//执行当前所有微任务队列:[promise0,promise1]
//执行promise0时将promise5放入了微任务队列:[promise1,promise5]
//接着执行微任务队列:输出promise1,promise5
//当微任务队列为空,开始执行宏任务队列[timer1,timer2]队首的timer1
//执行timer1时碰到了微任务promise2,放进微任务队列[promise2]
//宏任务timer1执行完了,开始执行所有当前所有微任务:[promise2]
//执行promise2完碰到微任务promise4,放进微任务队列:[promise4]
//当前微任务队列不为空,接着执行promise4
//微任务队列为空,接着执行宏任务队列队首[timer2]
//执行timer2时碰到了微任务promise3,放进微任务队列[promise3]
//宏任务timer2执行完了,开始执行所有当前所有微任务:[promise3]


// 打印结果: start promise0 promise1 promise5 timer1 promise2 promise4 timer2 promise3

async、await事件轮询执行时机

async隐式返回Promise


await xx;yy=promise(xx).then(yy)微任务


//1.script start(同步)
console.log("script start");

async function async1() {
  await async2(); // await 隐式返回promise
  console.log("async1 end"); // 这里的执行时机:在执行微任务时执行
}

async function async2() {
  console.log("async2 end"); // 这里是同步代码
}
//2.async2 end(同步)
//微任务队列:[async1 end]
async1();
//宏任务队列:[setTimeout],setTimeOut进入下一loop
setTimeout(function() {
  console.log("setTimeout");
}, 0);
//3.Promise(同步)
//宏任务队列:[setTimeout]
//微任务队列:[async1 end,promise1]
new Promise(resolve => {
  console.log("Promise"); // 这里是同步代码
  resolve();
})
  .then(function() {
    console.log("promise1");
  })
  .then(function() {
    console.log("promise2");
  }); 
//4.script end(同步)
console.log("script end");
//当前loop的宏任务(都是同步代码)都执行完毕
//执行所有微任务[async1 end,promise1]
//执行promise1完后碰到了promise2,加入微任务队列,接着执行
//当前所有微任务都执行完毕,开始执行宏任务队列[setTimeout]

// 打印结果:  script start => async2 end => Promise => script end => async1 end => promise1 => promise2 => setTimeout

await pending promise

async function async1 () {
  console.log('async1 start');
  await new Promise(resolve => {
    console.log('promise1')
  })
  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"

Node中的process.nextTick

process.nextTick执行顺序早于微任务

console.log("start");
//定时进入下一loop,宏任务队列:[timeout]
setTimeout(() => {
  console.log("timeout");
}, 0);
//微任务队列:[promise]
Promise.resolve().then(() => {
  console.log("promise");
});
//process.nextTick在微任务队首
//微任务队列:[nextTick,promise]
process.nextTick(() => {
  console.log("nextTick");
  Promise.resolve().then(() => {
    console.log("promise1");
  });
});
console.log("end");
// 执行结果 start end nextTick  promise promise1 timeout 

promise

pending

1.pengding时,then()收集依赖,将成功/失败回调放入成功/失败队列

2.触发resolve/reject,改变pending,从成功/失败队列中取出回调依次执行

const promise = new Promise((resolve, reject) => {
  console.log(1);
  console.log(2);
});
promise.then(() => {
  console.log(3);
});
console.log(4);
//124

new promise 初始为pengding,不存在为null的情况

const promise1 = new Promise((resolve, reject) => {
  console.log('promise1')
  resolve('resolve1')
})
const promise2 = promise1.then(res => {
  console.log(res)
})
console.log('1', promise1);
console.log('2', promise2);

'promise1'
'1' Promise{<resolved>: 'resolve1'}
'2' Promise{<pending>}
'resolve1'

return  error≠throw error,所以不会被捕获错误

then链式调用

new Promise((resolve, reject) => {
    resolve('成功了')
})
    .then(
        (data) => { console.log('onResolved1', data); },
        (error) => { console.log('onRejected1', error); }
    )
    .then(
        (data) => { console.log('onResolved2', data); },
        (error) => { console.log('onRejected2', error); }
    )
> "onResolved1" "成功了"
> "onResolved2" undefined

因为回调函数无返回值,所以resolve(x)中的x为undefined

// MyPromise.js

class MyPromise {
  ...
  then(onFulfilled, onRejected) {
    // 为了链式调用这里直接创建一个 MyPromise,并在后面 return 出去
    const promise2 = new MyPromise((resolve, reject) => {
      // 这里的内容在执行器中,会立即执行
      if (this.status === FULFILLED) {
        // 获取成功回调函数的执行结果
        const x = onFulfilled(this.value);
        // 传入 resolvePromise 集中处理
        resolvePromise(x, resolve, reject);

      } ...
    }) 
    
    return promise2;
  }
}

function resolvePromise(x, resolve, reject) {
  // 判断x是不是 MyPromise 实例对象
  if(x instanceof MyPromise) {
    // 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
    // x.then(value => resolve(value), reason => reject(reason))
    // 简化之后
    x.then(resolve, reject)
  } else{
    // 普通值
    resolve(x)
  }
}

连续3个以上的then链式调用

Promise.resolve().then(() => {
    console.log(0);
    return Promise.resolve(4);
}).then((res) => {
    console.log(res)
})

Promise.resolve().then(() => {
    console.log(1);
}).then(() => {
    console.log(2);
}).then(() => {
    console.log(3);
}).then(() => {
    console.log(5);
}).then(() =>{
    console.log(6);
})

// 0123456

return Promise.resolve(4);

x 等于 realOnFulfilled(this.value) 的执行结果,也就是 return 出来的 MyPromise.resolve(4),所以在 x 传入 resolvePromise 方法中进行类型判断时,会发现它是一个 Promise 对象(存在 then 方法),并让其调用 then 方法完成状态转换

// MyPromise.js

// 获取成功回调函数的执行结果
const x = realOnFulfilled(this.value);
// 传入 resolvePromise 集中处理
resolvePromise(promise2, x, resolve, reject);

Js引擎为了让microtask尽快的输出,做了一些优化,连续的多个then(3个)如果没有reject或者resolve会交替执行then而不至于让一个堵太久完成用户无响应,不单单v8这样其他引擎也是这样,因为其实promuse内部状态已经结束了。这块在v8源码里有完整的体现

string

str[i]=赋值

在JavaScript中,字符串是不可变的(immutable),str是基本数据类型,或者string对象,String的方法都是返回新值

可变只有数组和对象,不可变可以带来性能和安全性的好处

let str=new String("123")
str[0]="z"
console.log(str[0])//1

str.indexOf('',i):i

let str = "Hello";
let index = str.indexOf("",0);
console.log(index); // 输出 0
 index = str.indexOf("",3);
console.log(index); // 输出 3
 index = str.indexOf("",6);
console.log(index); // 输出 5

arr

var arr = [];
arr[''] = 1;
console.log(arr); // []

arr[2] = 2;
console.log(arr); // [undefined, undefined, 2]

arr.length = 0;
console.log(arr); // []

float运算

console.log(0.1+0.2);//0.30000000000000004
num.toFixed(1);

n=n++

缓存原值,自增,用缓存的原值进行运算

从左到右解析,能组成符号就组

编译器会从左到右一个字符一个字符解析,如果已解析的字符已经能够组成一个符号,再解析下一个字符,判断下一个字符能否和已解析出的符号再次组合成一个符号,如果能,再不断重复如上过程;如果不能,则使用最终解析出的符号。

var n=1
n=n++
console.log(n)//1

//等价于
var n=1;
//n=n++运算开始
var temp=n;    //编译器解析到“n=n++”中的“n++”后,会先在内存中缓存n的原值,用来参与其他运算
n = n+1;       //编译器解析到“n=n++”中的“=”后,会在做“=”运算前将n自加
n = temp;      //变量自加结束后,用缓存的原值进行“=”运算
//n=n++运算结束
console.log(n) //1

var a=3,b;
b=a++*a++;
console.log(b)    //12

var a=3,b;
b=a+++a;        //b=(a++)+a, b=3+4
console.log(b)  //7

参考链接:JS 经典面试题初篇(this, 闭包, 原型...)含答案 - 掘金

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值