【建议收藏】2020大厂JavaScript面试题汇总,持续更新中~

1.说几条写JavaScript的基本规范

不要在同一行声明多个变量
请是用 =/! 来比较 true/false 或者数值
使用对象字面量替代 new Array 这种形式
不要使用全局函数
Switch 语句必须带有 default 分支
If 语句必须使用大括号
for-in 循环中的变量 应该使用 let 关键字明确限定作用域,从而避免作用域污染

2.绕不过去的闭包

闭包就是能够读取其他函数内部变量的函数
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个
函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域
闭包的特性:

函数内再嵌套函数
内部函数可以引用外层的参数和变量
参数和变量不会被垃圾回收机制回收

优点:能够实现封装和缓存等
缺点:消耗内存、使用不当会内存溢出,
解决方法:在退出函数之前,将不使用的局部变量全部删除

3.说说你对作用域链的理解

作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到 window对象即被终止,作用域链向下访问变量是不被允许的。
简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期

4.JavaScript原型,原型链 ? 有什么特点?

每个对象都会在其内部初始化一个属性,就是 prototype (原型),当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去 prototype 里找这个属性,这个prototype 又会有自己的 prototype ,于是就这样一直找下去,也就是我们平时所说的原型链的概念
关系: instance.constructor.prototype = instance.proto
特点:JavaScript 对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变当我们需要一个属性的时, Javascript 引擎会先看当前对象中是否有这个属性, 如果没有的,就会查找他的 Prototype 对象是否有这个属性,如此递推下去,一直检索到 Object内建对象

5.Javascript如何实现继承?

  • 构造继承
  • 原型继承
  • 实例继承
  • 拷贝继承
    原型 prototype 机制或 apply 和 call 方法去实现较简单,建议使用构造函数与原型混合方式
function Parent(){
this.name = 'wang';
}
function Child(){
 this.age = 28;
}
Child.prototype = new Parent();//继承了Parent,通过原型
var demo = new Child();
alert(demo.age);
alert(demo.name);//得到被继承的属性

6.JS中的垃圾回收机制

必要性:由于字符串、对象和数组没有固定大小,所有当他们的大小已知时,才能对他们进行动态的存储分配。JavaScript程序每次创建字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便他们能够被再用,否则,JavaScript的解释器将会消耗完系统中所有可用的内存,造成系统崩溃。

这段话解释了为什么需要系统需要垃圾回收,JS不像C/C++,他有自己的一套垃圾回收机制(Garbage Collection)。JavaScript的解释器可以检测到何时程序不再使用一个对象了,当他确定了一个对象是无用的时候,他就知道不再需要这个对象,可以把它所占用的内存释放掉了。例如:

var a="hello world";
var b="world";
var a=b;
//这时,会释放掉"hello world",释放内存以便再引用

垃圾回收的方法:标记清除、计数引用。

标记清除

这是最常见的垃圾回收方式,当变量进入环境时,就标记这个变量为”进入环境“,从逻辑上讲,永远不能释放进入环境的变量所占的内存,永远不能释放进入环境变量所占用的内存,只要执行流程进入相应的环境,就可能用到他们。当离开环境时,就标记为离开环境。

垃圾回收器在运行的时候会给存储在内存中的变量都加上标记(所有都加),然后去掉环境变量中的变量,以及被环境变量中的变量所引用的变量(条件性去除标记),删除所有被标记的变量,删除的变量无法在环境变量中被访问所以会被删除,最后垃圾回收器,完成了内存的清除工作,并回收他们所占用的内存。

引用计数法

另一种不太常见的方法就是引用计数法,引用计数法的意思就是每个值没引用的次数,当声明了一个变量,并用一个引用类型的值赋值给改变量,则这个值的引用次数为1,;相反的,如果包含了对这个值引用的变量又取得了另外一个值,则原先的引用值引用次数就减1,当这个值的引用次数为0的时候,说明没有办法再访问这个值了,因此就把所占的内存给回收进来,这样垃圾收集器再次运行的时候,就会释放引用次数为0的这些值。

用引用计数法会存在内存泄露,下面来看原因:

function problem() {
var objA = new Object();
var objB = new Object();
objA.someOtherObject = objB;
objB.anotherObject = objA;
}

在这个例子里面,objA和objB通过各自的属性相互引用,这样的话,两个对象的引用次数都为2,在采用引用计数的策略中,由于函数执行之后,这两个对象都离开了作用域,函数执行完成之后,因为计数不为0,这样的相互引用如果大量存在就会导致内存泄露。

