【面试题系列】必须掌握的JS面试题

举报
猫先生c 发表于 2023/09/28 09:15:02 2023/09/28
【摘要】 1、原型和原型链当我们找实例对象的属性时,如果找不到,就会查找与对象关联的原型中去找,如果还找不到,就去找原型的原型,直到最顶层。function A() {}function B(a) { this.a = a;}function C(a) { if (a) { this.a = a; }}A.prototype.a = 1;B.prototype.a =...

1、原型和原型链

当我们找实例对象的属性时,如果找不到,就会查找与对象关联的原型中去找,如果还找不到,就去找原型的原型,直到最顶层。

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);
console.log(new B().a);
console.log(new C(2).a);

答案:1 undefined 2

解析:


//声明一个构造函数A
function A() {}
//声明一个构造函数B,传人参数a,自有属性a的值取决于传入的参数
function B(a) {
    this.a = a;
}
//声明一个构造函数B,如果有参数,则添加自有属性a,属性a的值为传入的参数值,
//如果没有传入参数,则构造函数C没有自有属性
function C(a) {
    if (a) {
        this.a = a;
    }
}
A.prototype.a = 1;
B.prototype.a = 1;
C.prototype.a = 1;
//构造函数function A(){},是没有自有属性的,然后就会顺着原型链查找,我们找到构造函数A的原型对象A.prototype上有属性a且等于1
console.log(new A().a);//1
console.log(new B().a);//undefined
console.log(new C(2).a);//2

2、异步队列

微任务:Promise.then、async/await、MutaionObserver
宏任务:script(整体)、setTimeout、setInterval、Ajax、DOM事件、postMessage
执行顺序如下:
在这里插入图片描述

  • 第一题
  async function async1() {
    console.log("a");
    const res = await async2();
    console.log("b");
   }
    async function async2() {
     console.log("c");
     return 2;
    }

    console.log("d");
    setTimeout(() => {
      console.log("e");
    }, 0);
    async1().then(res => {
    console.log("f")
    })
    new Promise((resolve) => {
      console.log("g");
      resolve();
    }).then(() => {
    console.log("h");
    });
  console.log("i");

答案:d、a、c、g、i、b、h、f、e

解析:

//打印d
//遇到setTimeout,放到宏任务队列
//遇到async1().then(),先执行async1(),then函数注册到微任务队列(但不是立即注册,而是等async1()执行完之后才会注册)
//在执行async1()时候
  async1().then(res => {
      console.log("f")
  })
//打印a
//执行async1()时,当执行到 const res = await async2()时,await后先让后面的表达式先执行,也就是async2()
//打印c
//然后将其后面的代码放到微任务队列中,然后跳出async函数,执行后面的代码,console.log("c")被放到了微任务队列中
//执行new Promise((resolve) => { console.log("g"); resolve();  }).then(),由于new Promise()是同步任务
//打印g
//new Promise后的then函数放到微任务队列
//打印i
//此时微任务队列[console.log("b"),console.log("h")][console.log("f")]
//宏任务队列[console.log("e")]
//先执行微任务,再执行宏任务
//打印g、f、e
  • 第二题
setTimeout(function(){
    console.log(1)
  },0)

  new Promise(function(resolve,reject){
      console.log(2)
      for(var i=0;i<10000;i++) {
          if(i===10){
              console.log(10);
          }
          i==9999 && resolve()
      }
      console.log(3);
  }).then(function(){
      console.log(4);
  })
  console.log(5);

答案:2 10 3 5 4 1

解析

//setTimeout放到宏任务队列
//执行 new Promise()同步任务
//打印2
//打印10
//打印3
// new Promise()后的then函数 放到微任务队列
//打印5
微任务队列:[console.log(4)]
宏任务队列:[ console.log(1)]
  • 第三题
Promise.reject(1).
			then(2).
			then(res => { return 3 }).
			catch(error => { return 4 }).
			then(Promise.resolve(5)).
			then(console.log)

答案:4
解析:

