面试题-JavaScript

Posted 猫狗记

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试题-JavaScript相关的知识,希望对你有一定的参考价值。

文章目录

1-get 请求传参长度的误区

误区:我们经常说get 请求参数的大小存在限制,而 post 请求的参数大小是无限制的。

实际上HTTP 协议从未规定 GET/POST 的请求长度限制是多少。

对 get 请求参数的限制是来源与浏览器或web 服务器,浏览器或web 服务器限制了url 的长度。为了明确这个概念,我们必须再次强调下面几点:

HTTP 协议 未规定 GET 和POST 的长度限制

GET 的最大长度显示是因为 浏览器和 web 服务器限制了 URI 的长度不同的浏览器和 WEB 服务器,限制的最大长度不一样

要支持IE,则最大长度为 2083byte,若只支持Chrome,则最大长度 8182byte

2-get 和 post 请求在缓存方面的区别

get 请求类似于查找的过程,用户获取数据,可以不用每次都与数据库连接,所以可以使用缓存。

post 不同,post 做的一般是修改和删除的工作,所以必须与数据库交互,所以不能使用缓存。因此get 请求适合于请求缓存。

3-说一下闭包

一句话可以概括:闭包就是能够读取其他函数内部变量的函数,或者子函数在外调用, 子函数所在的父函数的作用域不会被释放。

4-说一下类的创建和继承

1、类的创建(es5):new 一个function

在这个function 的prototype 里面增加属性和方法。

下面来创建一个 Animal 类:

// 定义一个动物类
function Animal (name) {
	// 属性
	this.name = name || 'Animal';
	// 实例方法
  this.sleep = function(){
		console.log(this.name + '正在睡觉!');
	}
}

// 原型方法
Animal.prototype.eat = function(food) { 
  console.log(this.name + '正在吃:' + food);
};

这样就生成了一个Animal 类,实力化生成对象后,有方法和属性。

2、类的继承——原型链继承:原型链继承

function Cat(){

}
Cat.prototype = new Animal(); 
Cat.prototype.name = 'cat';

//	Test Code
var cat = new Cat(); 
console.log(cat.name); 
console.log(cat.eat('fish')); 
console.log(cat.sleep());

console.log(cat instanceof Animal); //true 
console.log(cat instanceof Cat); //true

介绍:在这里我们可以看到 new 了一个空对象

这个空对象指向Animal 并且Cat.prototype 指向了这个空对象,这种就是基于原型链的继承。

特点:基于原型链,既是父类的实例,也是子类的实例缺点:无法实现多继承。

3、构造继承:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

function Cat(name){ 
  Animal.call(this); 
  this.name = name || 'Tom';
}

// Test Code
var cat = new Cat(); 
console.log(cat.name); 
console.log(cat.sleep());

console.log(cat instanceof Animal); // false 
console.log(cat instanceof Cat); // true

特点:可以实现多继承

缺点:只能继承父类实例的属性和方法,不能继承原型上的属性和方法。

4、实例继承和拷贝继承

实例继承:为父类实例添加新特性,作为子类实例返回拷贝继承:拷贝父类元素上的属性和方法

上述两个实用性不强,不一一举例。

5、组合继承

相当于构造继承和原型链继承的组合体。

通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

function Cat(name){
	Animal.call(this); 
  this.name = name || 'Tom';
}

Cat.prototype = new Animal(); 
Cat.prototype.constructor = Cat;

// Test Code
var cat = new Cat(); 
console.log(cat.name); 
console.log(cat.sleep());

console.log(cat instanceof Animal); // true 
console.log(cat instanceof Cat); // true

特点:可以继承实例属性/方法,也可以继承原型属性/方法

缺点:调用了两次父类构造函数,生成了两份实例

6、寄生组合继承

通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性

function Cat(name){ 
  Animal.call(this); 
  this.name = name || 'Tom';
}

(function(){
  // 创建一个没有实例方法的类
  var Super = function(){}; 
  Super.prototype = Animal.prototype;
  
  //将实例作为子类的原型
  Cat.prototype = new Super();
})();

// Test Code
var cat = new Cat(); 
console.log(cat.name); 
console.log(cat.sleep());

console.log(cat instanceof Animal); // true 
console.log(cat instanceof Cat); //true

较为推荐

5-如何解决异步回调地狱

promise、generator、async/await

6-说说前端中的事件流

html 中与javascript 交互是通过事件驱动来实现的,例如鼠标点击事件 onclick、页面的滚动事件onscroll 等等,可以向文档或者文档中的元素添加事件侦听器来预订事件。想要知道这些事件是在什么时候进行调用的,就需要了解一下“事件流”的概念。

什么是事件流:事件流描述的是从页面中接收事件的顺序,DOM2 级事件流包括下面几个阶段。

