前端面试题目(2)

参考

事件代理

  • 可参考“JS Web API 事件”
  • 事件代理,即需要绑定事件的元素太多时将事件绑定在他们的父元素上,利用冒泡机制触发该事件(在事件函数中再通过target.nodeName来筛选出需要的子元素标签即可
  • 优点:代码简洁、减少浏览器内存占用

target、currentTarget的区别?

  • currentTarget当前所绑定事件的元素
  • target当前被点击的元素

宏任务和微任务

  • 可参考JS 异步进阶“从输入url到页面显示都经历了什么”
  • 宏任务当前调用栈中执行的任务称为宏任务。异步任务中,宏任务是浏览器规定的。 ES 语法没有,JS 引擎不处理,浏览器(或 nodejs)干预处理,处理起来就比较慢。
    • 主代码快(script),定时器(setTimeout setInterval),ajax,DOM 事件等等)。
  • 微任务: 当前(此次事件循环中)宏任务执行完,在下一个宏任务开始之前需要执行的任务为微任务。异步任务中,微任务是ES6语法规定的。 ES 语法标准之内,JS 引擎来统一处理。即,不用浏览器有任何处理,即可一次性处理完,更快更及时
  • 宏任务先于微任务执行
  • 宏任务中的事件放在callback queue中,由事件触发线程维护;微任务的事件放在微任务队列中,由js引擎线程维护。

