1.原型链
每个对象都会在其内部初始化一个属性,就是prototype(原型),
当我们访问一个对象的属性时,
如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype又会有自己的prototype, 于是就这样一直找下去,也就是我们平时所说的原型链的概念。

关系:instance.constructor.prototype = instance.-proto-

特点: JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。

2.闭包
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域,将函数内部的变量和方法传递到外部。
闭包的优点
- 创建一个匿名函数,并立即执行,加强了安全性和封装性,由于外部无法引用内部变量,不会造成全局污染。
- 缓存,可以将值存储起来,当需要调用的时候,首先在缓存中查找,如果找不 到再进行计算,然后更新缓存并返回值
- 实现封装和实现面向对象中的对象。
闭包的缺点
- 闭包会导致变量不能释放,引起内存泄露。
示例
function say667() {
// Local variable that ends up within closure
var num = 666;
var sayAlert = function() {
alert(num);
}
num++;
return sayAlert;
}
var sayAlert = say667();
sayAlert()//执行结果应该弹出的667
执行say667()后,say667()闭包内部变量会存在,而闭包内部函数的内部变量不会存在
使得Javascript的垃圾回收机制GC不会收回say667()所占用的资源
因为say667()的内部函数的执行需要依赖say667()中的变量
这是对闭包作用的非常直白的描述
3.作用域
变量的作用域无非就是两种:全局变量和局部变量。
全局作用域:
最外层函数定义的变量拥有全局作用域,即对任何内部函数来说,都是可以访问的:
var outerVar = "outer";
function fn(){
console.log(outerVar);
}
fn();//result:outer
局部作用域:
和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,而对于函数外部是无法访问的,最常见的例如函数内部
<script>
function fn(){
var innerVar = "inner";
}
fn();
console.log(innerVar);// ReferenceError: innerVar is not defined
</script>
作用域的特点
次序性和向上线程性搜索
4、Ajax请求(jQuery方式)
定义
无需重新加载整个网页的情况下,能够更新部分网页的技术
ajax的全称:Asynchronous Javascript And XML。(异步传输+js+xml)
请求方式
$.ajax()返回其创建的 XMLHttpRequest 对象。
$.ajax({
type: "get",
dataType: "application/json",
url: '/Ajax.json',
success: function (data) {
}
});
==在ajax请求中type的值分为(GET、POST、DELETE)最常见的==
GET:获取服务端数据,用get方式可传送简单数据,但大小一般限制在1KB下,数据追加到url中发送(http的header传送)。安全性能高。
POST:上传数据到服务端,数据上传无大小限制,但是需要设置content—type请求头;
DELETE:用于删除数据。
加载方式
同步:单线程运行,发送方发出数据后,等接收方发回响应以后才发下一个数据包的通讯方式。用户填写所有信息后,提交给服务器,等待服务器的回应(检验数据),是一次性的。信息错误又要重新填写!
客户端请求(等待)->服务端处理->响应->页面载入异步:发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式。
页面上的操作和服务器端的操作互相之间不会造成阻塞
(1)new一个XHR对象
(2)调用open方法
(3)send一些数据
(4)对过程进行监听,来知道服务器是不是正确地做出了响应,接着可以做一些事情
跨域解决方式
- jsonp、
- iframe、
- window.name、
- window.postMessage、
- 服务器上设置代理页面(CORS)
5. 数据拷贝的方法
1、数据相互赋值实现拷贝
let a=[0,1,2,3,4],
b=a;
console.log(a===b);
b[0]=1;
console.log(a,b);
在这里打印a和b的时候,我们明明b复制了a,但修改数组a,数组b也跟着变了。而当我们a[0]=1时进行数组修改时,由于a与b指向的是同一个地址,所以自然b也受了影响,这就是所谓的浅拷贝了。
浅拷贝出现地址引用的原因:
而对于对象这种内存占用比较大的来说,直接让复制的东西等于要复制的,那么就会发生引用,因为这种复制,只是将复制出来的东西的指向指向了要复制的那个东西,简单的说,就是两个都同时指向了一个空间,如果改变其中一个,另一个也会发生变化。这就发生了引用。
** 引用数据类型--名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值**

