一个7月小总结

509 阅读7分钟

什么是闭包?闭包的用途是什么?闭包的缺点是什么?

什么是闭包: 闭包也可以被称为函数闭包,通过一个函数实现和周围环境的绑定,从而借由该函数获取周围变量的值。 闭包的用途是什么: 在ES6之前,JavaScript的变量以var存在,如果一个var属于全局变量,那么它在函数内部也可以被读取。 但函数外部则无法读取函数内部的局部变量。因此通过在一个函数内部重新定义一个子函数并将它返回,就能够通过这个子函数获取函数内部的变量,这个子函数被称为闭包。

function test(){
    let testValue = 1;
    function testClosure(){
        return testValue;
    }
    return testClosure;
}

let a = test();
console.log(a());

如上所示,通过函数test()的闭包testClosure(),获得了test内部的变量testValue。 闭包的缺点是什么: 上面这段代码中,test()被赋值给了一个全局变量a,因此test()及其包含的函数和变量都将常驻于内存中,调用结束后也不会被垃圾回收机制清理。闭包对内存的消耗较大,为了性能和安全考虑需要手动清理,防止出现内存泄露。 闭包与其父函数形似对象,如果父函数=对象,闭包=public method,内部变量=private variable,那么通过闭包,外部和内部都能操作内部变量,容易产生误解。

call、apply、bind 的用法分别是什么?

要理解call, apply, bind首先需要理解this\

this

参考:(JavaScript 的 this 原理)[www.ruanyifeng.com/blog/2018/0…]

var obj = {
  foo: function () { console.log(this.bar) },
  bar: 1
};

var foo = obj.foo;
var bar = 2;

obj.foo() // 1
foo() // 2

this在函数体内部,指代函数执行时的上下文环境。obj.foo()的运行环境指向obj,而foo()的运行环境指向当前的全局环境,也就是window,从window的属性中找到了bar。

this通常有四种调用模式:函数调用模式,方法调用模式,构造器调用模式,call/apply调用模式。

函数调用模式

函数调用模式即在函数中使用this后直接调用该函数:

function f1(){
    console.log(this);
}
f1();

此时this指向的上下文环境为window

方法调用模式

Method Invocation Pattern 在面向对象程序设计中,当函数(Function)作为对象属性时被称为方法(Method)。方法被调用时 this 会被绑定到对应的对象。 当一个函数被保存为对象的一个方法时,如果调用表达式包含一个提取属性的动作,那么它就是被当做一个方法来调用,此时的this被绑定到这个对象。

let a = 1;
let obj = {
    a:2,
    f2: function(){
        console.log(this.a);
    }
}
obj.f2();

打印结果为2。 obj.f2()即为将f2()绑定在obj上,此时f2()的上下文环境即为obj内部环境。 因此, obj.f2() 等同于 obj.f2().call(obj)

构造器调用模式

