高级前端软件工程师知识整理之基础篇(三)

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

 

  • 16
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值