事件捕获阶段处于目标阶段事件冒泡阶段

addEventListener:addEventListener 是DOM2 级事件新增的指定事件处理程序的操作

这个方法接收 3 个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true

表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。

IE 只支持事件冒泡。

7-如何让事件先冒泡后捕获

在DOM 标准事件模型中,是先捕获后冒泡

但是如果要实现先冒泡后捕获的效果,对于同一个事件,监听捕获和冒泡,分别对应相应的处理函数

监听到捕获事件,先暂缓执行,直到冒泡事件被捕获后再执行捕获之间。

8-说一下事件委托

简介:事件委托指的是,不在事件的发生地(直接dom)上设置监听函数,而是在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的触发,通过判断事件发生元素 DOM 的类型,来做出不同的响应。

举例:最经典的就是ul 和li 标签的事件监听,比如我们在添加事件时候,采用事件委托机制,不会在 li 标签上直接添加,而是在ul 父元素上添加。

好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事件触发机制。

9-说一下图片的懒加载和预加载

预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染。

懒加载:懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数。

两种技术的本质:两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。

懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。

10-mouseover 和 mouseenter 的区别

mouseover:当鼠标移入元素或其子元素都会触发事件,所以有一个重复触发,冒泡的过程。

对应的移除事件是 mouseout

mouseenter:当鼠标移除元素本身(不包含元素的子元素)会触发事件,也就是不会冒泡。

对应的移除事件是 mouseleave

11-JS 的 new 操作符做了哪些事情

new 操作符新建了一个空对象,这个对象原型指向构造函数的 prototype,执行构造函数后返回这个对象。

12-改变函数内部 this 指针的指向函数(bind,apply,call 的区别)

通过apply 和call 改变函数的this 指向

他们两个函数的第一个参数都是一样的表示要改变指向的那个对象

第二个参数,apply 是数组,而call 则是arg1,arg2…这种形式。

通过bind 改变this 作用域会返回一个新的函数,这个函数不会马上执行。

13-JS 的各种位置,比如 clientHeight,scrollHeight,offsetHeight ,以及 scrollTop, offsetTop,clientTop 的区别?

clientHeight:表示的是可视区域的高度,不包含border 和滚动条

offsetHeight:表示可视区域的高度,包含了border 和滚动条

scrollHeight:表示了所有区域的高度,包含了因为滚动被隐藏的部分。

clientTop:表示边框border 的厚度,在未指定的情况下一般为 0

scrollTop:滚动后被隐藏的高度,获取对象相对于由offsetParent 属性指定的父坐标(css 定位的元素或body 元素)距离顶端的高度。

14-JS 拖拽功能的实现

首先是三个事件,分别是 mousedown,mousemove,mouseup

当鼠标点击按下的时候,需要一个 tag 标识此时已经按下,可以执行mousemove 里面的具体方法。

clientX,clientY 标识的是鼠标的坐标,分别标识横坐标和纵坐标,并且我们用 offsetX 和offsetY 来表示元素的元素的初始坐标,移动的举例应该是:

鼠标移动时候的坐标-鼠标按下去时候的坐标。也就是说定位信息为:
鼠标移动时候的坐标-鼠标按下去时候的坐标+元素初始情况下的offetLeft.

还有一点也是原理性的东西,也就是拖拽的同时是绝对定位,我们改变的是绝对定位条件下的left,以及top 等等值。

补充:也可以通过html5 的拖放(Drag 和 drop)来实现

15-异步加载 JS 的方法

defer:只支持IE 如果您的脚本不会改变文档的内容,可将 defer 属性加入到<script>标签中,以便加快处理文档的速度。

因为浏览器知道它将能够安全地读取文档的剩余部分而不用执行脚本,它将推迟对脚本的解释,直到文档已经显示给用户为止。

async,HTML5 属性仅适用于外部脚本,并且如果在IE 中,同时存在defer 和async,那么defer 的优先级比较高,脚本将在页面完成时执行。

创建script 标签,插入到DOM 中。

16-Ajax 解决浏览器缓存问题

在ajax 发送请求前加上 anyAjaxObj.setRequestHeader(“If-Modified-Since”,“0”)。

在ajax 发送请求前加上 anyAjaxObj.setRequestHeader(“Cache-Control”,“no-cache”)。

在URL 后面加上一个随机数: “fresh=” + Math.random()。

在URL 后面加上时间搓:“nowtime=” + new Date().getTime()。

如果是使用jQuery,直接这样就可以了 $.ajaxSetup({cache:false})。

这样页面的所有 ajax 都会执行这条语句就是不需要保存缓存记录。

17-JS 的节流和防抖

参考回答: http://www.cnblogs.com/coco1s/p/5499469.html