//Promise.reject(1) 返回一个被拒绝的 Promise,其拒绝原因为数字 1。
//.then(2) 返回一个新的 Promise,它的回调函数不会被调用,因为前一个 Promise 被拒绝了。
//.then(res => { return 3 }) 返回一个新的 Promise,它的回调函数也不会被调用,因为前一个 Promise 被拒绝了。
//.catch(error => { return 4 }) 返回一个新的 Promise,它的回调函数被调用并返回数字 4(作为一个被解决的 Promise)。
//.then(Promise.resolve(5)) 返回一个新的 Promise,它的回调函数不会被调用,因为参数不是一个函数。
//.then(console.log) 返回一个新的 Promise,它的回调函数被调用并打印数字 4。
  • 第四题
var F = function() {};
Object.prototype.a = function() {
  console.log("a()");
};
Function.prototype.b = function() {
  console.log("b()");
};
var f = new F();
F.a();
F.b();
f.a();
f.b();

答案:a b a f.b is not a function

解析:

//1、声明变量F,F既是对象也是函数,故F可调用原型上的方法,无论是对象还是函数上的
//2、f = new F(),通过 构造函数 F new了一个实例f,此f只是一个对象并不是函数类型,故不能调用Function原型上的方法
//证明:

在这里插入图片描述

3、浅拷贝/深拷贝

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存
深拷贝生成一个新对象,新对象跟原对象不共享内存,修改新对象不会改到原对象

  • 第一题
var obj1 = {
    a:  1,
    b:  2,
    c:  3
}
var obj2 = obj1;
obj2.a = 5;
console.log(obj1.a);  
console.log(obj2.a);

答案:1 5

解析

//var obj2 = obj1属于浅拷贝,故而obj1,obj2共用一个内存,打印出5,5
  • 第二题
var obj1 = {
a: 1,
b: 2,
c: 3
}
var objString = JSON.stringify(obj1);
var obj2 = JSON.parse(objString);
obj2.a = 5;
console.log(obj1.a);
console.log(obj2.a);

解析

//JSON.stringify() 方法将一个 JavaScript 对象或值转换为 JSON 字符串
//JSON.parse() 方法用来解析 JSON 字符串,解析为 JavaScript 值或对象
//JSON.parse(JSON.stringify())也常被用作深拷贝
//深拷贝生成一个新对象,新对象跟原对象不共享内存,修改新对象不会改到原对象
//打印1、5
  • 第三题
       let obj = {
			num: 117
		}
		var num = 935;
		function func() {
			console.log(this.num)
		}
		const wrapper = func.bind(obj);
		setTimeout(() => {
			wrapper()
		}, 1000)
		obj = {
			num: 130
		}

答案:117

解析:

//在执行const wrapper = func.bind(obj)时,func的this指向obj
//接着执行setTimeout,1s后执行wrapper
//在上面的一秒内,重新赋值obj = {num:130},这并不会影响到已经绑定了 obj 的 wrapper 函数
//所以打印出117

4、作用域

JS代码的执行是由浏览器中的JS解析器来执行的。JS解析器执行JS代码的时候,分为两个过程:预解析过程,代码执行过程预解析过程:

1、把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值。
2、把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用。
3、先提升var,在提升function。

预解析顺序:

1、预解析的顺序是从上到下,函数的优先级高于变量,函数声明提前到当前作用域最顶端,在var之上。
2、函数执行的时候,函数内部才会进行预解析,如果有参数,先给实参赋值再进行函数内部的预解析。
3、预解析函数是声明+定义(开辟了内存空间,形参值默认是undefined)。
4、预解析变量是只声明,不赋值,默认为undefined。
5、==函数重名时,后者会覆盖前者。 ==
6、==变量重名时,不会重新声明,但是执行代码的时候会重新赋值。==
7、变量和函数重名的时候,函数的优先级高于变量

函数执行顺序:

1、形成私有作用域
2、形参赋值(也是私有变量)
3、变量提升
4、代码从上到下执行
5、作用域是否销毁

函数作用域不销毁的条件:

1、函数的返回值为引用类型
2、返回值被其他变量使用

  • 第一题
var a = 0, b = 0
function A(a) {
  A = function(b) {
    console.log(a + b++)
  }
  console.log(a++)
}
A(1)
A(2)

答案:2 4

解析

//执行A(1),传入参数1
//修改函数A的值为:function(b) { console.log(a + b++) }
// 执行 console.log(a++),打印出1

