JS面试题汇总—ing

js常见用法

1. 判断一个对象是否为数组

不能使用typeof关键字

let arr = [1, 2, 3];
let obj1 = {name: 'xf'};
let obj2 = null;
//打印出的结果都是 object
console.log(typeof arr);
console.log(typeof obj1);
console.log(typeof obj2);
  • Array.isArray(arr) //true
  • 判断原型
    let arr = [1, 2, 3];
    console.log(arr.__proto__ === Array.prototype) //true
    
  • Object.prototype.toString.call(arr) === ‘[object Array]’ //true
  • arr instanceof Array // true

2. js继承的实现方式

首先,定义一个父类

function Animal(name){
    this.name = name;
    this.sleep = function() {
        console.log(this.name + '正在睡觉'); 
    }
}
Animal.prototype.eat = function() {
    console.log(this.name + '正在吃东西');
}
原型链继承

核心任务是完成原型链上的对接

function Cat(name){
    this.name = name
}
Cat.prototype = new Animal();
// 完善原型链上的工作
Cat.prototype.constructor = Cat;
let cat = new Cat('xiefeng');
console.log(cat.name);  //xiefeng
console.log(cat.eat());  //xiefeng正在吃东西
console.log(cat.sleep());  //谢锋正在睡觉

实现核心:完成原型链的对接,子类的原型对象为父类的实例

特点:

  • cat是子类的实例,也是父类的实例
  • 父类新增原型方法或属性,子类可以访问
  • 简单,易于实现

缺点:

  • 无法实现多继承
构造函数继承

使用父类的构造函数来增强子类实例,该方法没有完成原型链上的对接

function Cat(name){
    Animal.call(this);
    this.name = name
}
let cat = new Cat('xiefeng');
console.log(cat.name);  //xiefeng
console.log(cat instanceof Animal);  //false

使用Animal.call(this)动态改变this指向,并且在call语句中可以进行传参

特点:

  • 创建子类实例时,可以向父类传递参数
  • 可以实现多继承,即call多个对象

缺点:

  • 实例不再是Animal(父类)的实例,只是子类的实例
  • 只能继承父类的实例的属性和方法,不能继承原型的属性和方法
组合继承

核心:通过调用父类构造函数,继承父类的属性并保留传参的优点,同时通过将父类实例作为子类原型,实现原型链继承

function Cat(name){
    Animal.call(this, name)
}
Cat.prototype = new Animal();
// 完善原型链上的工作
Cat.prototype.constructor = Cat;
let cat = new Cat('xiefeng');
console.log(cat.name);  //xiefeng
console.log(cat instanceof Cat); //true
console.log(cat instanceof Animal);  //true

特点:

  • 结合原型链方法和构造函数方法

缺点:

  • 调用两次父类的构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
寄生组合继承
function Cat(name){
    Animal.call(this, name);
}
// Object.create():用于创建对象,并为该对象选择原型对象
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;
let cat = new Cat('xiefeng');
圣杯继承方式
function Father(){}
function Son(){}
Father.prototype.name = 'Xie';

//圣杯模式
function inherit(Son, Father) {
    function F(){} //中间变量F
    // 让F和Father的原型指向同一对象
    F.prototype = Father.prototype;
    // 用中间变量的实例作为子类的原型对象,如果修改子类原型对象中的方法或属性,则对父类没有影响
    Son.prototype = new F();
    //由于Target.prototype指向的是objF,因此并没有constructor这一属性,沿着__proto__向上查找,发现constructor指向的是Father,
    //因此这里可以进行归位,让它的constructor重新指向它自己
    Son.prototype.constructor = Son;
    //uber是超类的意思,这里主要用来储存这个目标到底继承自谁,可写可不写
    Son.prototype.uber = Father;
}
inherit(Father, Son);
Son.prototype.sex='male';
console.log(son.lastName);//Jack
console.log(son.sex);//male
// 对Father的原型对象不会产生任何影响
console.log(father.sex);//undefined

3. 为什么Promise引入了微任务

首先,Promise中的执行函数是同步进行的,但是里面存在着异步操作

在异步操作结束后,会调用resolve或reject方法,这两者方法都是作为微任务进入到EventLoop中

