前端面试题——JS高级

文章目录

    • 数据类型转换
    • 作用域、let、var
    • 闭包
    • 函数
    • this
    • 原型链
    • 事件循环
      • 运行机制
      • 详细的事件循环顺序:
      • 微任务和宏任务分类

数据类型转换

// 1
let result1 = 100 + true + 21.2 + null + undefined + "Tencent" + [] + null + 9 + false;
console.log(result1);
// NaNTencentnull9false

// 2
let a = "?";
if (a == 1 && a == 2 && a == 3) {
    console.log("OK")
};
/*
let a = {
num: 0,
    toString: function () {
        return this.num += 1;
    }
};
*/

// 3
let arr = [27.2, 0, '0013', '14px', 123];
result3 = arr.map(parseInt);
console.log(result3);
// 27,NaN,1,1,27

作用域、let、var

let a = 123;
function fn1(){
  console.log(a);
}
function fn2(){
  let a = 456;
  fn1();
}
fn2(); // => 123
// function 和 全局变量一样都被提前声明,函数保存了父级环境,
// 在fn2中调用fn1会在自身作用域下寻找a,如果没有会向上寻找父级作用域,其父级为全局
for (let i = 0; i < 5; i++) {
  setTimeout(function())  {
    console.log(i);
  });
}
// => 01234
// 上面的for意思是利用循环开启了5个定时器  即使定时器不会立刻执行 
// 但let定义的变量存在自己的作用域最后还是输出01234  
for (var i = 0; i < 5; i++) {
  setTimeout(function())  {
    console.log(i);
  });
}
// => 55555
// 计时器是异步进行的定时器是异步执行的 
// 下面的for循环也是用循环开启5个定时器,但是定时器不会立刻触发,由于定时器是异步执行的
// 所以当for循环执行完才会触发定时器,for循环执行完i的值会变成5,并且最后的i值会覆盖前面的值所以是55555
if (! "a" in window) {
  console.log("a");
  var a = 1;
}
if (! "b" in window) {
  console.log("b");
	let b = 1;
}
console.log(a);  // undefined
console.log(b);  // Uncaught ReferenceError: b is not defined

闭包

闭包,保存一份变量

function fun () {
	var i = 0;
  return function () {
    console.log(i++);
  }
}

var f1 = fun();
var f2 = fun();
f1(); // 0
f1(); // 1
f2(); // 0

// f1、f2 产生闭包,各自保存一份变量 先打印再自增
// f1调用两次 里面i是闭包保存的同一个i,所以第二个打印1,当然打印1后i=2
f1() // 2

函数

命名函数只能在函数自身内部使用,在外使用会报错

var foo = function bar() {
  return 123;
};

console.log(typeof foo);   // function
console.log(typeof foo()); // number
console.log(typeof bar()); // error
console.log(typeof bar);   // error

// 第三个命名函数只能在函数自身内部使用,在外使用会报错

console.log(foo);
console.log(foo());
console.log(bar());
console.log(bar);

this

this(除箭头函数)永远指向当前正在运行的对象
即:非箭头函数指向调用它的对象,就是离它最近的,箭头函数本身没有this,其this指向离它最近的非箭头函数的作用域

var obj = {
	a = 1,
  foo: function() {
  	return this.a;
	}
}
var fun = obj.foo;
console.log(obj.foo());  // 1     this=>obj 
console.log(fun());      // undfined  复制了fun的方法,该方法调用实质是挂载到window上,window并未声明a

原型链

1 直接通过标识符访问变量,首先沿作用域查找每一个变量对象,
直到全局变量对象仍然没有,就沿全局变量的原型链查找
2 通过. 或[] 访问对象中的标识符,就直接沿原型链查找

function A(x) {
  this.x = x;
}

function B(x) {
  this.x = x;
}


A.prototype.x = 1;
B.prototype = new A();
var a = new A(2);
var b = new B(3);
delete b.x;

console.log(a.x);  // => 2
console.log(b.x);  // => undefined
// a x = 2
// b x = 3,但是delete删除了自身属性,x的值取原型链上x的值,
// 即new A()时, x = undefined因为未传参

// 1 直接通过标识符访问变量,首先沿作用域查找每一个变量对象,
// 直到全局变量对象仍然没有,就沿全局变量的原型链查找

// 2 通过. 或[] 访问对像中的标识符,就直接沿原型链查找
const obj = {
  flag: false,
}
function A() {
  this.flag = true;
  return obj;
}
const a = new A();
console.log(a.flag); // => false


// new关键字创建一个新对象{},新对象原型链指向构造函数A的prototype,新创建的对象赋值给this,
// 并执行构造函数里面的代码给this做赋值初始化,最后返回创建的对象this。
// 如果构造函数A没有返回值或返回简单类型,则返回this,因为构造函数A返回了obj,所以a===obj
// Function.prototype.__proto__ == Object.prototype

事件循环

事件循环:微任务和宏任务

每个宏任务之后,引擎会立即执行微任务队列中的所有任务,然后再执行其他的宏任务,或渲染,或进行其他任何操作。

运行机制

在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:

  • 执行一个宏任务(栈中没有就从事件队列中获取)
  • 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
  • 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
  • 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
  • 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)

详细的事件循环顺序:

  1. 从宏任务队列中出队最先插入的事件,依次执行,执行完之后
  2. 执行所有的微任务,直到微任务队列为空。 // 执行为空之后,假如宏队列有新的任务,继续执行宏任务,不会先走渲染,因为 js 执行引擎
  3. 执行渲染,假如有的话。

顺序:宏任务-微任务-渲染(如果需要渲染,比如到了时间,或有更改dom),而不是再次执行宏任务

微任务和宏任务分类

微任务包含:1、“Promise”;2、“Object.observe”;3、“MutationObserver”;4、Node.js环境下的“process.nextTick”;5、“async/await”。

// 微任务
script(整体代码)
setTimeout
setInterval
I/O
UI交互事件
postMessage
MessageChannel
setImmediate(Node.js 环境)

// 宏任务
Promise
Object.observe
MutationObserver
process.nextTick(Node.js 环境)
async/await
console.log(1);
setTimeout(() => {
  console.log(2);
});
console.log(3);
Promise.resolve(4).then(b => {
  console.log(b);
});
console.log(5);

// => 13542
// 先执行同步代码,输出135
// 在执行微任务Promise,输出4
// 再执行宏任务setTimeout,输出2
// js代码执行流程,先执行同步代码,再清空(执行)微任务.then,最后是宏任务。

console.log(1);
setTimeout(() => console.log(2));
Promise.resolve().then(() => console.log(3));
Promise.resolve().then(() => setTimeout(() => console.log(4)));
Promise.resolve().then(() => console.log(5));
setTimeout(() => console.log(6));
console.log(7);
// => 1 7 3 5 2 6 4

// 立即输出数字 1 和 7,因为简单的 console.log 调用没有使用任何队列。
// 然后,主代码流程执行完成后,开始执行微任务队列。
// 其中有命令行:console.log(3); setTimeout(...4); console.log(5)。
// 输出数字 3 和 5,setTimeout(() => console.log(4)) 将 console.log(4) 调用添加到了宏任务队列的尾部。
// 现在宏任务队列中有:console.log(2); console.log(6); console.log(4)。
// 当微任务队列为空后,开始执行宏任务队列。并输出 2、6 和 4。

你可能感兴趣的:(JS,前端,javascript,前端,开发语言,面试)