11. 介绍一下sessionStorage 、localStorage 和 cookie 之间的区别?cookie和token作为验证手段又有什么区别?
sessionStorage 、localStorage 和 cookie 都保存在浏览器端,它们的区别是:
- sessionStorage:作为临时数据存储,在不同的浏览器窗口里并不共享,当关闭浏览器后将会被自动删除。
- localStorage:存储的数据始终有效,而且在不同的浏览器窗口可以共享数据,当关闭浏览器后也不会被自动删除。
- cookie:存储的数据用于在服务端与浏览器之间传递,只要发生请求都会自动加到HTTP的请求数据中,常用于辨别用户身份,一般都会经过加密处理。
token是经过一定规则加密的信息字段,是前后端分离后比较常用的一种验证手段,由服务端生成并存储在客户端,在客户端向服务端发送请求时,会把token放在header中一起发送给服务端以验证请求的有效性。
token与cookie的区别是:
- cookie:cookie的验证是有状态的,就是说验证或者会话信息必须同时在客户端和服务端保存。这个信息服务端一般在数据库中记录,而前端会保存在cookie中。
- token:token的验证是无状态的。服务器不记录哪些用户已登陆。对服务器的每个请求都需要带上验证请求的token。该标记既可以加在header中,可以在POST请求的主体中发送,也可以作为查询参数发送。
token相对cookie有更多的优势:
- 优势1:它是无状态的,后端服务不需要记录token。每个令牌都是独立的,包括检查其有效性所需的所有数据,并通过声明传达用户信息。
- 优势2:防跨站请求伪造(CSRF),如防止已验证的cookie被拦截。
- 优势3:一个token支持多站点使用,而cookie只能是一对一。
- 优势4:支持移动端。
- 优势5:性能更优,免去服务器向数据库查询session信息以验证cookie。
- 优势6:现在的后端API大多设计成规范的无状态RESTful,token更搭配。
12. 介绍从输入URL到页面加载全过程?
这是一道经典的面试题,涉及到的知识点也很多。其大概过程如下:
(1)浏览器的地址栏输入URL并按下回车。
URL的构成包括:
其中主机为域名+端口,http协议默认端口80,https协议默认端口443。
(2)浏览器查找当前URL是否存在缓存,并检测缓存是否过期。
缓存的常用字段是last-modified和Etag:
- last-modified — 第一次请求资源时,服务器返回的字段,表示最后一次服务更新的时间。
- Etag — 资源的实体标识(哈希字符串),当资源内容更新时,Etag会改变。
如果缓存已过期就返回新资源给浏览器,否则返回403状态码告诉服务器使用缓存内容,渲染界面。
(3)DNS域名解析
这步是指将域名解析成IP地址,选择解析器的顺序为:本地hosts文件、本地DNS解析器缓存、本地DNS解析器。只要其中一步解析成功就停止解析,进入下一步。
(4)TCP连接
获取到具体IP地址后,便会开始建立一次连接,这是由TCP协议完成的,主要通过三次握手进行连接。主要目的是检查服务器是否在线,如果在线则开始发送数据请求。
(5)浏览器向服务器发送HTTP请求
请求遵循HTTP协议,格式包括请求行、请求头和请求体三部分
(6)服务器响应
格式格式包括状态行、响应头和响应体三部分
(7)页面渲染
浏览器渲染页面。
13. == 和 === 的区别,什么情况下用 ==?
==两个等号称为等值符,当等号两边的值为相同类型时比较值是否相同,类型不同时会发生类型的自动转换,转换为相同的类型后再作比较。
===三个等号称为等同符,当等号两边的值为相同类型的时候,直接比较等号两边的值,值相同则返回true,若等号两边的值类型不同时直接返回false。
有三种情况下可以使用等值符==:
- 清楚两个比较对象是同等类型的时候,因为类型相同其意义等同于===。
- undefined和null比较的时候,因为它们都表示无意义,undefined == null、null == null 都返回true。
- 字符串和数组比较的时候,可以实现字符串格式自动转换为数字后再比较。
14. bind、call、apply的区别是什么?
bind、call、apply都是函数自带的方法,目的都是用于改变自身this的指向,而在ES5中,function(){ this.x = xxx } 中的this是执行中绑定的。关于this的概念可以查看《18~19年大厂高级前端面招汇总之基础篇(二)》中的 “10.介绍this各种情况”。 bind、call、apply的区别是:
apply和call意义是一样的,只是入参方式不同;bind则是会生成一个新的实例函数,需要通过该实例函数触发todo函数。看下面代码:
function todo(key){
console.log(key, this.a);
}
var a = 1;
var obj = {a:2};
todo('fun'); // this指向window全局变量。打印:fun,1
todo.call(obj,'call'); // this指向obj,打印:call,2
todo.apply(obj,['apply']); // this指向obj,打印:apply,2
var bindTD = todo.bind(obj); // this指向obj,生成一个新的实例函数bindTD
bindTD('bind'); // 打印:bind,2
注意,如果obj也是一个函数,则可以用arguments表示其入参,如:
function todo(key) {
console.log(key, this.a);
}
var a = 1;
var obj = function(){
this.a = 2;
return todo.apply(this, arguments)
}
obj('apply'); // apply 2
15. 介绍一下原型链?
原型链示意图,这些指向就是原型链,原型和原型链常用于实现继承。
要千万记住一点:__proto__是每个对象都有的一个属性,而prototype是函数才会有的属性!!! function Person() { } 是函数,var person 则是对象。
详细可以看一下我的另一篇文章《透彻解读prototype与__proto__》
16. ES6中的Map和原生对象Object有什么区别?
JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
上面的例子展示了如何向 Map 添加成员。作为构造函数,Map 也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。
const map = new Map([
['name', '张三'],
['title', 'Author']
]);
map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"
Map 结构原生提供三个遍历器生成函数和一个遍历方法。
keys()
:返回键名的遍历器。values()
:返回键值的遍历器。entries()
:返回所有成员的遍历器。forEach()
:遍历 Map 的所有成员。
const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
Map 的forEach
方法,与数组的forEach
方法类似,也可以实现遍历。
map.forEach(function(value, key, map) {
console.log("Key: %s, Value: %s", key, value);
});
forEach
方法还可以接受第二个参数,用来绑定this
。
const reporter = {
report: function(key, value) {
console.log("Key: %s, Value: %s", key, value);
}
};
map.forEach(function(value, key, map) {
this.report(key, value);
}, reporter);
Map转换为数组可以使用...扩展符,如
const myMap = new Map()
.set(true, 7)
.set({foo: 3}, ['abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
其它更详细方法可以参考http://es6.ruanyifeng.com/#docs/set-map#Map
17. 如何设计一个localStorage,保证数据的实效性?
设计思路:在localStorage存储键值的同时,多存入一组带时间参数的键值对象,该对象的键由key+标识符组成,值为有效期时间。在取值时,先验证是否在有效期范围,如果已经过期则返回null。其存储效果如图:
具体实现代码如下:
// 添加带有效期的set方法,expired 单位(毫秒)
localStorage.__proto__.setExpiredItem = function(key, value, expired){
this.setItem(key, value);
if(expired){
this.setItem([`${key}__expires__`], Date.now() + expired);
}
}
// 添加带有效期的get方法
localStorage.__proto__.getExpiredItem = function(key){
var expired = this.getItem([`${key}__expires__`]);
var result;
if(expired){
var nowDate = Date.now();
if(nowDate-expired > 0){
// 已经超时, 删除该键值
this.removeItem(key);
this.removeItem([`${key}__expires__`]);
result = null;
}else{
result = this.getItem(key);
}
}else{
result = this.getItem(key);
}
return result;
}
// 过期。结果为null,而且localStorage里的相关键值被移除
localStorage.setExpiredItem('id','1', 4000);
setTimeout(function(){
console.log(localStorage.getExpiredItem('id')); // null
},5000)
// 未过期。结果为1,localStorage里键值依旧存在
localStorage.setExpiredItem('id','1', 6000);
setTimeout(function(){
console.log(localStorage.getExpiredItem('id')); // 1
},5000)
// 当未设置有效期时,效果等同于setItem和getItem
localStorage.setExpiredItem('id','1');
setTimeout(function(){
console.log(localStorage.getExpiredItem('id')); // 1
},5000)
18. 编写一个求和函数sum,使输入sum(2)(3)或输入sum(2,3),输出结果都为5
思路:使用arguments,要记住,arguments是函数自带的表示参数的对象,格式为数组。
function sum(){
if(arguments.length == 1){
var first = arguments[0];
return function(second){
return (first+second);
}
}else{
var total = 0;
for(var i=0;i<arguments.length;i++){
total += arguments[i];
}
return total;
}
}
console.log(sum(2,3)); // 5
console.log(sum(2)(3)); // 5
19. 如何比较两个对象是否相同?
转换成字符串格式后比较,JSON.stringify(objA)==JSON.stringify(objB),格式可以任何字符串、数字、数组或对象。
20. 介绍变量的作用域链?
当代码在一个环境中执行时,就会创建变量对象的作用域链。作用域链(Scope Chain)是javascript内部中一种变量、函数查找机制,它决定了变量和函数的作用范围。举个例子,每一段js代码(全局代码和函数)都有一个与之关联的作用域链,这个作用域链是一个对象列表或者链表,这组对象定义了这段代码‘作用域中的变量’,当js需要查找变量x的值得时候,它会从链的第一个对象开始查找,如果这个对象有个一个名为x的属性,则直接使用这个属性的值,如果不存在x属性,js会查找链上的下一个对象,如果仍然没有则继续下一个对象,以此类推。如果该链上没有任何一个对象用于x属性,那么就认为该段代码作用域链上不存在x,最终抛出一个引用异常错误。
判断方法:看函数构造体本身外层是否有其它函数,如果有,外N层函数中的作用域就是该变量的作用域,越靠近优先级越高; 如果没有,就是window。如:
var a = 1;
// 这里funA构造体本身外层就是window,故a取值于全局变量
function funA(){
console.log(a);
}
function funB(){
var a = 2;
funA();
}
funB(); // 1
var a = 1;
// 这里funA构造体本身外层是funB,故a取值于funB的作用域
function funB(){
var a = 2;
var funA = function (){
console.log(a);
}
funA();
}
funB(); // 2