处理回调问题的方式:

  • 使用同步回调

    异步任务处理会有一定时间延迟,如果异步操作后使用同步回调,则会让整个脚本阻塞,后面的任务都无法得到执行

    等待的时间无法完成其他事情,导致CPU利用率低

  • 使用异步回调,将回调函数放在宏任务队列的队尾

    异步回调能够解决后续任务的阻塞问题

    执行回调(resolve/reject)的时机应该是在前面所有的宏任务完成之后,倘若现在的任务队列非常长,那么回调迟迟得不到执行,造成应用卡顿

Promise利用微任务解决了两大痛点

Promise 引入微任务, 即把 resolve(reject) 回调的执行放在当前宏任务的末尾

  1. 采用异步回调替代同步回调,解决浪费CPU性能的问题
  2. 将任务放到当前宏任务最后执行解决了回调执行的实时性问题

4. 如何获取页面上的事件对象

e = event || window.event

在IE/Opera中,使用window.event,而在FireFox中,是event

event || window.event应用

function testKeyDown(event){
	event = event || window.event;
	if(event.KeyCode == 13){
		alert("回车键");
	}
	if(event.shiftKey == true){
		alert("shift键");
	}
}

通过KeyCode能够知道页面中的按键操作

5. null和undefined

null和undefined都是js中的基本数据类型

  1. typeof null // Object
  2. typeof undefined // undefined
  3. null == undefined // true
  4. null === undefined // false

关于3和4:两等于表示不全等,主要比较数值是否相等,在进行比较时会进行类型转换;而三等于表示严格相等,不仅数值要相同,数据类型也要相同

但null和undefined在进行比较时,不会进行数据类型的转换,但是在不全等中判断null和undefined相等是由于两者的行为很相似,都表示一个无效的值

  • null :表示无值
  • undefined : 表示一个未声明的变量,或已声明但没有赋值的变量,或一个并不存在的对象属性

6. 箭头函数和普通函数的区别

在ES6中,提供=>来定义一个箭头函数,箭头函数里包裹操作语句

箭头函数和普通函数有着很大的区别

  1. 箭头函数不是一个构造器(constructor),同时也不能使用new关键字来实例化对象

  2. 箭头函数没有this指针,箭头函数内部的this指针只能通过作用域链向上寻找确定

  3. 箭头函数内部没有arguments对象

  4. 可以进行函数简写

    • 如果箭头函数没有参数,直接写一个空括号即可。

    • 如果箭头函数的参数只有一个,也可以省去包裹参数的括号。

    • 如果箭头函数有多个参数,将参数依次用逗号(,)分隔,包裹在括号中即可

    let a = (x) => x;  //这样可以省略return
    let b = (x) => {x;}  //{}会被认为是函数体的大括号
    let c = (x) => {{x;}}
    console.log(a(1), b(1), c(1));  // 1 undefined undefined
    let d = x => ({x});
    console.log(d(1));  //{x: 1}
    

7. 事件委托机制

事件委托机制是一种代理模式,其基于事件冒泡,子元素上的事件对象会冒泡给父元素,父元素也会触发其相应的事件

let ul = document.getElementById("ul");
ul.addEventListener("click", function(ev){
	ev = ev || window.event;
	let target = e.target || e.srcElement;
	if(target.nodeName.toUpperCase() === "LI"){
		target.style.backgroundColor = "red";
	}
})

事件代理的优缺点:

  • 优点:事件代理可以减少子元素的事件注册,只需要在父级元素上对事件进行注册即可;可以实现动态新增子元素,并且新增的子元素也无需进行事件绑定
  • 缺点:无法冒泡的事件无法使用事件代理,比如blur/focus等;频繁触发的事件如 mousemove、mouseout、mouseover等,不适合事件委托

8. 数组升序/降序

let arr = [4, 1, 3, 2, 5];
arr.sort((a, b)=>a-b); // [1, 2, 3, 4, 5]升序结果
arr.sort((a, b)=>b-a); // [5, 4, 3, 2, 1]降序结果

9. Object.defineProperty

基本用法:

Object.defineProperty(obj, 'propertyName', {
	value: 42,
	writable: false
})

Object.defineProperty()方法用于直接在一个对象上定义一个新属性,或者修改一个对象的现有属性

  1. 参数1 obj:目标对象
  2. propertyName:要定义或修改的属性名称
  3. descriptor:要定义或修改的属性的描述符,或者称作选项配置