特别是在DOM对象中,也容易存在这种问题:

var element=document.getElementById(’‘);
var myObj=new Object();
myObj.element=element;
element.someObject=myObj;

这样就不会有垃圾回收的过程。

7.函数柯里化

在一个函数中,首先填充几个参数,然后再返回一个新的函数的技术,称为函数的柯里化。通常可用于在不侵入函数的前提下,为函数 预置通用参数,供多次重复调用。

const add = function add(x) {
	return function (y) {
		return x + y
	}
}
const add1 = add(1)
add1(2) === 3
add1(20) === 21

8.js的防抖

防抖(Debouncing)

防抖技术即是可以把多个顺序地调用合并成一次,也就是在一定时间内,规定事件被触发的次数。
通俗一点来说,看看下面这个简化的例子:

// 简单的防抖动函数
function debounce(func, wait, immediate) {
    // 定时器变量
    var timeout;
    return function() {
        // 每次触发 scroll handler 时先清除定时器
        clearTimeout(timeout);
        // 指定 xx ms 后触发真正想进行的操作 handler
        timeout = setTimeout(func, wait);
    };
};
 
// 实际想绑定在 scroll 事件上的 handler
function realFunc(){
    console.log("Success");
}
 
// 采用了防抖动
window.addEventListener('scroll',debounce(realFunc,500));
// 没采用防抖动
window.addEventListener('scroll',realFunc);

上面简单的防抖的例子可以拿到浏览器下试一下,大概功能就是如果 500ms 内没有连续触发两次 scroll 事件,那么才会触发我们真正想在 scroll 事件中触发的函数。

上面的示例可以更好的封装一下

// 防抖动函数
function debounce(func, wait, immediate) {
    var timeout;
    return function() {
        var context = this, args = arguments;
        var later = function() {
            timeout = null;
            if (!immediate) func.apply(context, args);
        };
        var callNow = immediate && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
        if (callNow) func.apply(context, args);
    };
};
 
var myEfficientFn = debounce(function() {
    // 滚动中的真正的操作
}, 250);
 
// 绑定监听
window.addEventListener('resize', myEfficientFn);

9.js节流

防抖函数确实不错,但是也存在问题,譬如图片的懒加载,我希望在下滑过程中图片不断的被加载出来,而不是只有当我停止下滑时候,图片才被加载出来。又或者下滑时候的数据的 ajax 请求加载也是同理。

这个时候,我们希望即使页面在不断被滚动,但是滚动 handler 也可以以一定的频率被触发(譬如 250ms 触发一次),这类场景,就要用到另一种技巧,称为节流函数(throttling)。

节流函数,只允许一个函数在 X 毫秒内执行一次。

与防抖相比,节流函数最主要的不同在于它保证在 X 毫秒内至少执行一次我们希望触发的事件 handler。
与防抖相比,节流函数多了一个 mustRun 属性,代表 mustRun 毫秒内,必然会触发一次 handler ,同样是利用定时器,看看简单的示例:

// 简单的节流函数
function throttle(func, wait, mustRun) {
    var timeout,
        startTime = new Date();
 
    return function() {
        var context = this,
            args = arguments,
            curTime = new Date();
 
        clearTimeout(timeout);
        // 如果达到了规定的触发时间间隔,触发 handler
        if(curTime - startTime >= mustRun){
            func.apply(context,args);
            startTime = curTime;
        // 没达到触发间隔,重新设定定时器
        }else{
            timeout = setTimeout(func, wait);
        }
    };
};
// 实际想绑定在 scroll 事件上的 handler
function realFunc(){
    console.log("Success");
}
// 采用了节流函数
window.addEventListener('scroll',throttle(realFunc,500,1000));

上面简单的节流函数的例子可以拿到浏览器下试一下,大概功能就是如果在一段时间内 scroll 触发的间隔一直短于 500ms ,那么能保证事件我们希望调用的 handler 至少在 1000ms 内会触发一次。

10.说一下Commonjs、AMD和CMD

一个模块是能实现特定功能的文件,有了模块就可以方便的使用别人的代码,想要什么功能就能加载什么模块。

Commonjs:开始于服务器端的模块化,同步定义的模块化,每个模块都是一个单独的作用域,模块输出,modules.exports,模块加载require()引入模块。

AMD:中文名异步模块定义的意思。

requireJS实现了AMD规范,主要用于解决下述两个问题。

1.多个文件有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器
2.加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应的时间越长。
语法:requireJS定义了一个函数define,它是全局变量,用来定义模块。

