https://segmentfault.com/a/1190000018719463
https://godbmw.com/passages/2019-03-27-javascript-second/
最近在准备面试,过程中遇到了很多自己看似知道,其实并不准确的知识点,在这里记录一下。
一、Javascript
1、深拷贝和浅拷贝
- 在JS中,函数和对象都是浅拷贝(地址引用);其他的,例如布尔值、数字等基础数据类型都是深拷贝(值引用)。ES6的Object.assign()和ES7的…解构运算符都是“浅拷贝”。
- 如何实现深拷贝?自己手动撸“轮子”或者借助第三方库(例如lodash):
-
手动实现深拷贝(递归):
//定义检测数据类型的函数 function checkedType(target){ return Object.prototype.toString.call(target).slice(8,-1) } //定义深拷贝函数 function clone(target){ let result, type = checkedType(target) if(type === 'Array'){ result = [] }else if(type === 'Object'){ result = {} }else{ return target } //遍历目标数据 for(let item in target){ let value = target[item] if(checkedType(value)==='Object' || checkedType(value)==='Array'){ result[item] = clone(value) }else{ result[item] = value } } return result }
-
借助第三方库:jq的extend(true, result, src1, src2[ ,src3])、lodash的_.cloneDeep(src)
-
JSON.parse(JSON.stringify(src)):这种方法有局限性,如果属性值是函数或者一个类的实例的时候,无法正确拷贝
-
借助HTML5的MessageChannel:这种方法有局限性,当属性值是函数的时候,会报错
2、数据类型的判断
js中的基本类型有六种:Number、String、Boolean、Null、Undefined和es6中的symbol;Object不属于基本类型,它是复杂类型。
- 使用typeof,缺点是不能判断null、[]、Date、RegExp等类型,因为都会返回object。
typeof Symbol() //symbol
typeof '' //string
typeof 1 //number
typeof true //boolean
typeof undefined //undefined
typeof new Function() //function
typeof null //object,无法判断
typeof [] //object,无法判断
typeof new Date() //object,无法判断
typeof new RegExp() //object,无法判断
- instanceof 表达式为:A instanceof B,如果A是B的实例,则返回true.不能用来检测null和undefined和基本类型.
- constructor 起作用与instanceof类似,但其还可以检测基本数据类型,不过这个方法是不稳定的,这主要体现在把类的原型进行重写,在重写的过程中很有可能出现把之前的constructor给覆盖了,这样检测的结果就不准确了.
- Object.prototype.toString.call(target),这个是最准确最常用的方式,都可以检测出来.
Object.prototype.toString.call(null) //[object Null]
Object.prototype.toString.call([]) //[object Array]
Object.prototype.toString.call(1) //[object Number]
3、this 指向问题
有如下几种情况:
- 情形1:对于直接调用函数来说,不管该函数放在什么地方,this一定是window;
- 情形2:对于obj.func()来说,谁调用了函数,谁就是this,所以此时func函数的this就是obj对象;
- 情形3:在构造函数中,类中(函数体中)出现的this.xxx=xxxx中的this是当前类的一个实例;
- 情形4:call\apply\bind:this是第一个参数;
- 情形5:箭头函数this指向:箭头函数没有自己的this,看其外层是否有函数,如果有呢,外层函数的this就是内部箭头函数的this,如果没有,则this就是window。
//情形1
function func(){
console.log(this.a)
}
var a =1
func()// 1
//情形2
function func(){
console.log(this)
}
var obj = {fn:fn}
obj.fun() //this -> obj
//情形3
function Fun(name,age){
this.name=name;
this.age =age;
}
var p1 = new Fun("alex",24)//this->p1
//情形4
function add(c,d){
return this.a + this.b+c+d
}
var obj = {a:1,b:2}
add.call(obj,10,20)//this->obj
add.apply(obj.[10,20]) //this->obj
//情形5
let obj={
name:'alex',
getName:function(){
return ()=>{
console.log(this) //obj
}
}
}
4、 同步与异步
- 场景:
- 定时任务:setTimeout,setInterval
- 网络请求:ajax请求,动态加载
- 事件绑定
- Event Loop
//异步任务
Promise.resolve().then(()=>{
console.log('Promise1')
setTimeout(()=>{
console.log("setTimeout2")
},0)
})
//同步任务
console.log(1)
setTimeout(()=>{
console.log("setPromise2")
Promise.resolve().then(()=>{
console.log('Promise2')
})
})
//最终输出
1
"Promise1"
"setPromise2"
"Promise2"
"setTimeout2"
下面这段代码输出顺序?(小米面试题)
setTimeout(function(){
console.log("time1")
})
var pro = new Promise(function(resolve,reject){
console.log("pro1")
setTimeout(function(){
console.log("time2")
},3000)
console.log("pro2")
})
pro.then(function(){
console.log("pro3")
})
setTimeout(function(){
console.log("time3")
})
5、原型链与继承
原型链+借用构造函数的组合继承
。核心是在子类构造函数中通过call方法继承父类的属性,然后改变子类的原型来继承父类的函数。
- 优点:构造函数可以传参,不会与父类引用属性共享,可以复用父类的函数
- 缺点:继承父类函数的时候调用了父类的构造函数,导致子类原型上多了不需要的父类属性,存在内存上的浪费。同时会导致子类的构造函数是Parent.
function Parent(value){
this.val = value
}
Parent.prototype.getValue = function(){
console.log(this.val)
}
function Child(value){
Parent.call(this,value)
}
Child.prototype = new Parent();
const child = new Child(1);
child.getValue(); //1
console.log(child instanceof Parent); //true
寄生组合方式
。核心是将父类的原型赋值给子类,并且将构造函数设置为子类,这样既解决了无用的父类属性问题,还能正确找到子类的构造函数。
function Parent(value){
this.val = value
}
Parent.prototype.getValue = function(){
console.log(this.val)
}
function Child(value){
Parent.call(this,value)
}
Child.prototype = Object.create(Parent.prototype,{
constructor:{
value:Child,
enumerable:false,
writable:true,
configurable:true,
}
})
const child = new Child(1)
child.getValue() //1
console,log(child instanceof Parent)//true
- ES6中的class继承。class可以通过extends关键字实现继承,还可以通关static关键字定义类的静态方法,其只是语法糖,本质仍然是基于原型实现的。
class Parent{
constructor(value){
this.val = value
}
getVal(){
console.log(this.val )
}
}
class Child extends Parent{
constructor(value){
super(value)
}
}
let child = new Child(1)
child.getVal()//1
console.log(child instanceof Parent)
6、DOM操作与BOM操作
- 事件代理
- 事件流
- 捕获阶段
- 目标阶段
- 冒泡阶段
7、什么是重绘和回流?
8、let、const、var
//情形一
var a;
console.log(a);//输出什么?(undefined)
a = 1;
//情形二
var a = 1;
console.log(a);//输出?
a=2;
//情形三
//可以重复声明b吗?
let b = 1;
try{
console.log(b);
let b = 2;
}catch(e){
conosle.log(e)
}
//情形四,可以这样重复声明b吗?
let b = 1;
function test(){
let b = 2;
console.log(b)
}
9、以下代码输出顺序是什么?
代码二
var bb = 1;
function aa(bb) {
bb = 2;//bb虽然没有用var声明,但是bb并不是全局中的bb,因为函数aa传进了一个参数bb,相当于在函数体内声明了bb,所以此处的bb是引用的arguments对象上的bb
alert(bb);
};
aa(bb);
alert(bb);
代码三
下面span标签的width、height分别是多少?
解析:首先span是行内标签,不支持设置宽高,但其float设置了left,所以span转换成块级元素,支持狂傲,height100%,所以height=200px;而width=auto,由内容撑开,但i标签是absolute,脱离文档流,所以span标签的width=0。
<div style=”width:400px;height:200px;”>
<span style=”float:left;width:auto;height:100%;”>
<i style=”position:absolute;float:left;width:100px;height:50px;”>hello</i>
</span>
</div>
代码四
考察函数声明和函数表达式的区别,函数声明,可以提前调用函数;而函数表达式不可以,因为赋值操作不会被提前。
(function() {
var x=foo();
var foo=function foo() {
return “foobar”
};
return x;
})();
10、HTTP/HTTPS
超文本传输协议,是一个基于请求与相应、无状态的应用层协议,常基于TCP/IP协议传输数据。
版本之间差异:
版本 | 产生时间 | 内容 |
---|---|---|
http0.9 | 1991年 | 不涉及数据包传输,规定客户端和服务器之间通信格式,只能GET请求(没有作为正式的标准) |
http1.0 | 1996年 | 传输格式不限制,增加了put、post、delete等等命令 |
http1.1 | 1997年 | 持久连接(长连接)、管道机制、节约带宽、HOST域 |
http2.0 | 2015年 | 多路复用、服务器推送、头信息压缩、二进制协议等等 |
- 问题1:为什么要有三次握手?
- 问题2:https与http的区别?https有内容加密、验证身份、保护数据完整性功能。
- 问题3:什么是多路复用?
- 问题4:为什么要有四次挥手?因为TCP是全双工模式。
1)请求(客户端->服务端[request])
GET(请求的方式) /newcoder/hello.html(请求的目标资源) HTTP/1.1(请求采用的协议和版本号)
Accept: /(客户端能接收的资源类型)
Accept-Language: en-us(客户端接收的语言类型)
Connection: Keep-Alive(维护客户端和服务端的连接关系)
Host: localhost:8080(连接的目标主机和端口号)
Referer: http://localhost/links.asp(告诉服务器我来自于哪里)
User-Agent: Mozilla/4.0(客户端版本号的名字)
Accept-Encoding: gzip, deflate(客户端能接收的压缩数据的类型)
If-Modified-Since: Tue, 11 Jul 2000 18:23:51 GMT(缓存时间)
Cookie(客户端暂存服务端的信息)
Date: Tue, 11 Jul 2000 18:23:51 GMT(客户端请求服务端的时间)
2)响应(服务端->客户端[response])
HTTP/1.1(响应采用的协议和版本号) 200(状态码) OK(描述信息)
Location: http://www.baidu.com(服务端需要客户端访问的页面路径)
Server:apache tomcat(服务端的Web服务端名)
Content-Encoding: gzip(服务端能够发送压缩编码类型)
Content-Length: 80(服务端发送的压缩数据的长度)
Content-Language: zh-cn(服务端发送的语言类型)
Content-Type: text/html; charset=GB2312(服务端发送的类型及采用的编码方式)
Last-Modified: Tue, 11 Jul 2000 18:23:51 GMT(服务端对该资源最后修改的时间)
Refresh: 1;url=http://www.it315.org(服务端要求客户端1秒钟后,刷新,然后访问指定的页面路径)
Content-Disposition: attachment; filename=aaa.zip(服务端要求客户端以下载文件的方式打开该文件)
Transfer-Encoding: chunked(分块传递数据到客户端)
Set-Cookie:SS=Q0=5Lb_nQ; path=/search(服务端发送到客户端的暂存数据)
Expires: -1//3种(服务端禁止客户端缓存页面数据)
Cache-Control: no-cache(服务端禁止客户端缓存页面数据)
Pragma: no-cache(服务端禁止客户端缓存页面数据)
Connection: close(1.0)/(1.1)Keep-Alive(维护客户端和服务端的连接关系)
Date: Tue, 11 Jul 2000 18:23:51 GMT(服务端响应客户端的时间)
在服务器响应客户端的时候,带上Access-Control-Allow-Origin头信息,解决跨域的一种方法。
11、new一个对象的过程都发生了什么?
- 创建一个新对象,比如:var obj = {};
- 将新对象的_proto_属性指向构造函数的原型对象;
- 将构造函数的作用域指向新对象,即将this指向obj;
- 执行构造函数内部的代码,将属性添加给this对象;
- 返回新对象obj;
12、类和构造函数的区别?如何编写代码实现构造函数不用new关键字会报错?
通常我们所谓的类就是用new来构造实例的普通函数。
通常实例化一个构造函数的时候,里面的this是指向被实例化的实例的。如下示例所示:
function Person(name, age){
this.name = name;
this.age = age;
}
但有的时候我们想直接执行函数来进行实例化,而不是用new。这个时候构造函数里面的this指向便不再是被实例化的实例了,就变成了Ins函数的接受者全局变量window,严格模式下则会直接抛出错误。
通常的解决方案是判断this是否是构造函数的实例,若是则继续执行,若不是则进行new的操作。代码如下:
function Person(name, age){
if(!this instanceof Person){
return new Person(name, age);
}
this.name = name;
this.age = age;
}
在构造函数参数个数固定的情况下这种方案是可行的。这种方式的一个缺点是它需要额外的函数调用,因此代价有点高。
在进行构造函数实例化的时候,如果构造函数内部没有明确return语句返回值,那么该构造函数会自动的构造一个实例对象返回,但是如果我们明确返回了非简单数据类型(简单数据类型还是会自动构造实例对象返回)的对象的话,构造函数实例化的时候返回的就是该对象。如此,我们可以构造如下的通用情况下的构造函数:
function Person(name, age){
let self = this instanceof Person ? this : Object.create(Person.prototype);
self.name = name;
self.age = age;
}
13、字符串相关问题
1、改写字符串形式,例如:“ILoveYou”改成“i_love_you”,注意正则表达式不要加引号。replace第二个参数可以是一个函数,对匹配到的词做更多处理。
function func(str){
return str.replace(/[A-Z]g/, function(word){return "_" + word.toLowerCase()})
}
2、将把 “Doe, John” 转换为 “John Doe” 的形式
function(str){
return str.replace(/(\w+)\s*, \s*(\w+)/, "$2 $1");
}
3、我们将把所有的花引号替换为直引号
function func(str){
return str.replace(/"([^"]*)"/g, "'$1'")
}
4、将一个数组随机打乱
function randArr(arr){
var len = arr.length;
for(var i=0; i< len; i++){
var j = parseInt(Math.random()*(len-1));
var tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
return arr;
}
14、说一说浏览器缓存
浏览器缓存主要分为:强缓存和协议缓存,强缓存主要涉及的响应头有expires、cach-control,前者是http1.0的产物,后者是http1.1中的,前者需要结合last-modified一起使用,两者同时存在时后者优先级高于前者;当日期过期后呢,即强缓存失效,此时进入协议缓存,浏览器发出的请求会携带if-modified-since头部,该头部的值来源于之前的last-modified,服务器接收到请求后,比对if-modified-since的值如果文件修改时间没变,说明缓存仍可以使用,则返回304,not-modified,表示浏览器可以继续使用之前的缓存,如果变化了则返回200和请求的文件。但是使用这种策略有两个弊端,一是当在本地打开缓存文件时last-modified会被修改,二是last-modified的最小单位是秒,可能会出现服务端刚修改了文件,然后接着就收到了请求,并且last-modified的时间与其相差在一秒以下,此时服务器仍会认为命中返回304,但其实文件已经被修改了。解决这个问题的方法是使用etag,它是http1.1的产物,它是由服务端产生的代表文件的唯一标识,可以认为是由文件内容产生的哈希值,所以当我们修改文件后产生的新的etag肯定会和之前的不一样,当浏览器对A文件再次发出请求时,会将etag值放到if-none-match请求头中,然后服务端会比对该值,如果与服务端相同,则返回304.etag的优先级高于if-modified-since。
15、说一说前端模块化开发
前端模块化开发可以总结为一个渐进的过程,由浅入深如下:
- 使用函数来定义一组语句;
- 将一组函数封装到一个对象中,通过对象调用(此种方法的缺点是通过对象能够访问到其中的对象变量,无法封装细节);
- 为了弥补上面的缺点,出现了立即执行函数,在立即执行函数中返回一个对象,对象中包含功能函数,通过闭包的方式解决上面的问题;
- 对于中大型项目上面的方式有很多缺点比如难以维护、不容易协同开发等,然后出现了一些规范:
- commonjs:主要在服务端使用,特点是同步加载因为代码就存在服务端所以同步加载没有问题,但是在浏览器端由于文件需要请求服务端,同步加载会造成堵塞,所以不适用于前端,webpack天生支持commonjs。
- AMD(Asynchromous Module Definition) 异步模块定义,适用于前端,异步加载,根据该规范实现的库是requireJs。
- CMD(Common Module Definition)通用模块定义,适用于前端,同步加载,根据该规范实现的库是seaJS。
- UMD(Universal Module Definition)是AMD和commonjs的综合产物,目的是为了解决跨平台问题,在前端使用异步加载,后端使用同步加载。
- ES6引入了内置的模块系统,import、export,使用需要通过babel转译。
16、cookie和session的区别
两者出现的原因是http是一个无状态协议,仅凭http无法识别用户,为了弥补这一缺点,所以提出了cookie和session,cookie存在于客户端,最初目的是用来识别用户,比如用户登录网站后一定时间内再次登录不用再次输入账号,它的存储容量有限制大概4M左右,不同浏览器有些许差别;session存在于服务端,也是为了存储一些信息,比如购物车,用户加入购物车后,在下次登录购物车中的东西还存在,这里使用的就是session。
17、异步编程的实现方式
- 回调函数,比如通过setTimeOut的方式将回调函数加入异步队列,实现异步编程;
- 事件监听;
- 发布\订阅模式
这个模式有多种实现,下面采用的是Ben Alman的Tiny Pub/Sub,这是jQuery的一个插件。首先,f2向"信号中心"jQuery订阅"done"信号。
jQuery.subscribe("done", f2);
然后,f1进行如下改写:
function f1(){
setTimeout(function () {
// f1的任务代码
jQuery.publish("done");
}, 1000);
}
jQuery.publish(“done”)的意思是,f1执行完成后,向"信号中心"jQuery发布"done"信号,从而引发f2的执行。
此外,f2完成执行后,也可以取消订阅(unsubscribe)。
jQuery.unsubscribe(“done”, f2);
这种方法的性质与"事件监听"类似,但是明显优于后者。因为我们可以通过查看"消息中心",了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。
- promise、async\await
17、数组去重
18、VUE
https://www.cnblogs.com/yszblog/p/10135969.html
1、说一下vue如何通信
(1)父组件向子组件通信,通过props。
父组件:
<template>
<div>
<son v-bind:msg="name" />
</div>
</template>
<script>
import Son from './Son';
export default {
name: "app",
data:{name : "Alex"},
components:{Son}
}
</script>
子组件:
<template>
<div>
{{name}}
</div>
</template>
<script>
export default {
name: "son",
props:[name]
}
</script>
(2)子组件向父组件通信通过
.
e
m
i
t
(
e
v
e
n
t
,
v
a
l
)
,
e
v
e
n
t
是
父
组
件
在
子
组
件
上
定
义
的
事
件
,
通
过
在
子
组
件
中
触
发
该
事
件
,
并
将
v
a
l
作
为
参
数
传
给
该
监
听
函
数
,
就
能
实
现
子
组
件
向
父
组
件
通
信
。
(
3
)
兄
弟
组
件
之
间
通
信
,
可
以
定
义
一
个
v
u
e
实
例
作
为
通
信
的
“
b
u
s
”
,
比
如
c
h
i
l
d
1
要
给
c
h
i
l
d
2
传
参
,
可
以
在
c
h
i
l
d
1
中
通
过
.emit(event, val), event是父组件在子组件上定义的事件,通过在子组件中触发该事件,并将val作为参数传给该监听函数,就能实现子组件向父组件通信。 (3)兄弟组件之间通信,可以定义一个vue实例作为通信的“bus”,比如child1要给child2传参,可以在child1中通过
.emit(event,val),event是父组件在子组件上定义的事件,通过在子组件中触发该事件,并将val作为参数传给该监听函数,就能实现子组件向父组件通信。(3)兄弟组件之间通信,可以定义一个vue实例作为通信的“bus”,比如child1要给child2传参,可以在child1中通过.emit触发“bus”的事件,然后在child2中通过$.on监听该事件,来达到通信的目的。
(4)通过provide / inject 方法,可以以一个祖先组件向所有子孙后代注入依赖(内容)。
(5)vuex,通过状态管理插件,实现状态共享,类似于定义一个全局变量。
2、说一下vue的虚拟dom
3、说一下vue的生命周期函数
4、说一下diff算法
5、vue的双向绑定实现原理
通过Object.defineProperty()劫持数据的getter和setter属性,然后结合发布者\订阅者模式来实现双向绑定。