常见配置:

  1. configurable:其值为true时(默认为true),该属性的描述符才能被改变,同时该属性也能从对应的对象上被删除
  2. enumerable:表示属性是否能被枚举/遍历
  3. value:属性的值
  4. writable:表示属性值是否可以被赋值运算符修改
  5. get:读取属性时,调用getter函数
  6. set:设置属性时,调用setter函数

10. bind()

bind方法是将上下文对象绑定给一个函数返回对应函数,便于稍后调用

apply和call方法则是改变this指向,并立即调用

bind,apply,call三个方法都可以改变函数运行时的上下文对象(context)

Function.prototype.bind()方法主要将函数绑定到某个对象,该方法会返回一个函数,函数体内的this对象的值会被绑定到传入bind()第一个参数的值

let a = {
	name: "xf",
	b(){
		let c = function(){
			console.log(this.name);
		}
		c();  //由于是一个普通函数调用,this为window
	}
}
a.b();  // undefined

第一个解决办法就是保存this指向

let a = {
	name: "xf",
	b(){
		let that = this;
		let c = function(){
			console.log(that.name);
		}
		c();
	}
}
a.b();  // xf

另一种解决办法就是使用bind方法

let a = {
	name: "xf",
	b(){
		let c = function(){
			console.log(this.name);
		}.bind(this);
		c();
		// 也可以是: c().bind(this)()
	}
}
a.b();  // xf

bind的另一种用法


function f(y, z){
	return this.x + y + z;
}
var m = f.bind({x : 1}, 2);
console.log(m(3));
//6

这里bind方法会把它的第一个实参绑定给f函数体内的this,所以这里的this即指向{x : 1}对象,从第二个参数起,会依次传递给原始函数,这里的第二个参数2,即是f函数的y参数,最后调用m(3)的时候,这里的3便是最后一个参数z了,所以执行结果为1 + 2 + 3 = 6

11. js String常用方法

  1. substr(start, length)
  2. substring(from, to)
  3. slice(from, to)
  4. split(“seperator”); 切分
  5. trim() 去除字符串两边的空白
  6. indexOf, lastIndexOf
  7. includes 查找字符串中是否包含指定的子字符串
  8. charAt() 方法可返回指定位置的字符

12. Array常用方法

|concat() |连接两个或更多的数组,并返回结果。 |
|entries() |返回数组的可迭代对象。 |
|keys() |返回数组的可迭代对象,包含原始数组的键(key)。 |
|every() |检测数值元素的每个元素是否都符合条件。 |
|some() |检测数组元素中是否有元素符合指定条件。 |
|array.fill(value, start, end) |使用一个固定值来填充数组。 |
|filter() |检测数值元素,并返回符合条件所有元素的数组。 |
|map() |通过指定函数处理数组的每个元素,并返回处理后的数组。 |
|forEach() |数组每个元素都执行一次回调函数。 |
|reduce() |将数组元素计算为一个值(从左到右)。 |
|reduceRight() |将数组元素计算为一个值(从右到左)。 |
|find() |返回符合传入测试(函数)条件的数组第一个元素的值。 |
|findIndex() |返回符合传入测试(函数)条件的数组元素索引。 |
|indexOf() |搜索数组中的元素,并返回它所在的位置。 |
|lastIndexOf() |搜索数组中的元素,并返回它最后出现的位置。 |
|Array.from |通过给定的对象中创建一个数组。 |
|includes() |判断一个数组是否包含一个指定的值。 |
|isArray() |判断对象是否为数组。 |
|join() |把数组的所有元素放入一个字符串,默认为,号连接。 |
|reverse() |反转数组的元素顺序。 |
|pop() |删除数组的最后一个元素并返回删除的元素。 |
|push() |向数组的末尾添加一个或更多元素,并返回新的长度。 |
|shift() |删除并返回数组的第一个元素。 |
|unshift() |向数组的开头添加一个或更多元素,并返回新的长度。 |
|slice() |选取数组的的一部分,并返回一个新数组。 |
|sort() |对数组的元素进行排序。 |
|array.splice(index,howmany,item1,…,itemX) |从数组中添加或删除元素(改变数组自身)。 |
|toString() |把数组转换为字符串,并返回结果。 |

13. 严格模式"use strict"

