总纲:前端面试知识点大全
目录
1.javascript调试工具
浏览器自带的调试工具,比如firebug
语法检查工具ESlint
Mocha测试框架,只需在Mocha提供的全局函数,比如describe,it就是最核心的两个全局函数,可以在测试中直接使用。
decribe函数被用来包裹一组相关的测试。它是一个函数,第一个参数是这组测试的描述,第二个参数是一个实际执行的函数。
it函数则用于包裹一个单独的测试。它也是一个函数,第一个参数是名称,第二个参数是一个实际执行的函数。
2.对象遍历 和 数组遍历
对象遍历:for...in...,Object.keys(obj)
数组遍历:for..in..,for..of..,forEach,map,filter,every,some
3.可变对象和不可变对象
可变对象:引用类型
不可变对象:基本类型
防篡改对象:不可拓展对象 Object.preventExtension(obj) Object.isExtensible();密封对象 Object.seal(obj),不能拓展,且已存在的属性删除,也不能添加,可以修改;冻结对象Object.freeze(obj),不能拓展、删除添加修改Object.isFrozen(obj)
4.什么是事件循环 (event loop)
setTimeout运行的时间间隔约是4ms(另外,setInterval也是一样的),实事上,这正是浏览器两次Event Loop之间的时间间隔,相关标准各位可以自行查阅。另外,在Node中,这个时间间隔跟浏览器不一样,是1ms。
主线程和任务队列,主线程空闲,就会循环调用任务队列。主线程空闲,取出任务队列的第一项放入(主线程的)执行栈,重复上述步骤。其中Promise和process.nextTick是微任务,加入当前任务的尾部,而setTimeout和setInterval是放到下一个任务队列的首部。
1、一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。
2、任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。
3、macro-task大概包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。
4、micro-task大概包括: process.nextTick, Promise, Object.observe(已废弃), MutationObserver(html5新特性)
5、setTimeout/Promise等我们称之为任务源。而进入任务队列的是他们指定的具体执行任务。
6、来自不同任务源的任务会进入到不同的任务队列。其中setTimeout与setInterval是同源的。
7、事件循环的顺序,决定了JavaScript代码的执行顺序。它从script(整体代码)开始第一次循环。之后全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的micro-task。当所有可执行的micro-task执行完毕之后。循环再次从macro-task开始,找到其中一个任务队列执行完毕,然后再执行所有的micro-task,这样一直循环下去。
8、其中每一个任务的执行,无论是macro-task还是micro-task,都是借助函数调用栈来完成。
5.let var const
let和const是ES6提出来的声明变量的标识符,他们定义的变量具有块级作用域。var声明的变量没有块级作用域。
let和const声明的变量不会提升,而var声明的变量会提升。
let和var声明的变量是可变的,const声明的变量是不可变的。
let和const存在暂时性死区。块级作用域内,在let声明变量之前,使用变量会导致暂时性死区。ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
let和const都不允许在相同作用域内,重复声明同一个变量。var可以重复声明定义同一个变量,后面的会覆盖前面定义的。
6.数组的方法
1、push、unshift返回添加了新元素后的数组长度
2、pop和shift返回被删除的元素
3、concat可以给数组添加多个元素并返回新数组的引用。若给concat传一些数组作为参数,它会把这些数组拆分后的元素添加到原始数组的拷贝中,原数组不会变
4、slice(start,end)返回新数组的引用,原数组不变
5、splice(start,length,[params]),会改变原数组,返回删除的子数组(若无,则为[])
6、reverse,颠倒数组的排序
7、sort,指定排序函数,返回'true'则改变顺序,返回'false'则不改变顺序。默认情况是按字符串的ASCII码进行升序排列
8、map,返回新数组,数组中的元素为原始数组元素调用函数处理后的值。
9、filter,创建一个新的数组,新数组中的元素是根据回调函数返回true的元素。
10、reduce(),接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。回调函数的第一个参数为累加值,其余跟别的回调函数一样,元素、索引、数组本身。
11、copyWithin(target,start,end),将数组中一串有序的元素复制到数组的另一个位置(target),复制的同时会覆盖原来数组中的内容。参数可以为负数。(不含end位)
12、fill(value,start,end),可以将一个固定值赋值给数组中的任意位置元素,且会修改当前数组,同样不包含end位
13、some,支持回调函数,原数组中至少一个满足回调函数返回true,则返回true
14、every,支持回调函数,原数组中的每一项都让回调函数返回true,则返回true
15、indexOf,返回字符串的在数组中的位置,可以设置起始位置
16、findIndex,传入回调函数,返回符合回调函数的元素的下标
17、find,传入回调函数,返回符合回调函数的元素
18、forEach,数组中的每一项执行回调函数
19、of,用于直接生成数组,可以传入数组元素。为了解决Array对象实例化是,传入单个参数时带来的误差。new Array(3)有三个未定义的元素,Array.of(3)只有一个3的数组
20、from,将类数组或者具有iterator接口的对象转成数组
7.web worker
这是H5提出的新API,它由浏览器提供,可以启动一个独立于网页的线程,另辟蹊径的解决JS单线程的问题,但是并未改变JS是单线程的现实。通过postMessage(msg)进行线程间的双向通信,通过message时间监听数据传输。
7.1 使用Web Worker
//main.js传递一个Object
let worker = new Worker('worker.js');
worker.addEventListener('message', function (e) {
console.log(e.data.a);
});
worker.addEventListener('error', function (e) {
console.log(’error‘);
});
let msg = {a:2};
worker.postMessage(msg);
//worker.js 接收,并返回
self.addEventListener('message', function (e) {
e.data.a = 3;
self.postMessage(e.data);
});
在worker中,self就是new Worker的实例化内容。
通过postMessage传递的msg并不是两个线程共享的,实际上只是一个副本。
7.2 关闭worker
(1)worker.terminate(),在外部终结worker
(2)self.close(),在worker内部终结
官方推荐使用self.close()进行内部的自动关闭,这样能防止意外关闭正在运行的worker
7.3 web worker可以访问的特性
worker的线程与JS主线程存在一定区别,它无法访问和操作dom
(1)window.navigator对象的相关属性和方法
(2)只读window.location内容
(3)可使用XMLHttpRequest与后台通信
(4)setInterval、setTimeout相关函数
除以上几个特性外,worker不能访问别的特性或者API
7.4 同域限制
worker在访问的JS文件只能是在同一host下,即worker只能处于指定目录下的path中。
若本地调试,直接打开页面(file://XXX)这种,也不能使用worker。
7.5 subWorker
在一个worker中可以在调用其他的worker,当然也有同域限制。
如果想在当前worker里面加载其他库文件,需要使用importScripts方法导入,如importScripts('react.js','jquery.js')。
7.6 worker的用处
(1)懒加载数据
(2)文本分析
(3)流媒体数据处理
(4)web database的更新
(5)大量JSON返回数据的处理
7.7 web worker的分类
(1)Dedicated worker(DW,专用worker):即上面介绍的,使用new Worker创建,该worker一般只能在创建worker的js脚本中使用。
(2)shared workers(SW):使用new shareWorker()创建,能在不同JS脚本中使用。
SW的特性:1、引入外部文件,importScripts();2、错误监听(error事件);3、关闭通信,port.close();4、可以使用XMLHttpRequest对象;5、navigator、location、setInterval的访问
7.8 线程之间postMessage通信
//第一种传递方式
worker.postMessage(message,taransferList(是个数组));
//第二种传递方式
worker.postMessage({
operation: "list_all_users",
//ArrayBuffer object
input: buffer,
threshold: 0.8,
}, [buffer]);
第二个参数是用于传递大对象(例如ArrayBuffer),直接从一个上下文移交到另外一个上下文。如果主线程JS到Web Worker线程。一旦提交成功,这个大对象在原有的上下文中,将不可访问。
8.柯里化
8.1 函数柯里化的概念
把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
8.2 思想思路
1、闭包保存args变量,存储之前的参数
2、新建一个_add函数,参数的长度为0,就执行加法,否则,存储参数到args,然后返回函数自身(可以选择匿名函数,返回arguments.callee即可,但是在严格模式下不能使用,所以还是使用方法一比较稳妥)。
function add() {
//建立args,利用闭包特性,不断保存arguments
let args = [].slice.call(arguments);
//方法一,新建_add函数实现柯里化
let _add = function () {
if (arguments.length === 0) {
//参数为空,对args执行加法
return args.reduce(function (a, b) {
return a + b
});
} else {
//否则,保存参数到args,返回一个函数
[].push.apply(args, arguments);
return _add;
}
};
//返回_add函数
return _add;
}
8.3 通用函数柯里化方法
// 通用的函数柯里化构造方法
function curry(func){
//新建args保存参数,注意,第一个参数应该是要柯里化的函数,所以args里面去掉第一个
let args = [].slice.call(arguments,1);
//新建_func函数作为返回值
let _func = function(){
//参数长度为0,执行func函数,完成该函数的功能
if(arguments.length === 0){
return func.apply(this,args);
}else {
//否则,存储参数到闭包中,返回本函数
[].push.apply(args,arguments);
return _func;
}
}
return _func;
}
8.4 实现add(1)(2)(3)
思路:返回一个函数
//可拓展
function add(x) {
var sum = x;
var tmp = function (y) {
sum = sum + y;
return tmp;
};
tmp.toString = function () {
return sum;
};
return tmp;
}
//不可拓展
function add(x) {
return function (y) {
return function(z) {
return x+y+z;
}
}
}
8.5 函数柯里化的作用
1、延迟计算。
2、参数复用。当在多次调用同一个函数,并且传递的参数绝大多数是相同的,那么该函数可能是一个很好的柯里化候选。
3、动态创建函数。这可以是在部分计算出结果后,在此基础上动态生成新的函数处理后面的业务,这样省略了重复计算。或者可以通过将要传入调用函数的参数子集,部分应用到函数中,从而动态创造出一个新函数,这个新函数保存了重复传入的参数(以后不必每次都传)。例如,事件浏览器添加事件的辅助方法
8.6 简单利用柯里化实现bind函数
bind函数本身也是一个柯里化函数
Function.prototype.bind = function(){
var fn = this;
var args = Array.prototye.slice.call(arguments);
var context = args.shift();
return function(){
return fn.apply(context,
args.concat(Array.prototype.slice.call(arguments)));
};
};
9.创建对象的三种方法
一个对象实例的创建方式
方法1:字面量方法
方法2:利用Object构造函数,new Object();
方法3:Object.create(null) 创建的对象是一个空对象,在该对象上没有继承 Object.prototype 原型链上的属性或者方法,例如:toString(), hasOwnProperty()等方法。
Object.create()方法接受两个参数:Object.create(obj,propertiesObject) ;
别的意义上的创建
9.1 工厂模式
每次都是返回一个包含2个属性和一个方法的对象(工厂的产品的一致性)。虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)
function createPerson(name,age) {
let obj = new Object();
obj.name = name;
obj.age = age;
obj.sayName = function () {
console.log(this.name);
};
return obj;
}
9.2 构造函数模式
解决了工厂模式的问题,但是每个方法都要在每个实例上重新创建一遍。
function Person(name,age) {
this.name = name;
this.age = age;
this.sayName = function () {
console.log(this.name);
};
}
9.3 原型模式
原型模式解决了构造函数中方法重复创建的问题,但是实例属性也变成了共享,一旦某个实例改变了属性,那么其他实例的属性也跟着改变。同时他也没有办法在实例化的时候初始化参数。
function Person(name,age) {
}
Person.prototype.name = 'David';
Person.prototype.age = 25;
Person.prototype.sayName = function () {
console.log(this.name);
};
9.4 组合使用构造函数模式和原型模式
兼容两种模式的优点,目前主流的方式
function Person(name,age) {
this.name = name;
this.age = age;
}
Person.prototype.sayName = function () {
console.log(this.name);
};
9.5 动态原型模式
只有原型上不存在某个方法或者属性的时候才会添加
function Person(name, job) {
this.name = name;
this.age = age;
if ( typeof this,sayName != 'function') {
Person.prototype.sayName = function(){
console.log(this.name);
}
}
}
9.6 寄生构造函数模式
和工厂模式一模一样,但是是通过new操作进行实例化。即new Person
但需要注意的是:返回的对象与构造函数或者与构造函数的原型属性之间没有关系,即构造函数返回的对象与在构造函数外部创建的对象没有什么区别。
instanceof来确定对象类型没用,都是object的实例。
function Person(name,age) {
let obj = new Object();
obj.name = name;
obj.age = age;
obj.sayName = function () {
console.log(this.name);
};
return obj;
}
9.7 稳妥构造函数模式
稳妥构造函数遵循的寄生构造函数类似的模式,但稳妥构造函数一是新创建的实例方法不引用this;二是不使用new操作符调用构造函数
function Person(name, age) {
//创建要返回的对象
let o = new Object();
//这里可以定义私有变量和函数
//添加方法
o.sayName = function() {
console.log(name);
};
return o;
}
10.深拷贝和浅拷贝
浅拷贝与深拷贝都可以实现在已有对象上再生出一份的作用。但是对象的实例是存储在堆内存中然后通过一个引用值去操作对象,由此拷贝的时候就存在两种情况了:拷贝引用和拷贝实例,这也是浅拷贝和深拷贝的区别。
JS中基本类型的复制操作是深拷贝,引用类型的复制是浅拷贝。
10.1 浅拷贝
function simpleClone(initalObj) {
var obj = {};
for ( var i in initalObj) {
obj[i] = initalObj[i];
}
return obj;
}
Object.assign(target, source),这是ES6的新API。用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。source可以是多个对象,以逗号分隔。
如果目标对象中的属性具有相同的键,则属性将被源中的属性覆盖。后来的源的属性将类似地覆盖早先的属性。
10.2 深拷贝
a) 利用JSON.parse(JSON.stringify(obj))
可以实现对象的深拷贝,但是具有以下缺点:
I、undefined、任意的函数、正则表达式类型以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时);
II、它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object;即继承属性会丢失
III、如果对象中存在循环引用的情况无法正确处理。
b) 利用递归实现深拷贝
function deepCopy1(obj) {
// 创建一个新对象
let result = {}
let keys = Object.keys(obj),
key = null,
temp = null;
for (let i = 0; i < keys.length; i++) {
key = keys[i];
temp = obj[key];
// 如果字段的值也是一个对象则递归操作
if (temp && typeof temp === 'object') {
result[key] = deepCopy1(temp);
} else {
// 否则直接赋值给新对象
result[key] = temp;
}
}
return result;
}
上面这个方法没有考虑循环引用和同级引用,改良上面的方法
function deepCopy3(obj) {
// hash表,记录所有的对象的引用关系
let map = new WeakMap();
function dp(obj) {
let result = null;
let keys = Object.keys(obj);
let key = null,
temp = null,
existobj = null;
existobj = map.get(obj);
//如果这个对象已经被记录则直接返回
if(existobj) {
return existobj;
}
result = {}
map.set(obj, result);
for(let i =0,len=keys.length;i<len;i++) {
key = keys[i];
temp = obj[key];
if(temp && typeof temp === 'object') {
result[key] = dp(temp);
}else {
result[key] = temp;
}
}
return result;
}
return dp(obj);
}