继承的几种方式及优缺点

  • 区分 原型继承 和原型链继承
    • 原型继承: 子原型 继承 父原型
    • 原型链继承:子原型的显式原型 指向 父原型的一个实例对象(Child.prototype = new Parent();
  • 参考,除了下面几种还有 寄生式继承、寄生组合式继承

ES6 class + extends + super(常用)

  • 可参考JS class和继承 原型和原型链实际开发比较常用,写法更加面向对象,原理还是原型链
  • extends:子类继承父类,让子类获得父类的方法、属性
  • super:子类extends父类后,需要在子类的构造函数中使用super();(可传参,相当于执行父类的构造函数)
  • 子类中可扩展或重写方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    // 父类
    class People {
    constructor(name) {
    this.name = name
    }
    eat() {
    console.log(`${this.name} eat something`)
    }
    }

    // 子类
    class Student extends People {
    constructor(name, number) {
    super(name)
    this.number = number
    }
    sayHi() {
    console.log(`姓名 ${this.name} 学号 ${this.number}`)
    }
    eat() {
    console.log(`${this.name} 爱吃油炸食品`)
    }
    }

    // 子类
    class Teacher extends People {
    constructor(name, major) {
    super(name)
    this.major = major
    }
    teach() {
    console.log(`${this.name} 教授 ${this.major}`)
    }
    }

    // 实例
    const xialuo = new Student('夏洛', 100)
    console.log(xialuo.name)
    console.log(xialuo.number)
    xialuo.sayHi()
    xialuo.eat()

    // 实例
    const wanglaoshi = new Teacher('王老师', '语文')
    console.log(wanglaoshi.name)
    console.log(wanglaoshi.major)
    wanglaoshi.teach()
    wanglaoshi.eat()

原型链继承

  • 思路:Child.prototype = new Parent();
    • Child:子构造函数
    • Parent:父构造函数
    • 让 子构造函数 的显示原型 指向 父构造函数的实例,则 子构造函数的实例 不仅有 子构造函数的属性和方法,还有顺着 父父构造函数实例的隐式原型一路找到父构造函数的属性和方法
  • 可参考“JS原型和原型链”中 封装DOM查询的例子
  • 原型链继承:利用原型链来实现继承,父类的一个实例作为子类的原型,也就是 **子构造函数.prototype = new 父构造函数()**。这样 子类的实例对象 不仅有子类的属性,还有继承自 子构造函数.prototype(即 父构造函数()的一个对象)的属性。
  • 优点:原先存在父类型的实例中的所有属性和方法也能存在于子类型的原型中。
  • 缺点:(因为如下两个问题,实践中很少会单独使用原型继承
    1. 在通过原型链实现继承时,子类的原型实际上会成为父类型的实例。所以父类的实例属性实际上会成为子类的原型属性。结果就是所有的子类的实例都会共享父类的实例属性(引用类型的)
    2. 子类型实例 无法在不影响父类实例的情况下给父类型的构造函数 传递参数

借用构造函数继承(call,apply,bind)(少用)

  • 借用构造函数(经典继承):子构造函数中利用apply()/call()调用父构造函数
  • 优点:解决了“原型继承”的两种问题(可以在不影响父类实例下传参给父类)
  • 缺点:无法复用父构造函数中的函数。(因为这个问题,实践中也很少会单独使用构造函数继承
1
2
3
4
5
6
7
8
9
10
11
function SuperType() {
this.colors = ["red","blue","green"];
}
function SubType() {
SuperType.call(this);//继承了SuperType
}
var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors);//"red","blue","green","black"
var instance2 = new SubType();
console.log(instance2.colors);//"red","blue","green"

组合继承(原型链继承+借用构造函数继承)

  • 组合继承(伪经典继承):
  • 思路:使用原型链继承实现对原型属性和方法的继承,而通过借用构造函数继承来实现对父类属性的继承(注意区分构造函数和显式原型)。
    1. 使用“原型链继承”解决“借用构造函数继承”中子类无法复用父类函数的问题(子类可顺着原型链去找父类的方法)。
    2. 使用“借用构造函数继承”解决“原型链继承”中子类无法在不影响父类实例下传参给父类的问题。(使用call/apply的同时可以传参)
  • 优点:
    • 通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性
    • 避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继承模式。而且,instanceof 和 isPrototypeOf() 也能够用于识别基于组合继承创建的对象。
  • 缺点:
    • 无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
    • 没错,子类型最终会包含超类型对象的全部实例属性,
  • 例子:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    // 父类SuperType 构造函数定义了两个属性:name 和 colors
    function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
    }

    // 父类SuperType 的原型定义了一个方法 sayName()
    SuperType.prototype.sayName = function(){
    alert(this.name);
    };

    // 子类SubType 构造函数
    function SubType(name, age){
    // 继承父构造函数的属性并传参
    // (借用构造函数继承)继承父类的属性并传参数name(解决原型链继承的传参问题)
    SuperType.call(this, name);
    // 定义子类自己的属性 age
    this.age = age;
    }

    // 继承父原型的方法
    // (原型链继承)将 SuperType 的实例赋值给 SubType 的原型(解决构造函数继承无法复用父构造函数中的函数的问题)
    SubType.prototype = new SuperType();
    SubType.prototype.constructor = SubType;
    // 在该新原型上定义了方法 sayAge()
    SubType.prototype.sayAge = function(){
    alert(this.age);
    };

    var instance1 = new SubType("Nicholas", 29);
    instance1.colors.push("black");
    alert(instance1.colors); //"red,blue,green,black"
    instance1.sayName(); //"Nicholas";
    instance1.sayAge(); //29

    var instance2 = new SubType("Greg", 27);
    alert(instance2.colors); //"red,blue,green"
    instance2.sayName(); //"Greg";
    instance2.sayAge(); //27
    这样一来,就可以让两个不同的 SubType 实例既分别拥有自己属性(包括 colors 属性),又可以使用相同的方法了。

原型式继承(少用)

1
2
3
4
5
6
// 可用以下函数说明原型式继承的思想
function object(o) {
function F(){}
F.prototype = o;
return new F();
}
  • 原型式继承:借助原型可以基于已有的对象创建新对象,同时还不必须因此创建自定义的类型。
1
2
3
4
5
6
7
8
9
10
11
var person = {
name:"EvanChen",
friends:["Shelby","Court","Van"];
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
console.log(person.friends);//"Shelby","Court","Van","Rob","Barbie"

闭包

  • 可参考“JS作用域和闭包”
  • 闭包的实质是因为函数嵌套形成的作用域链
  • 闭包的定义即:函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包(闭包就是定义在一个函数内部的函数
  • 闭包作用:让一个函数有权访问另一个函数作用域(闭包)中的变量。
  • 为什么使用闭包:闭包常常用来「间接访问一个变量」。换句话说,「隐藏一个变量」。
  • 闭包特性:
    • 函数中定义函数
    • 封闭性外界无法访问闭包内部的数据,如果在闭包内声明变量,外界是无法访问的,除非闭包主动向外界提供访问接口;
    • 持久性:一般的函数调用完毕之后,系统自动注销函数,而对于闭包来说,在外部函数被调用之后,闭包结构依然保存在(参数和变量不会在函数被调用完后被垃圾回收机制回收)

export和export default的区别

  • 使用上的不同,可参考《export、export default和module.exports、exports》
  • export default在同一个模块中只能出现一次,export导出多个对象,export default只能导出一个对象
  • export导出对象需要用{ },export default 不需要{ }
  • 仅允许表达式、函数或类作为export default导出,你想使用{ }导出对象时只能使用default。
  • 性能上export会略微好一些,建议使用export
    1
    2
    3
    4
    5
    export default  xxx
    import xxx from './'

    export {xxx,yyy,zzz}
    import {xxx} from './'

常用的es6的功能

常用的CSS3、HTML5

参考前端基础-HTTP/HTML/浏览器(3)

会话cookie 持久cookie

  • 指定了expires属性的是持久cookie,没有指定的是会话cookie
    • 如果expires设置一个过去的时间点,那么这个cookie 会被立即删掉(失效)
  • cookie是一些数据, 存储于你电脑上的文本文件中。(可参考“JS Web API 存储”
  • 当 web 服务器向浏览器发送 web 页面时,在连接关闭后,服务端不会记录用户的信息。Cookie 的作用就是用于解决 “如何记录客户端的用户信息”:
    • 当用户访问 web 页面时,他的名字可以记录在 cookie 中。
    • 在用户下一次访问该页面会在请求中带上cookie,这样服务器就可以在 cookie 中读取用户访问记录。

数组去重(5种方法)

js中3种方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
var arr=['12','32','89','12','12','78','12','32'];
// 方法1:最简单数组去重法(遍历结果数组,没有的就加进去)
function unique1(array){
var n = []; //一个新的临时数组
for(var i = 0; i < array.length; i++){ //遍历当前数组
if (n.indexOf(array[i]) == -1) //没有找到的情况下返回-1
n.push(array[i]);//数组尾部添加元素
}
return n;
}
arr=unique1(arr);

// 方法2:速度最快, 占空间最多(空间换时间)
function unique2(array){
var n = {}, r = [], type;
for (var i = 0; i < array.length; i++) {
type = typeof array[i];
if (!n[array[i]]) {
n[array[i]] = [type];
r.push(array[i]);
} else if (n[array[i]].indexOf(type) < 0) {
n[array[i]].push(type);
r.push(array[i]);
}
}
return r;
}

//方法3:数组下标判断法(遍历原数组,符合的就加进结果数组中)
function unique3(array){
var n = [array[0]]; //结果数组
for(var i = 1; i < array.length; i++) { //从第二项开始遍历
//注意:indexOf遇到多个时返回第一个的位置
if (array.indexOf(array[i]) == i)
n.push(array[i]);
}
return n;
}

ES6的2种方法:

1
2
3
4
5
6
7
8
// 方法1:注意Set对象不是数组,需要转换为数组
arr=[...new Set(arr)];

// 方法2:
function dedupe(array) {
//Array.from()能把set结构转换为数组
return Array.from(new Set(array));
}

get、post的区别

  1. 传参方式get传参方式是通过地址栏URL传递,是可以直接看到get传递的参数,post传参方式参数URL不可见,get把请求的数据在URL后通过连接,通过&进行参数分割psot将参数存放在HTTP的包体内
  2. 参数长度限制: get传递数据是通过URL进行传递,对传递的数据长度是受到URL大小的限制,URL最大长度是2048个字符post没有长度限制
  3. get后退不会有影响,post后退会重新进行提交
  4. 缓存: get请求可以被缓存,post不可以被缓存(标准如此,但是现在浏览器他们都能被缓存)
  5. 编码方式: get请求只支持URL编码,post支持多种编码方式
  6. 历史记录: get请求的记录会留在历史记录中,post请求不会留在历史记录
  7. 字符类型: get只支持ASCII字符,post没有字符类型限制

http的响应码及含义

  • 1xx(临时响应)
    • 100: 请求者应当继续提出请求。
    • 101(切换协议) 请求者已要求服务器切换协议,服务器已确认并准备进行切换。
  • 2xx(成功)
    • 200:正确的请求返回正确的结果
    • 201:表示资源被正确的创建。比如说,我们 POST 用户名、密码正确创建了一个用户就可以返回 201。
    • 202:请求是正确的,但是结果正在处理中,这时候客户端可以通过轮询等机制继续请求。
  • 3xx(已重定向)
    • 300:请求成功,但结果有多种选择。
    • 301:请求成功,但是资源被永久转移。
    • 303:使用 GET 来访问新的地址来获取资源。
    • 304:请求的资源并没有被修改过
  • 4xx(请求错误)
    • 400:请求语法出现错误,比如请求头不对等。
    • 401:未授权的请求,没有提供身份认证信息。请求的时候没有带上 Token 等。
    • 402:为以后需要所保留的状态码。
    • 403:请求的资源不允许访问。就是说没有权限
    • 404:请求的内容不存在
  • 5xx(服务器错误)
    • 500:服务器错误。
    • 501:请求还没有被实现。
,