严格模式,即在严格的条件下运行Javascript脚本,严格模式消除了Javascript语法的一些不合理、不严谨之处,减少一些怪异行为

  1. 不允许直接声明全局变量,只能使用var/let/const关键词申明变量,不允许不使用关键词(即直接)声明变量

  2. 不允许delete变量和函数

    // 严格模式
    "use strict";
    var x = 1;
    delete x; // Uncaught SyntaxError: Delete of an unqualified identifier in strict mode.
    
  3. 要求函数的参数名唯一

  4. arguments保留原始参数

    // 非严格模式
    function s(a, b){
        a = 2;
        console.log(arguments[0], arguments[1]); // 2 2
    }
    s(1, 2);
    
    // 严格模式
    "use strict";
    function s(a, b){
        a = 2;
        console.log(arguments[0], arguments[1]); // 1 2
    }
    s(1, 2);
    
  5. this不再绑定为window,而是undefined

14. 判断一个对象是否为空对象

  1. Object.keys(obj).length === 0
  2. JSON.stringify(obj) === “{}”

但是存在一个问题:这两种方法都不能将属性中Symbol类型的属性给返回出来,详情看下面的Symbol

15. Symbol

基本数据类型:String,Number,Boolean,null,undefined,Symbol

Symbol的基本介绍

Symbol表示独一无二,通过Symbol()生成。凡属于Symbol类型都是独一无二的,从而可以保证不会与其他属性名产生命名冲突

let a = Symbol("hello");

// Symbol()函数里面传递的参数只是该Symbol变量的描述

typeof a; //Symbol
a.toString(); //'Symbol(hello)'
a.description;//hello,用于获取Symbol的描述文字

Symbol函数可以接收一个字符串作为参数,表示对Symbol实例的描述,Symbol其实是一种类似于字符串的数据类型

如果Symbol的参数是一个对象,就会调用该对象的toString方法将其转为字符串,然后生成Symbol值

注意:Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的

let a = Symbol("a");
let aa = Symbol("a");
a === aa;  //false
Symbol作为属性名

对象的属性名现在可以有两种类型

  • 一种是原来就有的字符串
  • 另一种就是新增的 Symbol 类型

凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突

注意:

  • Symbol 值作为对象属性名时,不能用点运算符,因为点运算符后面总是字符串
  • 在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中,如果s不放在方括号中,该属性的键名就是字符串s,而不是s所代表的那个 Symbol 值
let obj = {};

// String型作为属性
obj["name"] = "xf";
obj.age = "21";

console.log(obj); //{name: "xf", age: 21}

//Symbol型作为属性
let gender = Symbol("gender");
obj[gender] = "male"
console.log(obj); //{name: "xf", age: 21, Symbol(gender): "male"}
let mySymbol = Symbol();

// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';

// 第二种写法
let a = {
  [mySymbol]: 'Hello!'
};

// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
关于遍历

Symbol 作为属性名,遍历对象的时候,该属性不会出现在for...infor...of循环中,也不会被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回

有一个Object.getOwnPropertySymbols()方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值

let a = Symbol("a");
let obj = {
	[a]: "a"
}
JSON.stringify(obj); //{}
Object.getOwnPropertySymbols(obj)  //[Symbol(a)]

16. Set & WeakSet, Map & WeakMap

Set

Set和WeakSet都是一种新的数据结构,表示没有重复值的集合

let s = new Set();
let ws = new WeakSet();

Set和WeakSet提供的方法

  1. add()
  2. delete()
  3. has()
  4. clear():Set独有
  5. keys()
  6. values()
  7. set.forEach((value, key)=>{console.log(key, value)})

Set和WeakSet的区别

  1. Set中的成员可以是任意类型,而WeakSet限定了成员只能是对象

  2. WeakSet当中的对象成员都是弱引用,即垃圾回收站机制不考虑WeakSet对该对象的引用

    WeakSet中的成员可能随时都会消失,即被垃圾回收站给回收

Set去重

[...new Set(arr)]
Map

Map与Object类似,都是生成键值对集合,Object提供了“字符串/Symbol-值”的对应关系,而Map中提供“值-值对应关系”,各种类型的值都可以当作键

const m = new Map();
const obj = {name: "xf"};

m.set(o, "content");
m.get(o);  //content

m.has(o); //true
m.delete(o); //true

const map = new Map([
	["name": "xf"],
	["age", 21]
])

Map和WeakMap的区别

  1. WeakMap只接收对象作为键名(null除外),不接受其他类型的值作为键
  2. WeakMap对键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内

基本上,如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值