18-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;

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

19-eval 是做什么的

它的功能是将对应的字符串解析成 JS 并执行,应该避免使用 JS

因为非常消耗性能(2 次,一次解析成 JS,一次执行)

20-如何理解前端模块化

前端模块化就是复杂的文件编程一个一个独立的模块,比如JS 文件等等

分成独立的模块有利于重用(复用性)和维护(版本迭代)

这样会引来模块之间相互依赖的问题, 所以有了commonJS 规范,AMD,CMD 规范等等

以及用于 JS 打包(编译等处理)的工具 webpack

21-说一下 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,而如果需要依赖某些,也是异步去依赖,依赖后再执行某些方法。

22-对象深度克隆的简单实现

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 是对象还是数组。

23-实现一个 once 函数,传入函数参数只执行一次

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

24-将原生的 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');
			}
		}
	});
};

25-JS 监听对象属性的改变

我们假设这里有一个user 对象,
1、在ES5 中可以通过Object.defineProperty 来实现已有属性的监听

Object.defineProperty(user,'name',{ 
  setfunction(key,value){ }
})

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

var	user = new Proxy({}, { 
	setfunction(target,key,value,receiver){  }
})

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

26-如何实现一个私有变量,用 getName 方法可以访问,不能直接访问

1、通过defineProperty 来实现

obj = { 
  name:yuxiaoliang, 
  getName:function(){ 
    return this.name
	}
}

object.defineProperty(obj,"name",{
	//不可枚举不可配置
});

2、通过函数的创建形式

function product(){
  var name='yuxiaoliang'; 
  this.getName=function(){ 
    return name;
	}
}

var obj=new product();

27-=、以及 Object.is 的区别

1、==

主要存在:强制转换成 number

null==undefined " "==0 //true

"0"==0 //true " " !="0" //true

123=="123" //true null==undefined //true 

2、Object.js
主要的区别就是 +0!=-0 而NaNNaN (相对比=和==的改进)

28-setTimeout、setInterval 和 requestAnimationFrame 之间的区别

这里有一篇文章讲的是 requestAnimationFrame: http://www.cnblogs.com/xiaohuochai/p/5777186.html

与setTimeout 和setInterval 不同,requestAnimationFrame 不需要设置时间间隔

大多数电脑显示器的刷新频率是 60Hz,大概相当于每秒钟重绘 60 次

大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过那个频率用户体验也不会有提升。

因此,最平滑动画的最佳循环间隔是 1000ms/60,约等于 16.6ms。

RAF 采用的是系统时间间隔,不会因为前面的任务,不会影响 RAF

但是如果前面的任务多的话,会响应setTimeout 和setInterval 真正运行时的时间间隔。

特点:
(1)requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率。
(2)在隐藏或不可见的元素中,requestAnimationFrame 将不会进行重绘或回流,这当然就意味着更少的 CPU、GPU 和内存使用量
(3)requestAnimationFrame 是由浏览器专门为动画提供的 API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU 开销。

29-实现一个两列等高布局,讲讲思路

为了实现两列等高,可以给每列加上

padding-bottom:9999px;

margin-bottom:-9999px;

同时父元素设置 overflow:hidden;

30-自己实现一个 bind 函数

原理:通过apply 或者call 方法来实现。

(1)初始版本

Function.prototype.bind=function(obj,arg){
	var arg=Array.prototype.slice.call(arguments,1); 
  var context=this;
	return function(newArg){ 
    arg=arg.concat(Array.prototype.slice.call(newArg)); 
    return context.apply(obj,arg);
	}
}

(2) 考虑到原型链
为什么要考虑?因为在 new 一个bind 过生成的新函数的时候,必须的条件是要继承原函数的原型

Function.prototype.bind=function(obj,arg){
	var arg=Array.prototype.slice.call(arguments,1); 
  var context=this;
	var bound=function(newArg){ 
    arg=arg.concat(Array.prototype.slice.call(newArg)); 
    return context.apply(obj,arg);
	}
  
	var F=function(){}
	//这里需要一个寄生组合继承
  F.prototype=context.prototype; 
  bound.prototype=new F(); 
  return bound;
}

31-用 setTimeout 来实现 setInterval

1、用setTimeout()方法来模拟setInterval()与setInterval()之间的什么区别?

首先来看setInterval 的缺陷,使用setInterval()创建的定

以上是关于面试题-JavaScript的主要内容,如果未能解决你的问题,请参考以下文章

2018 – 2019 年前端 JavaScript 面试题

Java进阶之光!2021必看-Java高级面试题总结

JavaScript面试题之代码篇

JavaScript经典面试题详解

一个javascript面试题

经验总结:Java高级工程师面试题-字节跳动,成功跳槽阿里!