一:什么是闭包?闭包的用途是什么?闭包的缺点是什么?
1:什么是闭包
我认为的闭包是:当一个函数能够访问和操作另一个函数作用域中的变量时,就构成一个闭包。闭包的特点就是能记住声明时所处的作用域,这样就能让函数在其他作用域中也能被成功调用,即使作用域消失了也还是能访问其中的变量。 以代码为例
var name ="strick"
function outer(){
var name = "freedom";
function inner(){
return name;
}
return inner;
}
var fine =outer();
result = func();
console.log(result); //"freedom"
2:闭包的作用:
1):如果在函数内创建一个闭包,就能通过闭包来访问私有变量。
function func(){
//私有变量
var name;
//设置私有变量
this.setName = function(value){
name = value;
};
//访问私有变量
this.getName = function(){
return name;
};
}
var obj = new func();
obj.setName("strick");
obj.getName(); //"strick"
2):当把函数作为值传递到某处,并在某个时刻进行回调的时候,就会创建一个闭包。当执行异步回调时,在函数内部可能需要访问或更新外部的数据(即存在于函数声明时所处作用域内的数据),借助闭包就能实现这个功能
var count = 0;
setTimeout(function(){
count++;//1
},0);
3:闭包的缺点
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
(2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
二:call、apply、bind 的用法分别是什么?
apply()、call()和bind()方法都是Function.prototype对象中的方法,而所有的函数都是Function的实例。三者都可以改变this的指向,将函数绑定到上下文中。
-
语法
1.1 Function.prototype.apply()
apply() 方法调用一个函数,
其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数。
语法:
func.apply(thisArg,[argsArray])1.2 Function.prototype.call()
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
该方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。
语法:
function.call(thisArg, arg1, arg2, ...)1.3 Function.prototype.bind()
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
语法: function.bind(thisArg[, arg1[, arg2[, ...]]]) -
用法 这三个方法的用法都非常相似,将函数绑定到上下文中,即用来改变函数中this的指向
let me = {
name:"me",
sayHello:function(age){
console.log("hello,I am",this.name + ""+age+""+"years old")
}
}
let someone = {
name:"somename"
}
me.sayHello(24) //Hello,I am me 24 years old
me.sayHello.apply(someone,[24]) //hello,I am someone 24 years old
me.sayHello.call(someone,24) //hello,I am someone 24 years old
me.sayHello.bind(someone,24) //hello,I am someone 24 years old
me.sayHello.bind(someone,([24])() //hello,I am someone 24 years old
bind方法传递给调用函数的参数可以逐个列出,也可以写在数组中。bind方法与call,apply最大的不同就是前者返回一个绑定上下文的函数,而后两着是直接执行了函数。因此,以上代码也可以这样写。
me.sayHello.bind(someone)(24)//hello,I am someone 24 years old
me.sayHello.bind(some)([24])//hello,I am someone 24 years old
总结bind()的用法:该方法创建一个新函数,称为绑定函数,绑定函数会以创建它时传入bind()的第一个参数作为this,传入bind()的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。
总结:
- 1:三者都可以改变函数的this对象指向
- 2:三者第一个参数都是this要指向的对象,如果没有这个参数,默认指向全局window
- 3:三者都可以传参,但是apply是数组,而call是有顺序的传入
- 4:bind是返回对应函数,便于稍后调用,apply,call则是立即执行
三:请说出至少 10 个 HTTP 状态码,并描述各状态码的意义。
| 状态码 | 类别 | 意义 |
|---|---|---|
| 1xx | 信息 | 请求已被接受,正在处理中 |
| 2xx | 成功 | 请求已处理成功 |
| 200 | 成功,OK | 正常返回信息 |
| 201 | Created | 请求成功并且服务器创建了新的资源 |
| 202 | Accepted | 服务器已接受请求,但尚未处理 |
| 3xx | 重定向 | 客户端需要附加操作才能完成请求 |
| 301 | Moved Permanently | 请求的网页已永久移动到新位置 |
| 302 | Found | 临时性重定向 |
| 303 | See Other | 临时性重定向,且总是使用 GET 请求新的 URI |
| 304 | Not Modified | 自从上次请求后,请求的网页未修改过 |
| 4xx | 客户端错误 | 客户端发起的请求服务器无法处理 |
| 400 | Bad Request | 服务器无法理解请求的格式,客户端不应当尝试再次使用相同的内容发起请求 |
| 401 | Unauthorized | 请求未授权 |
| 403 | Forbidden | 禁止访问 |
| 404 | Not Found | 找不到如何与 URI 相匹配的资源 |
| 5xx | 服务器错误 | 服务器在处理请求时发生错误或异常 |
| 500 | Internal Server Error | 最常见的服务器端错误 |
| 503 | Service Unavailable | 服务器端暂时无法处理请求(可能是过载或维护 |
四:如何实现数组去重?
如案例所示:
假设有数组 array = [1,5,2,3,4,2,3,1,3,4]
你要写一个函数 unique,使得unique(array) 的值为 [1,5,2,3,4]
也就是把重复的值都去掉,只保留不重复的值。
要求写出两个答案:
一个答案不使用 Set 实现
另一个答案使用 Set
(使用了 Map / WeakMap 以支持对象去重)
茴香豆的四种写法: 1:利用Map数据结构去重
function array NonRepeatfy(arr){
let map = new May();
let array = new Array();//数组用于返回结果
for (let i =0li<arr.length;i++){
if(map.has(arr[i])){
map.set(arr[i],true);
}else{
map.set(arr[i],false);
array.push(arr[i])
}
}
return array;
}
var arr = [1,1,'true',true,true,15,15,false,false,undefined,undefined];
console.log(unique(arr)//[1,'true',true,15,false,undefined]
创建一个空Mao数据结构,遍历需要去重的数组,把数组的每一个元素作为key存到Map中。由于Map中不会出现相同的key值,所以最终得到的就是去重后的结果
2:利用ES6 Set去重
function unique(arr){
return Array.form(new Set(arr)
}
var arr = [1,1,'true',true,true,15,15,false,false,undefined,undefined,{},{}];
console.log(unique(arr)//[1,'true',true,15,false,undefined,{},{}]
如果不考虑兼容性的话,这种去重的方法代码最少。而且这种方法无法去掉{}空对象。
3:利用hasOwnProperty
function unique(arr) {
var obj = {};
return arr.filter(function (item, index, arr) {
return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
})
}
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}];
console.log(unique(arr))//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}]
利用hasOwnProperty判断是否存在对象属性,去重彻底,缺点就是我看不懂
4:利用递归去重
function unique(arr) {
var array = arr;
var len = array.length;
array.sort(function (a, b) { //排序后更加方便去重
return a - b;
})
function loop(index) {
if (index >= 1) {
if (array[index] === array[index - 1]) {
array.splice(index, 1);
}
loop(index - 1); //递归loop,然后数组去重
}
}
loop(len - 1);
return array;
}
var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}];
console.log(unique(arr))//[1, "true", false, null, 0, true, 15, NaN, NaN, "NaN", "a", {…}, {…}, undefined]
五:DOM相关
1:什么是事件委托 事件委托是一种提高程序性能,减小内存空间的技术手段,它利用了事件冒泡的特性,只需在某个祖先元素上注册一个时间,就能管理其所有后代元素上同一类型的事件。
<div id="delegation">
<button type="button">提交</button>
<button type="button">返回</button>
<button type="button">重置</button>
</div>
// 给容器元素注册点击事件,那么它的三个子元素也能执行这个点击事件,
// 这其实就是一种事件委托。再通过事件对象的target属性,就能分辨出当前运行在哪个事件目标上。
<script>
var container = document.getElementById("delegation")
container.addEventListener("click",function(event){
event.target;
},false)
</script>
//使用委托可以避免对容器中的每个子元素注册时间,并且如果在容器中动态的添加子元素,
//新加入的子元素也能使用容器元素上注册的事件,而不用再单独绑定一次事件处理程序
function delegate(element, eventType, selector, fn) {
element.addEventListener(eventType, e => {
let el = e.target
while (!el.matches(selector)) {
if (element === el) {
el = null
break
}
el = el.parentNode
}
el && fn.call(el, e, el)
})
return element
}
思路是点击 span 后,递归遍历 span 的祖先元素看其中有没有 ul 里面的 li。
2:怎么阻止默认动作
//阻止浏览器的默认行为
function stopDefault(e) {
//组织浏览器默认动作(W3C)
if (e && e.preventDefault) {
e.preventDefault();
// IE中组织函数器默认动作的方式
} else {
window.event.returnValue = false;
return false;
}
}
3:阻止事件冒泡
function stopBubble(e) {
//如果提供了事件对象,则这是一个非IE浏览器
if (e && e.stopPropagation) {
//因此它支持W3C的stopPropagation()方法
e.stopPropagation();
} else {
//否则我们需要使用IE的方式来取消事件冒泡
window.event.cancelBubble = true;
}
}
六:如何理解JS中的继承
JS创造者将new关键字创建对象后面不接class,改成构造函数,又考虑到继承,于是在构造函数上加一个原型对象,最后让所有通过new构造函数创建出来的对象,就继承构造函数的原型对象的属性
function Person(){
//构造函数
this.name = "jay"
}
Person.prototype = {
sex:"male"
}
var person1 = new Person();
console.log(person1.name);//jay
console.log(person1.sex);//male
1:原型链继承
function Parent(){
this.name = ["aa","bb","cc"];
this.age = 18;
}
function Child(){
//...
}
Child.prototype = new Parent();
var child1 = new Child();
//继承name属性
console.log(child1.name);//["aa","bb","cc"]
console.log(child1.age)//18
child1.names.push("dd")
child1.age=20;
var child2=new Child();
console.log(child2.name);//["aa","bb","cc","dd"]
console.log(child2.age);//18
这两个栗子暴露出了原型链继承的两个问题: 1:包含引用类型数据的原型属性,会被所有实例共享,基本数据类型则不会 2:在创建子类型实例时,无法向父类型的构造函数中传递参数
原型式继承 原型式继承就是将父类型作为一个对象,直接变成子类型的原型对象
function object(o){
function F(){
F.prototype = o;
return new F();
}
var parent = {
age:18;
names:["aa","bb","cc"]
};
var child1 = objcet(parent);
//继承了names属性
console.log(child1.names);
child1.names.push("dd")
console.log(child1.age);
var child2 = object(parent);
console.log(child2.names);//["aa","bb","cc"]
console.log(child2.age)//18
原型式继承其实就是对原型链继承的一种封装,他要求你有一个已有的对象作为基础,但是原型式继承也有共享父类引用属性,无法传递参数的缺点。 这个方法后来有了正式的API
Object.creat({...})
所以当有一个对象,想让子实例继承的时候,可以直接用Object.creat()方法
4:ES6 class extends
class Parent {
constructor(name) {
this.name = name;
}
doSomething() {
console.log('parent do something!');
}
sayName() {
console.log('parent name:', this.name);
}
}
class Child extends Parent {
constructor(name, parentName) {
super(parentName);
this.name = name;
}
sayName() {
console.log('child name:', this.name);
}
}
const child = new Child('son', 'father');
child.sayName(); // child name: son
child.doSomething(); // parent do something!
const parent = new Parent('father');
parent.sayName(); // parent name: father
ES6 的 class extends 本质上是 ES5 的语法糖。 ES6实现继承的具体原理:
class Parent {
}
class Child {
}
Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto;
return obj;
}
// B 的实例继承 A 的实例
Object.setPrototypeOf(Child.prototype, parent.prototype);
// B 继承 A 的静态属性
Object.setPrototypeOf(Child, Parent);
5:call或apply继承
function Parent(age){
this.names = ["aa","bb","cc"]
this.age = age;
}
function Child(){
Parent.call(this,18);
}
var child = new Child();
//继承了names属性
console.log(child1.names);//["aa","bb","cc"]
child1.names.push("dd");
console.log(child1.age);/18
var child2 = new Child();
console.log(child2.names);//["aa","bb","cc"]
console.log(child2.age)//18
call或apply的原理是在子类型的构造函数中,“借调”父类型的构造函数,最终实现了子类型中拥有父类型中属性的副本
七:数组排序
给出正整数数组 array = [2,1,5,3,8,4,9,5] 请写出一个函数 sort,使得 sort(array) 得到从小到大排好序的数组 [1,2,3,4,5,5,8,9] 新的数组可以是在 array 自身上改的,也可以是完全新开辟的内存。
1):冒泡排序
通过相邻元素的比较和交换,使得每一趟循环都能找到乱序数组的最大值或最小值
function bubbleSort_twoWays(nums){
let low=0;
let high =num.length-1;
while(low<high){
let mark = true;
//找到最大值放到右边
for(let i =low;i<high;i++){
if(nums[i]>nums[i+1]){
[nums[i],nums[i+1]]=[nums[i+1],nums[i]];
mark = false;
}
}
high--;
//找到最小值放在左边
for(let j =high;j>low;j--){
if(nums[j]<nums[j-1]){
[nums[j],nums[j-1]] = [nums[j-1],nums[j]];
mark =false;
}
}
low++;
if(mark) return;
}
}
2):选择排序
选择排序将每一个元素和它后面的元素进行比较和互换
function selectSort(nums){
for(let i =0,len=nums.length;i<len;i++){
for(let j =i+1;j<len;j++){
if(nums[i]>nums[j]){
[nums[i],nums[j]]=[nums[j],nums[i]];
}
}
}
}
八:关于promise的理解
1:Promise 的用途
答:promise 是一个异步操作返回的对象,用来传递异步操作的消息。
1:可以解决的问题:
a:解决了回调地狱问题,不会导致难以维护;
b:合并多个异步请求,节约时间。
2:Promise 有三种状态:
Pending Promise 对象实例创建时的初始态;
Fulfilled 成功时的状态;
Rejected 失败时的状态。
2:如何创建一个 new Promise
Promise 有三种状态:Pending 初始态; Fulfilled 成功态; Rejected 失败态。 promise.then():用来指定 Promise 对象的状态改变时要执行的操作。
function Promise(executor) {
let self = this;
self.status = 'pending'; //等待态
self.value = undefined; //成功的返回值
self.reason = undefined; //失败的原因
function resolve(value){
if(self.status === 'pending'){
self.status = 'resolved';
self.value = value;
}
}
function reject(reason) {
if(self.status === 'pending') {
self.status = 'rejected';
self.reason = reason;
}
}
try{
executor(resolve, reject);
}catch(e){
reject(e);// 捕获时发生异常,就直接失败
}
}
//onFufiled 成功的回调
//onRejected 失败的回调
Promise.prototype.then = function (onFufiled, onRejected) {
let self = this;
if(self.status === 'resolved'){
onFufiled(self.value);
}
if(self.status === 'rejected'){
onRejected(self.reason);
}
}
module.exports = Promise;
测试一下:
let Promise = require('./Promise');
let promise = new Promise(function (resolve, reject) {
resolve(100);
})
promise.then(function (data) {
console.log('data:', data);
},function (err) {
console.log('err:', err);
})
//输出:data: 100
2:如何使用 Promise.prototype.then(可查 MDN)
Promise 实例可以多次then,当成功后会将 then 中的成功方法按顺序执行,我们可以先将 then 中成功的回调和失败的回调存到数组内。当成功的时候调用成功的数组即可。
self.onResolvedCallbacks = []; /* 存放then成功的回调*/
self.onRejectedCallbacks = []; /* 存放then失败的回调*/
function resolve(value){
if(self.status === 'pending'){
self.status = 'resolved';
self.value = value;
self.onResolvedCallbacks.forEach(function (fn) {
fn();
})
}
}
function reject(reason) {
if(self.status === 'pending') {
self.status = 'rejected';
self.reason = reason;
self.onRejectedCallbacks.forEach(function (fn) {
fn();
})
}
}
self.onResolvedCallbacks = []; /* 存放then成功的回调*/
self.onRejectedCallbacks = []; /* 存放then失败的回调*/
function resolve(value){
if(self.status === 'pending'){
self.status = 'resolved';
self.value = value;
self.onResolvedCallbacks.forEach(function (fn) {
fn();
})
}
}
function reject(reason) {
if(self.status === 'pending') {
self.status = 'rejected';
self.reason = reason;
self.onRejectedCallbacks.forEach(function (fn) {
fn();
})
}
}
实现链式调用: 众所周知 Promise 的一大特点,就是链式调用。而 Promise 实现链式调用就是通过 then 方法返回一个新的 Promise。 如果第一个 then 中返回了一个结果,会将 Promise 的结果继续传给下一个 then 中;如果有错误则走下一个 then 的失败。
// 添加 resolvePromise 方法 处理链式调用问题
function resolvePromise(p2, x, resolve, reject) {
if(p2 === x){
return reject(new TypeError('循环引用'));
}
if(x!==null || (typeof x === 'object' || typeof x === 'function')){
try{
let then = x.then;
if(typeof then === 'function'){
then.call(x, function (y) {
resolvePromise(promise2, y, resolve, reject);
},function (err) {
reject(err);
});
}else{
resolve(x);
}
}catch(e){
reject(e);
}
}else{
resolve(x);
}
}
2:如何使用 Promise.all
Promise.all():接收一个数组,数组内是 Promise 实例,必须都成功呢才表示成功。
3:如何使用 Promise.race
Promise.race():接收一个数组,数组内是 Promise 实例,最早返回的对象成功了,就变为成功态,如果失败了,就改变状态为失败态。
let fs = require('fs');
function read(url){
return new Promise(function(resolve, reject){
fs.readFile(url,'utf8',function(err, data){
if(err) reject( err);
resolve( data);
})
})
}
Promise.all([read('1.txt'), read('2.txt')]).then(function (data) {
console.log(data);
},function (err) {
console.log('err: ', err);
})
Promise.race([read('1.txt'), read('2.txt')]).then(function (data) {
console.log(data);
},function (err) {
console.log('err: ', err);
})
九:跨域相关
指路(当然这个也同样是我整理):juejin.cn/post/684490…
十:说说你对前端的理解
指路(当然这个也同样是我整理):juejin.cn/post/684490…