//执行A(2),传入参数2
//由于A函数被修改,所以执行的的是function(b) { console.log(a + b++) }
// 此时a为2,传入的b为2 ,所以打印4
  • 第二题
console.log(a);  
var a=12;
function fn(){
		console.log(a);
        a=13;
	}	
	
fn();
console.log(a); 

答案:undefined 12 13

解析:

//如果在函数中定义变量时,如果不添加var关键字, 这个变量是一个全局变量 
//打印undefined
//由于a=13在定义a变量没有用关键字,所以在这里是全局变量
//fn执行console.log(a)时没有找到私有变量a,会沿着作用域链查找变量
//打印12
//紧接着a=13修改全局变量
//打印13
  • 第三题

console.log(a); 
a=2;
function fn(){
		console.log(a);
		a=3;	
	}	
	
fn();
console.log(a);

答案:Uncaught ReferenceError: a is not defined

解析:

//变量a不会被提升,因为没有var声明,
//如果在函数中定义变量时,如果不添加var关键字, 这个变量是一个全局变量 
//所以会报错:Uncaught ReferenceError: a is not defined
  • 第四题

var a=10,b=11,c=12;
   
function test(a){
        a=1;var b=2;c=3;
   }  
 
test(10);
alert(a);  alert(b);   alert(c); 

答案:1 11 3

  • 第五题
var foo = function () {
    console.log("foo1")
}
foo()

var foo = function () {
    console.log("foo2")
}
foo()


function foo() {
    console.log("foo1")
}
foo()

function foo() {
    console.log("foo2")
}
foo()

答案:foo1 foo2 foo2 foo2

解析:

//该题可以把代码分开
var foo = function () {
    console.log("foo1")
}
foo()

var foo = function () {
    console.log("foo2")
}
foo()

//1、声明了变量foo,因为声明变量重名了,故不会重新声明,
//2、执行代码时候,会先执行
function () {
    console.log("foo1")
}
//3、又对变量进行重新复制,故执行
function () {
    console.log("foo2")
}
//所以打印出 foo1 foo2
----------------------------------------
function foo() {
    console.log("foo1")
}
foo()

function foo() {
    console.log("foo2")
}
foo()
//1、在预解析的时候,由于声明函数foo重名,故后者会覆盖前者,所以最后foo函数声明的结果如下
function foo() {
    console.log("foo2")
}
//2、执行打印出foo2 foo2

5、矩阵转置

将 [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]——>[ [1, 4, 7], [2, 5, 8], [3, 6, 9] ]

let a = [
  [123],
  [456],
  [789]
]
let b = [
  [147],
  [258],
  [369]
]
//其实不难发现 a[0][0] = b[0][0],a[0][1] = b[1][0],a[0][2] = b[2][0]
//也就是 a[i][j] = b[j][i]

解析:

function transposeArray(array) {
	// 获取原行数和列数 
	const rows = array.length;
	const cols = array[0].length;
	 // 创建一个新的二维数组,长度等于原来数组列数
	const transposedArray = [];
	for (let j = 0; j < cols; j++) {
		transposedArray[j] = new Array(rows);
	}
	// 将元素从原数组复制到新数组中 
	for (let i = 0; i < rows; i++) {
		for (let j = 0; j < cols; j++) {
			transposedArray[j][i] = array[i][j];
		}
	}
	return transposedArray;
}
const myArray = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
const transposedArray = transposeArray(myArray);
console.log(transposedArray);
// 输出:[[1, 4, 7], [2, 5, 8], [3, 6, 9]]

6、类、继承

     	class cls{
			constructor(){
				this.show()
			}
			show(){
				console.log("yoo")
			}
		}
		class son extends cls{
			constructor(){
				super()
			}
			show(){
				console.log('ohh')
			}
		}
		new son()
		new cls()

答案:ohh yoo

解析

//1、在创建 son 对象时,会先调用父类 cls 的构造函数
//2、然后再调用子类 son 的构造函数。
//3、在子类 son 的构造函数中,没有显式调用父类的 show 方法,因此会直接执行子类中重写的 show 方法,输出 “ohh”。
//4、接着创建 cls 对象时,同样会调用父类 cls 的构造函数,执行父类的 show 方法,输出 “yoo”
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。