构造函数调用模式的特征:

  1. 构造函数的首字母一般要大写
  2. 一般情况下和关键字 new 一起使用
  3. 构造函数中的 this 指向 new 关键字创建出来的实例对象
  4. 默认返回 new 创建出来的这个对象(this

new一个函数时,实际上会创建一个连接到prototype成员的新对象,同时this会被绑定到那个新实例对象上。

function Person(name,age){
// 这里的this都指向实例
    this.name = name;
    this.age = age;
    this.getAge = function(){
        console.log(this.age);
    }
}
 
var person = new Person('Alice,1);
person.getAge();

打印结果为1。

call(), apply(), bind()

call(), apply()的用法

call(), apply()非常接近,call 是 bind 的语法糖,用来向函数指定this和传递参数。
唯一的不同在于两者接受的参数类型不一样。

call(this, args, args, ...);
apply(this, [argsArray]);

call() 除指定的 this 之外,接受数个参数。
apply() 接受指定的 this ,只接受一个数组形式的参数
如果 this = undefined || null,则this指向window

bind()用法

bind 方法直接改变函数的 this 指向并且返回一个新的函数,之后再次调用这个函数的时候 this 都是指向 bind 绑定的第一个参数。 bind 第二个参数开始是传入的参数列表,常被用于指定函数的默认参数。

let alice = {
    name: 'alice',
    age: 1
}
function Person(name, age){
    console.log(`this: ${this.name} ${this.age}`);
    console.log(`default: ${name} ${age}`);
    console.log(arguments)
}
let test = Person.bind(alice,'Bob',2);
test('third one', 3);

结果如下

this: alice 1
default: Bob 2
Arguments(4) ["Bob", 2, "third one", 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]

请说出至少 10 个 HTTP 状态码,并描述各状态码的意义。

响应分为五类:信息响应(100199),成功响应(200299),重定向(300399),客户端错误(400499)和服务器错误 (500599)

100 Continue:

临时响应。客户端应该继续请求

200 Ok:

请求成功。根据http的四种方法有四种含义

206 Partial Content:

部分成功。已成功处理部分GET请求。多用于实现断点续传或大文件传输,也可用于分段视频传输。请求中必须包含Range以表明需要请求的文件范围。

301 Moved Permanently:

永久移动。被请求的资源已永久移动到新位置。该状态码表示所请求的URI资源路径已经改变,新的URL会在响应的Location:头字段里找到。最好是在应对 GET 或 HEAD 方法时使用301。

308 Permanent Redirect:

永久重新定向。语义与301等同,但是用户不能更改使用的http方法。

302 Found:

临时重新定向。所请求的URI资源路径临时改变,并且还可能继续改变.因此客户端在以后访问时还得继续使用该URI.新的URL会在响应的Location:头字段里找到。

400 Bad Request:

错误请求。服务器无法理解请求,语义或参数有误。

403 Forbidden:

禁止访问。服务器已理解请求,但拒绝执行,客户端没有权利访问所请求内容。

404 Not Found:

请求失败。服务器无法找到被请求的内容。

502 Bad Gateway:

网关错误。服务器从上游服务器获得了一个错误的http响应。

如何实现数组去重?

假设有数组 array = [1,5,2,3,4,2,3,1,3,4]
你要写一个函数 unique,使得 unique(array) 的值为 [1,5,2,3,4]
也就是把重复的值都去掉,只保留不重复的值。

要求写出两个答案:

  1. 一个答案不使用 Set 实现(6分)
  2. 另一个答案使用 Set (4分)
  3. (附加分)使用了 Map / WeakMap 以支持对象去重的,额外加 5 分。
  4. 说出每个方案缺点的,再额外每个方案加 2 分。
Map 去重

Map 有点类似于其他语言中的 dictionary,由[key]value构成,key 不可重复。借由 has() 方法查验 key 是否已经存在。

// Use Map
function unique(array){
    let map = new Map();
    let unique_array = new Array();

    for(let i=0; i<array.length; i++){
        if(map.has(array[i]) === false){
        map.set(array[i], true);
        unique_array.push(array[i]);
        }
    }
    return unique_array;
}
console.log(unique([1,5,2,3,4,2,3,1,3,4]));
Set 去重

Set 只拥有 key,且 key 不能重复,借由这一特性可直接完成去重。

// Use Set()
function unique(array){
    return Array.from(new Set(array));
}
console.log(unique([1,5,2,3,4,2,3,1,3,4]));
缺点

无论是Set还是Map都是ES6中出现的新数据结构,尽管语法简单高效,但ES6之前都无法使用,存在兼容性问题。
对象不能作为key,因此Set和Map都无法进行对象去重。

参考:
lequ7.com/JavaScript-…
juejin.cn/post/684490…

DOM 事件相关

  1. 什么是事件委托?4分
  2. 怎么阻止默认动作?3分
  3. 怎么阻止事件冒泡?3分

juejin.cn/post/695578…

你如何理解 JS 的继承?

  1. 答出基于原型的继承给 5 分
  2. 答出基于 class 的继承给 5 分

区别于 Java或者 C++,JavaScript 中的 class 和 extends 都可以算作语法糖。JS并没有真正实现class,所有关于继承的操作仍然基于原型链。

JavaScript只有一种结构:对象。
每个 object 都有一个私有属性 __proto__ 指向其构造函数的prototype。
每个构造函数的 __proto__ 指向其父类构造函数的 prototype。

ES6 extends 实质完成了每个 object 和构造函数的__proto__和prototype指向,构成原型链。

super 则等同于 call() 和 bind() 所进行的传参,将父类构造函数的属性传给子类。

数组排序

给出正整数数组 array = [2,1,5,3,8,4,9,5]
请写出一个函数 sort,使得 sort(array) 得到从小到大排好序的数组 [1,2,3,4,5,5,8,9]
新的数组可以是在 array 自身上改的,也可以是完全新开辟的内存。

不得使用 JS 内置的 sort API

function mergeSort(arr){
    let len = arr.length;
    if(len < 2){
        return arr;
    }
    let left = arr.slice(0,Math.floor(len/2));
    let right = arr.slice(Math.floor(len/2),len);
    return merge(mergeSort(left),mergeSort(right))
}
function merge(left,right){
    if(left.length === 0){
        return right;
    }
    if(right.length === 0){
        return left;
    }
    return left[0] > right[0] ? [right[0]].concat(merge(left,right.slice(1))) : [left[0]].concat(merge(left.slice(1),right))
}
numbers =  [2,1,5,3,8,4,9,5]
console.log(mergeSort(numbers));

你对 Promise 的了解?

答题要点:

  1. Promise 的用途
  2. 如何创建一个 new Promise
  3. 如何使用 Promise.prototype.then(可查 MDN)
  4. 如何使用 Promise.all(可查 MDN)
  5. 如何使用 Promise.race(可查 MDN)

juejin.cn/post/698733…

说说跨域。

要点:

  1. 什么是同源
  2. 什么是跨域
  3. JSONP 跨域
  4. CORS 跨域

juejin.cn/post/698761…

对前端的理解

前端和后端的区分应该来自于前后端分离的思想。对用户来说,“前端”代表软件中一切可被看见,理解和操作的部分,“后端”则负责支持和处理前端所提供和反馈的信息。 对开发者来说,狭义前端指仅指web前端,也即html+css+js所构造的世界。而现在有了大前端的概念,囊括了网站、Android客户端、iOS客户端和微信小程序等。一次开发,通过跨平台技术解决问题,对知识面的要求高,却是跳出限制且更有希望的方向。 无论使用哪一种技术,前端的本质都是实现人机交互,方便用户阅读、理解和使用软件和硬件的各种功能。单纯的按照设计实现确实是稳妥的,但如果在实现的过程中能够融入对交互和安全的理解或许能够减少未来的许多问题。
前端工程师需要的不仅是写代码的能力,也要具备基本的UI设计知识,了解基本的可用安全常识,也需要一些与后端和数据库相关的经验,以便于更快的定位问题,解决问题。