第1题.什么是闭包?闭包的用途是什么?闭包的优点、缺点是什么?
闭包: 如果一个函数用到了外部的变量,那么该函数和这个变量就叫做闭包。
用途:
1.从外部读取函数内部的变量
2.将创建的变量的值始终保持在内存中
3.封装对象的私有属性和私有方法。
优点: 可以避免全局变量的污染。
缺点:
1.闭包会使得函数中的变量都被保存在内存中,内存消耗很大。
2.闭包会在父函数外部,改变父函数内部变量的值。
详解
函数内声明的变量,函数外是无法读取的。如果要想得到局部变量只能在函数内部再定义一个函数,这样就可以在外部获取内部变量了。
例1
function f1() {
var n = 96;
function f2() {
console.log(n++);
}
return f2;
}
var result = f1();
result(); // 97
用途2:函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
例2
function f1(n) {
return function () {
return n++;
};
}
var a1 = f1(1);
a1() // 1
a1() // 2
a1() // 3
var a2 = f1(5);
a2() // 5
a2() // 6
a2() // 7
用途3:a1和a2是相互独立的,各自返回自己的私有变量。
缺点详解:
1.外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。在IE中可能导致内存泄露。
解决方法,在退出函数之前将不使用的局部变量全部删除。
2.闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,这时一定要小心,不要随便改变父函数内部变量的值。
第2题.call、apply、bind的用法分别是什么?
call是函数的正常调用方式,可指定上下文this。
apply作用和call一样,只是在调用的时传参数的方式不同。区别是 apply接受的是数组参数,call接受的是连续参数。
bind接受的参数跟call一致,都接受数组参数。只是bind不会立即调用,它会生成一个新的函数,必须调用它才会被执行,否则不会执行。
例1
function add(a,b){
return a+b;
}
add.call(add, 5, 3); //8 连续参数
add.apply(add, [5, 3]); //8 数组参数
例2
function add(a, b){
return a+b;
}
var foo1 = add.bind(add, 5,3);
foo1(); //8 调用才会执行
如果bind的第1个参数是undefined/null,this就指向全局对象window
调用的差异: call、apply会立即执行,bind必须调用才执行否则不执行。
传参的差异: call、bind接受连续参数,apply接受数组参数。
第3题.请说出至少10个HTTP状态码,并描述各状态码的意义。
1.状态码100- Continue表示继续。客户端应继续其请求
2.状态码101- Switching Protocols表示切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议
3.状态码200- OK表示请求成功。一般用于GET与POST请求
4.状态码201- Created表示已创建。成功请求并创建了新的资源
5.状态码202- Accepted表示已接受。已经接受请求,但未处理完成
6.状态码300- Multiple Choices表示多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端选择(例如:浏览器)
7.状态码301- Moved Permanently表示永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
8.状态码305- Use Proxy表示使用代理。所请求的资源必须通过代理访问
9.状态码400- Bad Request表示客户端请求的语法错误,服务器无法理解
10.状态码401- Unauthorized表示请求要求用户的身份认证
11.状态码403- Forbidden表示服务器理解请求客户端的请求,但是拒绝执行此请求
12.状态码404- Not Found表示服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面
13.状态码405- Method Not Allowed表示客户端请求中的方法被禁止
14.状态码500- Internal Server Error表示服务器内部错误,无法完成请求
15.状态码502- Bad Gateway表示作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应
第4题.如何实现数组去重?
假设有数组 array = [1,5,2,3,4,2,3,1,3,4]
你要写一个函数unique,使得unique(array) 的值为 [1,5,2,3,4]
使用Map/WeakMap以支持对象去重
方法1:使用indexOf
let arr = [1, 5, 2, 3, 4, 2, 3, 1, 3, 4]
const unique = (array) => {
let result = []
for (let i = 0; i < array.length; i++) {
if (result.indexOf(array[i]) === -1) {
result.push(array[i])
}
}
return result
}
console.log(unique(arr))
缺点:只支持数字或者字符串数组。如果数组里面有对象,比如array=[{number:1}, 2]
就会出错。
方法二:使用Set
let arr = [1, 5, 2, 3, 4, 2, 3, 1, 3, 4]
const unique = (array) => {
return [...new Set(array)] //用展开操作符将Set转换为Array
//return Array.from(new Set(array))
}
console.log(unique(arr))
缺点:API太新,旧浏览器不支持,对象不可以去重。
3.使用Map
let arr = [1, 5, 2, 3, 4, 2, 3, 1, 3, 4]
const unique = (a) => {
let map = new Map()
return arr.filter((a) => !map.has(a) && map.set(a, 1))
}
console.log(unique(arr))
缺点:API太新,旧浏览器不支持,对象不可以去重
第5题.DOM事件相关:(1)什么是事件委托?(2)怎么阻止默认动作?(3)怎么阻止事件冒泡?
(1)事件委托也称之为事件代理(Event Delegation)是js中常用绑定事件的常用技巧。
由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方法叫做事件的代理。
事件代理的原理是DOM元素的事件冒泡。
(2)一个事件处理程序的3种取消技术
var el = window.document.getElementById("a");
el.onclick =function defaultEvent(e){
if(e && e.preventDefault) { //非IE
e.preventDefault();
} else { //IE(IE11以下)
window.event.returnValue = false;
}
return false;
}
(3)w3c使用e.stopPropagation(),IE使用e.cancelBubble = true
function stopHandler(event){
window.event ? window.event.cancelBubble=true : event.stopPropagation();
}
第6题.你如何理解JS的继承?
A对象通过继承B对象,就能直接拥有B对象的所有属性和方法。
1.基于原型链的继承
基本原理是,将父类的实例赋值给子类的原型。
缺点:子类的实例可以访问父类的私有属性,子类的实例还可以更改该属性,这样不安全。
SubType.prototype = new SuperType()
SubType.prototype.constructor = SubType;
所有涉及原型链继承的继承方式都要修改子类构造函数的指向,否则子类实例的构造函数会指向SuperType
实例
function Staff() { //父类
this.company = 'tianchuang';
this.list = [];
}
Staff.prototype.getComName = function() { //父类的原型
return this.company;
};
function Coder(name, skill) { //子类
this.name = name;
this.skill = skill;
}
Coder.prototype = new Staff(); //继承Staff
因为子类原型的指向已经变了,所以需要把子类原型的contructor指向子类本身
Coder.prototype.constructor = Coder;
Coder.prototype.getInfo = function() { //给子类原型添加属性
return {
name: this.name,
skill: this.skill
};
};
let coder = new Coder('小明', 'javascript');
coder.getInfo(); // {name: '小明', skill: 'javascript'}
coder.getComName(); // 'tianchuang'
2.基于class的继承
ES6中有了类的概念,用class声明一个类,通过extends关键字来实现继承关系。
class A {}
class B extends A {
constructor() {
super();
}
}
实例
class Parent {
constructor(name) {
this.name = name;
}
static say() {
return 'hello';
}
}
class Child extends Parent {
constructor(name, age) {
super(name); //调用父类的constructor(name)
this.age = age;
}
}
var child1 = new Child('kevin', '18');
console.log(child1);
第7题.数组排序
array = [2,1,5,3,8,4,9,5]
请写出一个函数sort,使得sort(array)得到从小到大排好序的数组[1,2,3,4,5,5,8,9]
新的数组可以是在array自身上改的,也可以是完全新开辟的内存。
不得使用JS内置的sort API
//归并排序
let mergeSort = arr => {
let k = arr.length
if (k === 1) {
return arr
}
let left = arr.slice(0, Math.floor(k / 2))
let right = arr.slice(Math.floor(k / 2))
return merge(mergeSort(left), mergeSort(right))
}
let merge = (a, b) => {
if (a.length === 0) return b
if (b.length === 0) return a
return a[0] > b[0] ? [b[0]].concat(merge(a, b.slice(1))) : [a[0]].concat(merge(a.slice(1), b))
}
let array = [2,1,5,3,8,4,9,5]
mergeSort(array) // [1, 2, 3, 4, 5, 5, 8, 9]
第8题.你对 Promise 的了解?
要点
(1)Promise的用途
(2)如何创建一个new Promise
(3)如何使用 Promise.prototype.then
(4)如何使用 Promise.all
(5)如何使用 Promise.race
(1)Promise的用途
Promise对象用于表示一个异步操作的最终完成(或失败)及其结果值,是异步编程的一种解决方案。Promise就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法看,Promise 是一个对象,从它可以获取异步操作的消息。比传统的解决方案(回调函数和事件)更合理、强大。
(2)如何创建一个new Promise
return new Promise((resolve,reject)=>{...})
(3)如何使用 Promise.prototype.then
Promise实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为Promise实例添加状态改变时的回调函数。then方法的第1个参数是resolved状态的回调函数,第2个参数(可选)是rejected状态的回调函数。
const promise1 = new Promise((resolve, reject) => {
resolve('Success!');
});
promise1.then((value) => {
console.log(value); // "Success!"
});
/*---------------------------------------*/
var p1 = new Promise((resolve, reject) => {
resolve('成功!');
// reject(new Error("出错了!"));
});
p1.then(value => {
console.log(value); // 成功!
}, reason => {
console.error(reason); // 出错了!
});
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
(4)如何使用Promise.all
Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve)。如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。
一个页面聊天系统,我们需要从两个不同的URL分别获得用户的个人信息和好友列表,这两个任务是可以并行执行的,用Promise.all()实现如下
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function (results) {
console.log(results); // 获得一个Array: ['P1', 'P2']
});
(5)如何使用Promise.race
Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});
Promise.race([promise1, promise2]).then((value) => {
console.log(value); // "two"
// Both resolve, but promise2 is faster
});
第9题.说说跨域。
要点
(1)什么是同源
(2)什么是跨域
(3)JSONP跨域
(4)CORS跨域
(1)什么是同源
源=协议+域名+端口号。
如果两个url的协议、域名、端口号完全一致,那么这两个url就是同源的。同源策略即:不同源之间的页面,不准互相访问数据。例如js运行在源A里,那么就只能获取源A的数据,不允许获取源B的数据,即不允许跨域。可以通过window.origin或location.origin
得到当前源。
(2)什么是跨域
不同源页面之间的访问,浏览器试图执行其他网站的脚本。但是由于同源策略的限制,导致我们无法实现跨域。
(3)CORS跨域
CORS的全称是"跨域资源共享"。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
CORS是HTTP的一部分,它允许服务端来指定哪些主机可以从这个服务端加载资源。
例如,源B要访问源A,源A就要在响应头里声明源B可以访问,具体写法如下:
Access-Control-Allow-Origin: * // 表明该资源可以被任意外域访问。
Access-Control-Allow-Origin: http://foo.example //限制访问,只能http://foo.example访问
(4)JSONP跨域
跨域时由于某些原因不支持CORS,这时可以使用JSONP请求一个JS文件,这个JS文件执行一个回调,回调里面就是我们需要的数据。回调的名字可以是随机生成的一个随机数,传给后台并返回给我们执行。JSONP是一种依靠开发人员的聪明才智创造出的一种非官方跨域数据交互协议。
优点:兼容IE,可以跨域
缺点:因为是script标签,所以拿不到状态码也拿不到header,不支持POST只支持GET
第10题.说说你对前端的理解