requireJS的例子:

//定义模块
define(['dependency'], function(){
var name = 'Byron';
function printName(){
console.log(name);
}
return {
printName: printName
};
});
//加载模块
require(['myModule'], function (my){
my.printName();
}

requirejs定义了一个函数define,它是全局变量,用来定义模块:
define(id?dependencies?,factory)

在页面上使用模块加载函数:
require([dependencies],factory);

总结AMD规范:require()函数在加载依赖函数的时候是异步加载的,这样浏览器不会失去响应,它指定的回调函数,只有前面的模块加载成功,才会去执行。

因为网页在加载js的时候会停止渲染,因此我们可以通过异步的方式去加载js,而如果需要依赖某些,也是异步去依赖,依赖后再执行某些方法。

由于篇幅有限,只能分享部分面试题,更多面试题及答案可以【点击我】阅读下载哦~无偿分享给大家,算是一个感恩回馈吧

11.请解释什么是事件委托/事件代理

事件代理( Event Delegation ),又称之为事件委托。是 JavaScript 中常用的绑定事件的常用技巧。顾名思义,“事件代理”即是把原本需要绑定的事件委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。使用事件代理的好处是可以提高性能
可以大量节省内存占用,减少事件注册,比如在 table 上代理所有 td 的 click 事件就非常棒
可以实现当新增子对象时无需再次对其绑定

12.事件模型

W3C 中定义事件的发生经历三个阶段:捕获阶段( capturing )、目标阶段
( targetin )、冒泡阶段( bubbling )

冒泡型事件:当你使用事件冒泡时,子级元素先触发,父级元素后触发
捕获型事件:当你使用事件捕获时,父级元素先触发,子级元素后触发
DOM 事件流:同时支持两种事件模型:捕获型事件和冒泡型事件
阻止冒泡:在 W3c 中,使用 stopPropagation() 方法;在IE下设置 cancelBubble =true
阻止捕获:阻止事件的默认行为,例如 click - a 后的跳转。在 W3c 中,使用preventDefault() 方法,在 IE 下设置 window.event.returnValue = false

13.new操作符具体干了什么呢?

创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型
属性和方法被加入到 this 引用的对象中
新创建的对象由 this 所引用,并且最后隐式的返回 this

14.Ajax原理

Ajax 的原理简单来说是在用户和服务器之间加了—个中间层( AJAX 引擎),通过XmlHttpRequest 对象来向服务器发异步请求,从服务器获得数据,然后用 javascript来操作 DOM 而更新页面。使用户操作与服务器响应异步化。这其中最关键的一步就是从服务器获得请求数据
Ajax 的过程只涉及 JavaScript 、 XMLHttpRequest 和 DOM 。 XMLHttpRequest 是ajax的核心机制

15.对象深度克隆的简单实现

function deepClone(obj){
var newObj= obj instanceof Array ? []:{};
for(var item in obj){
var temple= typeof obj[item] == 'object' ? deepClone(obj[item]):obj[item];
newObj[item] = temple;
}
return newObj;
}

ES5的常用的对象克隆的一种方式。注意数组是对象,但是跟对象又有一定区别,所以我们一开始判断了一些类型,决定newObj是对象还是数组~

16.将原生的ajax封装成promise

var  myNewAjax=function(url){
return new Promise(function(resolve,reject){
var xhr = new XMLHttpRequest();
xhr.open('get',url);
xhr.send(data);
xhr.onreadystatechange=function(){
if(xhr.status==200&&readyState==4){
var json=JSON.parse(xhr.responseText);
resolve(json)
}else if(xhr.readyState==4&&xhr.status!=200){
reject('error');
}
}
})
}

17.实现一个once函数,传入函数参数只执行一次

function ones(func){
var tag=true;
return function(){
if(tag==true){
func.apply(null,arguments);
tag=false;
}
return undefined
}
}

18.js监听对象属性的改变

我们假设这里有一个user对象,

(1)在ES5中可以通过Object.defineProperty来实现已有属性的监听

Object.defineProperty(user,'name',{
set:function(key,value){
}
})

缺点:如果id不在user对象中,则不能监听id的变化
(2)在ES6中可以通过Proxy来实现

var  user = new Proxy({},{
set:function(target,key,value,receiver){
}
})

这样即使有属性在user中不存在,通过user.id来定义也同样可以这样监听这个属性的变化哦~

19.如何实现sleep的效果(es5或者es6)

(1)while循环的方式

function sleep(ms){
var start=Date.now(),expire=start+ms;
while(Date.now()<expire);
console.log('1111');
return;
}

执行sleep(1000)之后,休眠了1000ms之后输出了1111。上述循环的方式缺点很明显,容易造成死循环。

(2)通过promise来实现

function sleep(ms){
var temple=new Promise(
(resolve)=>{
console.log(111);setTimeout(resolve,ms)
});
return temple
}
sleep(500).then(function(){
//console.log(222)
})
//先输出了111,延迟500ms后输出222

(3)通过async封装

function sleep(ms){
return new Promise((resolve)=>setTimeout(resolve,ms));
}
async function test(){
var temple=await sleep(1000);
console.log(1111)
return temple
}
test();
//延迟1000ms输出了1111

(4).通过generate来实现

function* sleep(ms){
yield new Promise(function(resolve,reject){
console.log(111);
setTimeout(resolve,ms);
})
}
sleep(500).next().value.then(function(){console.log(2222)})

20.Function.proto(getPrototypeOf)是什么?

获取一个对象的原型,在chrome中可以通过_proto_的形式,或者在ES6中可以通过Object.getPrototypeOf的形式。
那么Function.proto是什么么?也就是说Function由什么对象继承而来,我们来做如下判别。
Function.proto==Object.prototype //false
Function.proto==Function.prototype//true
我们发现Function的原型也是Function。

由于篇幅有限,只能分享部分面试题,更多面试题及答案可以【点击我】阅读下载哦~无偿分享给大家,算是一个感恩回馈吧

21.如何解决跨域问题?

首先了解下浏览器的同源策略 同源策略 /SOP(Same origin policy) 是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到 XSS 、 CSFR 等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源

  • 通过jsonp跨域
var script = document.createElement('script');
script.type = 'text/javascript';
// 传参并指定回调执行函数为onBack
script.src = 'http://www.....:8080/login?user=admin&callback=onBack';
document.head.appendChild(script);
// 回调执行函数
function onBack(res) {
 alert(JSON.stringify(res));
}
  • document.domain + iframe跨域
//父窗口:(http://www.domain.com/a.html)
<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
<script>
 document.domain = 'domain.com';
 var user = 'admin';
</script>

//子窗口:(http://child.domain.com/b.html)
document.domain = 'domain.com';
// 获取父窗口中变量
alert('get js data from parent ---> ' + window.parent.user);
  • nginx代理跨域
  • nodejs中间件代理跨域
  • 后端在头部信息里面设置安全域名

22.介绍js有哪些内置对象

Object 是 JavaScript 中所有对象的父对象
数据封装类对象: Object 、 Array 、 Boolean 、 Number 和 String
其他对象: Function 、 Arguments 、 Math 、 Date 、 RegExp 、 Error

23.JS有哪些方法定义对象

对象字面量: var obj = {};
构造函数: var obj = new Object();
Object.create(): var obj = Object.create(Object.prototype);

24.你觉得jQuery源码有哪些写的好的地方

  • jquery 源码封装在一个匿名函数的自执行环境中,有助于防止变量的全局污染,然后通过传入 window 对象参数,可以使 window 对象作为局部变量使用,好处是当 jquery 中访问 window 对象的时候,就不用将作用域链退回到顶层作用域了,从而可以更快的访问window对象。同样,传入 undefined 参数,可以缩短查找 undefined 时的作用域链

  • jquery 将一些原型属性和方法封装在了 jquery.prototype 中,为了缩短名称,又赋值给了 jquery.fn ,这是很形象的写法

  • 有一些数组或对象的方法经常能使用到, jQuery 将其保存为局部变量以提高访问速度

  • jquery 实现的链式调用可以节约代码,所返回的都是同一个对象,可以提高代码效率

25.如何通过JS判断一个数组

  • instanceof 运算符是用来测试一个对象是否在其原型链原型构造函数的属性
var arr = [];
arr instanceof Array; // true
  • isArray
Array.isArray([]) //true
Array.isArray(1) //false
  • constructor 属性返回对创建此对象的数组函数的引用,就是返回对象相对应的构造函数
var arr = [];
arr.constructor == Array; //true
  • Object.prototype
Object.prototype.toString.call([]) == '[object Array]'
// 写个方法
var isType = function (obj) {
 return Object.prototype.toString.call(obj).slice(8,-1);
 //return Object.prototype.toString.apply([obj]).slice(8,-1);
}
isType([])  //Array

猜你喜欢

转载自blog.csdn.net/hugo233/article/details/112547887