2.对象使用递归实现拷贝
function deepClone(obj){
let objClone = Array.isArray(obj)?[]:{};
if(obj && typeof obj==="object"){
for(key in obj){
if(obj.hasOwnProperty(key)){
//判断ojb子元素是否为对象,如果是,递归复制
if(obj[key]&&typeof obj[key] ==="object"){
objClone[key] = deepClone(obj[key]);
}else{
//如果不是,简单复制
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
let a=[1,2,3,4],
b=deepClone(a);
a[0]=2;
console.log(a,b);
递归递归去复制所有层级属性的拷贝方式,是开辟一个新的内存专门为b存放值,这样就实现了深拷贝。
在ES6中的,对于数组的拷贝复制又有新的方式
- Array.from(要复制的数组):
var arr1=[1,2,3];
var arr2=Array.from(arr1);
arr1.push(4);
alert(arr1); //1234
alert(arr2); //123
arr2.push(5);
alert(arr1); //1234
alert(arr2); //1235
- 对象转字符串再将其转回对象的方式
let a =[{a:1}]
let b = JSON.parse(JSON.stringify(a))
b[0].a=3
consloe.log(a[0].a) // 1
用json转才可以完成深赋值,因为字符串转成对象之后就是一个新的对象了;
- ...方式
var arr1=[1,2,3];
var arr2=[...arr1];
arr1.push(4);
alert(arr1); //1234
alert(arr2); //123
arr2.push(5);
alert(arr1); //1234
alert(arr2); //1235
6. 数组去重方式
1. 循环对比
双层循环,外层循环元素,内层循环时比较值,相同的值则跳过,不相同则push进数组
Array.prototype.distinct = function(){
var arr = this,
result = [],
i,
j,
len = arr.length;
for(i = 0; i < len; i++){
for(j = i + 1; j < len; j++){
if(arr[i] === arr[j]){
j = ++i;
}
}
result.push(arr[i]);
}
return result;
}
var arra = [1,2,3,4,4,1,1,2,1,1,1];
arra.distinct(); //返回[3,4,2,1]
2. 属性对比去重
利用对象的属性不能相同的特点进行去重
Array.prototype.distinct = function (){
var arr = this,
i,
obj = {},
result = [],
len = arr.length;
for(i = 0; i< arr.length; i++){
if(!obj[arr[i]]){ //如果能查找到,证明数组元素重复了
obj[arr[i]] = 1;
result.push(arr[i]);
}
}
return result;
};
var a = [1,2,3,4,5,6,5,3,2,4,56,4,1,2,1,1,1,1,1,1,];
var b = a.distinct();
console.log(b.toString()); //1,2,3,4,5,6,56
3. 利用ES6的set
Set数据结构,它类似于数组,其成员的值都是唯一的。
function dedupe(array){
return Array.from(new Set(array));
}
dedupe([1,1,2,3]) //[1,2,3]
7.call和apply的使用
call和apply,它们的作用都是将函数绑定到另外一个对象上去运行
/*apply()方法*/
function.apply(thisObj[, argArray]);
/*call()方法*/
function.call(thisObj[, arg1[, arg2[, [,...argN]]]]);
它们各自的定义:
apply:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.apply(A, arguments);即A对象应用B对象的方法。
call:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.call(A, args1,args2);即A对象调用B对象的方法。
它们的共同之处:
都“可以用来代替另一个对象调用一个方法,将一个函数的对象上下文从初始的上下文改变为由thisObj指定的新对象”。
它们的不同之处:
apply:最多只能有两个参数——新this对象和一个数组argArray。如果给该方法传递多个参数,则把参数都写进这个数组里面,当然,即使只有一个参数,也要写进数组里。如果argArray不是一个有效的数组或arguments对象,那么将导致一个TypeError。如果没有提供argArray和thisObj任何一个参数,那么Global对象将被用作thisObj,并且无法被传递任何参数。
call:它可以接受多个参数,第一个参数与apply一样,后面则是一串参数列表。这个方法主要用在js对象各方法相互调用的时候,使当前this实例指针保持一致,或者在特殊情况下需要改变this指针。如果没有提供thisObj参数,那么 Global 对象被用作thisObj。
7、[] == ![] 为什么是 true?
转化步骤:
1、!运算符优先级最高,![]会被转为为 false,因此表达式变成了:[] == false
2、根据上面第(4)条规则,如果有一方是 boolean,就把 boolean 转为 number,因此表达式变成了:[] == 0
3、根据上面第(5)条规则,把数组转为原始类型,调用数组的 toString()方法,[]转为空字符串,因此表达式变成了:'' == 0
4、根据上面第(3)条规则,两边数据类型为 string 和 number,把空字符串转为 0,因此表达式变成了:0 == 0
5、两边数据类型相同,0==0 为 true
8、JavaScript执行机制
首先我们需要深知JavaScript是一门单线程语言,一切的多线程都是由单线程模拟出来的,而在HTML5中提出了Web-Worker其实本质还是单线程
JavaScript事件循环
单线程的js,就像是单行道的车辆,必须一辆一辆的通过,如果有一辆车行走的很慢,那么后面的车辆就必须等待。这样在我们前端上就会出现页面卡住或者白屏情况。
为解决这样的问题我们采用了同步/异步任务。
简单的分类
同步任务:耗时少,界面骨架、界面元素渲染
异步任务:耗时久,多为资源占比大的数据和图片
任务执行机制
<img :src="$withBase('/loop.png')" alt="foo">
在JavaScript中比较典型的实例就是Ajax的执行,(Promise、setTimeout、setInterval)
let data = [];
$.ajax({
url:www.javascript.com,
data:data,
success:() => {
console.log('发送成功!');
}
})
console.log('代码执行结束');
打印结果
1、代码执行结束
2、发送成功
执行说明
1、ajax进入Event Table,注册回调函数success。
2、执行console.log('代码执行结束')。
3、ajax事件完成,回调函数success进入Event Queue。
4、主线程从Event Queue读取回调函数success并执行。
任务分类
在同步/异步任务中,我们还将任务更加细致的划分成
宏任务 :大的代码结构体,setTimeout、setInterval、ajax
微任务 :Promise
进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。
<img :src="$withBase('/loop2.png')" alt="foo">
总结
- 一般的JavaScript代码(同步)的属于宏任务,定时器相关的异步代码,包括setTimeOut、setInterval等也属于宏任务,promise、 process.nextTick属于微任务;
- 同步的代码会按照执行顺序顺序执行,遇到异步代码的时候,属于宏任务的放到宏队列,微任务放到微队列,其中promise需要resolve或者reject才会执行then或者catch里面的内容,其他的放到队列的属于回调函数的内容。
- 执行顺序是宏任务-微任务-宏任务……,因为整个脚本就是一个宏任务,所以当里面宏任务和微任务同时放入队列,会先执行玩微任务再执行宏任务;前提是代码执行完毕,如果存在嵌套关系,则会先执行完该任务再执行下一个任务,
9、HTML5 中 cookie,sessionStorage 和 localStorage
cookie 用来保存登录信息,大小限制为 4KB 左右
localStorage 是 Html5 新增的,用于本地数据存储,保存的数据没有过期时间,一般浏览器大小限制在 5MB
sessionStorage 接口方法和 localStorage 类似,但保存的数据的只会在当前会话中保存下来,页面关闭后会被清空
<img :src="$withBase('/cooke.png')" alt="foo">
至Chrome 80版本升级后,Chrome 80开始执行新的Cookie策略,对未设置“SameSite”的Cookie默认其值为Lax,即三方网站如果使用了该资源,在请求中是不会带上相关的Cookie的;对SameSite设置为None的Cookie,要求必须同时设置Secure,否则拒绝此Cookie。
名词解读
cookie的作用
用于解决客户端与服务端会话状态的问题,这个状态是指后端服务的状态而非通讯协议的状态。
cookie中的sameSite属性
作用:SameSite 属性可以让 Cookie 在跨站请求时不会被发送,从而可以阻止跨站请求伪造攻击(CSRF)。
属性值:
1、Strict 仅允许一方请求携带 Cookie,即浏览器将只发送相同站点请求的 Cookie,即当前网页 URL 与请求目标 URL 完全一致。
2、Lax 允许部分第三方请求携带 Cookie
3、None 无论是否跨站都会发送 Cookie
Chrome80 之前默认是 None 的,Chrome80 后默认是 Lax。
解决方案
1、在请求中设置 SameSite 为 none。
2、采用同域,避开浏览器同源策略(SOP),(1、资源处于同一台服务器,2、采用nginx代理处理成同域)
10、进程和线程的理解?
进程:cpu 的资源分配的最小单位。
多进程:多进程指的是在同一个时间里,同一个计算机系统中如果允许两个或两个以上的进程处于运行状态。
线程:
一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号)。
在浏览器的渲染进程中,主要存在一下几大线程。
1、GUI 线程(负责渲染浏览器界面 HTML 元素)
2、Javascript 引擎线程(负责处理 Javascript 脚本程序,与 GUI 线程互斥,当 JavaScript 引擎执行时 GUI 线程会被挂起,GUI 更新会被保存在一个队列中等到引擎线程空闲时立即被执行。)
3、事件触发线程(状态变更时,放入 JavaScript 引擎的处理队列中等待处理。)
4、定时器线程(浏览器定时计数器并不是由 JavaScript 引擎计数的, 因为 JavaScript 引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确, 因此通过单独线程来计时并触发定时是更为合理的方案。)
5、网络请求线程(状态变更时,放入 JavaScript 引擎的处理队列中等待处理。)
11、页面从输入 URL 到页面加载显示完成的具体流程
1、浏览器根据请求的 URL 交给 DNS 域名解析,找到真实 IP,向服务器发起请求(TCP 三次握手四次挥手);
2、服务器交给后台处理完成后返回数据,浏览器接收文件(HTML、JS、CSS、图象等);
3、浏览器对加载到的资源(HTML、JS、CSS 等)进行语法解析,建立相应的内部数据结构(如 HTML 的 DOM);
4、载入解析到的资源文件,渲染页面,完成。
具体流程分析:
1、DNS域名解析:
(1、)解析查找浏览器DNS缓存,如果未查到执行第二步;
(2、)查找系统中hosts文件中的DNS缓存,如果未查到执行第三步;
(3、)查找本地DNS中的缓存是否存在记录,如果无执行第四步;
(4、)本地DNS向远程域服务器发起请求,获取到对应的域名和IP,返回给本地并保存对应关系,以便下次查询
2、三次握手
(1、)第一次握手:客户端将标志位SYN置为1,随机产生一个值为seq=J(J的取值范围为=1234567)的数据包到服务器,客户端进入SYN_SENT状态,等待服务端确认;
(2、)第二次握手:服务端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务端将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给客户端以确认连接请求,服务端进入SYN_RCVD状态。
(3、)第三次握手:客户端收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给服务端,服务端检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,完成三次握手,随后客户端A与服务端B之间可以开始传输数据了。
3、HTTP请求
建立了TCP连接之后,发起一个http请求;服务器接受并处理完请求,返回 HTTP 响应
4、浏览器解析渲染页面
5、四次挥手
第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
为什么需要三次握手:
三次握手的目的是“为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。
12、[] == ![] 为什么是 true?
转化步骤:
1、!运算符优先级最高,![]会被转为为 false,因此表达式变成了:[] == false
2、根据上面第(4)条规则,如果有一方是 boolean,就把 boolean 转为 number,因此表达式变成了:[] == 0
3、根据上面第(5)条规则,把数组转为原始类型,调用数组的 toString()方法,[]转为空字符串,因此表达式变成了:'' == 0
4、根据上面第(3)条规则,两边数据类型为 string 和 number,把空字符串转为 0,因此表达式变成了:0 == 0
5、两边数据类型相同,0==0 为 true