目录
js事件循环机制(event loop)
同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。
当指定的事情完成时,Event Table会将回调函数注册到Event Queue。
主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
上述过程会不断重复,也就是常说的Event Loop(事件循环)。
例如:
console.log(1);
setTimeout(()=>{
console.log(3);
},100)
console.log(2);
js自上而下执行,先执行console.log(1);输出1,然后执行定时器setTimeout,由于setTimeout是异步的,所以先将setTimeout存放在Event Table,再然后执行console.log(2);输出2,当代码执行完成后会去检测Event Queue,如果有则执行,没有即等待,当100ms过去后setTimeout将进入Event Queue,再然后主线程没有任务执行时将setTimeout取出推入主线程开始执行setTimeout
微任务与宏任务
宏任务(macrotask )和微任务(microtask ) 表示异步任务的两种分类。常见宏任务:I/O 、setTimeout、setInterval、setImmediate、requestAnimationFrame;微任务:Promise.then catch finally、process.nextTick
例如
setTimeout(()=>{
console.log(4)
})
new Promise((res,rej)=>{
console.log(1)
res()
})
.then(()=>{
console.log(3)
})
console.log(2)
代码执行先遇到setTimeout,推入Event Table,然后遇到Promise,执行Promise内部代码输出1,再执行res,遇到Promise.then推入Event Table,然后执行输出2,再然后由于Promise.then是微任务,setTimeout是宏任务,所以先将Promise.then推入Event Queue,再推setTimeout,则主线程先执行Promise.then再执行setTimeout;
每次宏任务执行完后都会清空队列里的微任务,微任务执行优先级高于宏任务。
常见的状态码
200,
201表示请求成功且服务器创建了新的资源
202表示服务器已经接受了请求,但还未处理
301,302重定向
304表示自从上一次请求以来,页面的内容没有改变过
404,
500服务器错误
503表示服务器暂时无法处理请求
普通函数可以new吗
可以,但是没有什么意义。(返回一个空的this有何用?)
例如
function a(){
}
console.log(new a());//a{}
addEventListener中第三个参数是干嘛的
控制函数是在捕获阶段执行还是在冒泡阶段执行,默认冒泡阶段false
js垃圾回收机制
当一个变量的生命周期结束之后它所指向的内存就应该被释放。JS有两种变量,全局变量和在函数中产生的局部变量。局部变量的生命周期在函数执行过后就结束了,此时便可将它引用的内存释放(即垃圾回收),但全局变量生命周期会持续到浏览器关闭页面。
回收有两种方式:标记清除、引用计数。
标记清除:大部分浏览器以此方式进行垃圾回收,当变量进入执行环境(函数中声明变量)的时候,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”,在离开环境之后还有的变量则是需要被删除的变量。
引用计数:机制就是跟踪一个值的引用次数,当声明一个变量并将一个引用类型赋值给该变量时该值引用次数加1,当这个变量指向其他一个时该值的引用次数便减一。当该值引用次数为0时就会被回收。
什么是闭包以及应用场景
函数嵌套函数,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。例如:
function addOne(){
var a = 10;
function fn(){
a++;
console.log(a);
}
return fn;
}
应用场景
如下图,每次切换选项,都会发出请求获取当前选项对应的数据,当反复切换时数据重复请求这样是不是就有点浪费呢?那么我只需要在首次请求时把数据存下来,下次再切换回来时使用存下的数据就可以了吧?
思路没有问题,但若用户切换过快时,先点击下标1再点击下标2,当下标1数据返回慢时,那数据1就会存储到下标2的位置,此时就会出问题啦,这时候运用闭包就可以完美解决问题啦。
解决闭包造成的内存泄漏
主动设置为空,打破循环引用
js函数作用域链
js函数作用域链是从定义处开始逐层向外查找,而非被调用处
call、apply和bind的区别
bind不会立即执行,需手动调用; apply, call则是立即调用。
例如
var obj = {
name : 'wang'
}
function start(){
console.log(this.name);
}
start.bind(obj)()
start.call(obj)
start.apply(obj)
apply, call的区别
Object.call(this,obj1,obj2,obj3)
Object.apply(this,arguments)
手动实现call、apply和bind
function start(sex,age){
this.sex = sex;
this.age = age;
return this;
}
obj = {
name : '风舞红枫'
}
mybind
Function.prototype.mybind = function(context) {
if (typeof this !== 'function') {
return new Error("不是一个函数");
}
console.log(context,arguments)
var arg = [...arguments].slice(1);
var that = this;
return function(){
return that.apply(context,arg);
}
}
var result = start.mybind(obj,'男','16')();
console.log(result);
mycall
Function.prototype.mycall = function(context) {
if (typeof this !== 'function') {
return new Error("不是一个函数");
}
console.log(context,this)
var arg = [...arguments].slice(1);
context.fn = this;
var result = context.fn(...arg);
delete context.fn;
return result;
}
var result = start.mycall(obj,'男','16');
console.log(result);
myapply
Function.prototype.myapply = function(context) {
if (typeof this !== 'function') {
return new Error("不是一个函数");
}
var arg = arguments[1];
context.fn = this;
if (arg) {
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
delete context.fn;
return result;
}
var result = start.myapply(obj,['男','16']);
console.log(result);
this指向
先记以下两条,再往下看
1、this指向直接调用者;
2、找不到调用者时即为window;
例1:
function fun(){
console.log(this.num);
}
var obj = {
num:'1',
f:fun
}
var num = 2;
obj.f(); //1 此处是obj调用了函数fun,所以fun函数内this指向obj,故而输出1
fun(); //2 此处无明显调用者,顾指向window,所以输出2
例2(同理):
var A = {
name: '张三',
f: function () {
console.log(this.name);
}
};
var B = {
name: '李四'
};
B.f = A.f;
B.f() //李四
A.f() //张三
例3:
function foo() {
console.log(this.a);
}
var obj2 = {
a: 2,
fn: foo
};
var obj1 = {
a: 1,
o1: obj2
};
obj1.o1.fn(); //2 此处fn的直接调用者是o1,即obj2;
例4:
function C(){
this.a = 37;
}
var o = new C();
console.log(o.a); //37 构造函数无return时默认返回this
function C2(){
this.a = 37;
return {a:38};
}
o = new C2();
console.log(o.a); //38 手动设置了返回对象
例5:
var a = 1;
setTimeout(function(){
console.log(this.a) //1 定时器中的this指向window
},100)
例6:
var a = 1;
function(){
console.log(this.a) //1 匿名函数的this指向window
}()
让匿名函数中的this指向对象的两种方法
可以使用对象冒充强制改变this的指向
将this赋值给一个变量,闭包访问这个变量
call、applay、bind不做例子
箭头函数的this指向为其父级/宿主
原型与原型链
原型
1、原型就是一个属性,这个属性是构造函数的属性,构造函数是用来制造对象的,是构造函数制造出来的公共祖先,后面所有的对象都会继承原型的属性与方法(原型也是个对象)
2、__proto__这个是用来查看原型的,这个是对象的属性,这个属性可以查看但是不能修改(隐式属性)
3、prototype 设置原型,这个是构造函数的属性
原型链
由于__proto__是任何对象都有的属性,而js里万物皆对象,所以会形成一条__proto__连起来的链条 ,递归访问__proto__必须最终到头,并且值是null。
例如
Object.prototype.toString=function(){
console.log('找到我没有呀?');
}
var func = function(){};
var obj = new func();
console.log(obj.__proto__ === func.prototype) //true
console.log(func.prototype.__proto__ === Object.prototype) //true
// console.log(func.__proto__ === Function.prototype) //true
// console.log(Function.prototype.__proto__ === Object.prototype) //true
console.log(Object.prototype.__proto__ === null)
obj.toString();//此时先在obj中找toString,没有找到后沿着obj.__proto__到了func.prototype。发现func.prototype也没有,再然后沿着func.prototype.__proto__到了Object.prototype,最后找到了toString
console.log(obj.constructor === func) //true
console.log(func.constructor.prototype.__proto__ === Object.prototype) //true
console.log(func.constructor === Function) //true
console.log(func.constructor.__proto__ === Function.prototype) //true
console.log(Function.constructor.prototype.__proto__ === Object.prototype) //true
console.log(Object.constructor.prototype.__proto__ === Object.prototype) //true
继承
原型链继承
即在prototype上new一个你要继承的那个对象(如:Run.prototype=new Show();会覆盖掉原本的prototype)
function Person(){
this.name="wang";
}
function Boy(){
this.age="20";
}
Boy.prototype=new Person();//Boy继承了Person,通过原型,形成链条
var boy=new Boy();
console.log(boy.name)//wang
构造函数继承(对象冒充继承)
为了解决引用共享和超类型无法传参的问题,我们采用一种叫借用构造函数的技术,或者成为对象冒充(伪造对象、经典继承)的技术来解决这两种问题
function Person(age){
this.age=age;
}
function Boy(age){
Person.call(this,age); //对象冒充,给超类型传参
}
var boy = new Boy(22);
console.log(boy.age);//22
组合继承(原型链继承+构造函数继承)
借用构造函数虽然解决了刚才两种问题, 但没有原型, 复用则无从谈起。 所以,我们需要原型链+借用构造函数的模式,这种模式成为组合继承。
function Person(age) {
this.age = age;
}
Person.prototype.say = function () {
return '我今年' + this.age + '岁';
};
function Boy(age) {
Person.call(this, age); //对象冒充
}
Boy.prototype = new Person(); //原型链继承
var boy = new Boy(100);
console.log(boy.say());
原型式继承
function obj(data) { //传递一个字面量函数
function F() {} //创建一个构造函数
F.prototype = data; //把字面量函数赋值给构造函数的原型
return new F(); //最终返回出实例化的构造函数
}
var data = { //字面量对象
type : '年龄',
arr : [21,22,23]
};
var data1 = obj(data); //传递
console.log(data1.type); //'年龄'
console.log(data1.arr); //[21,22,23]
data1.type = '年龄呀';
data1.arr.push(24);
console.log(data1.type); //'年龄呀'
console.log(data1.arr); //[21,22,23,24]
var data2 = obj(data); //传递
console.log(data2.type); //'年龄'
console.log(data2.arr); //[21,22,23,24] 引用类型共享了
get与post的区别
其实没有本质的区别,只是习惯get的请求参数放到url中,post的请求参数放到body中。
get请求将参数放在body中也不会报错,post同理,一切只源于潮流…
常用的方法
字符串常用方法
toLowerCase(): 把字符串转为小写,返回新的字符串。
toUpperCase(): 把字符串转为大写,返回新的字符串。
indexOf(): 返回某个指定的子字符串在字符串中第一次出现的位置
lastIndexOf(): 返回某个指定的子字符串在字符串中最后出现的位置。
slice(): 返回字符串中提取的子字符串。
substring(): 提取字符串中介于两个指定下标之间的字符。
substr(): 返回从指定下标开始指定长度的的子字符串。
split(): 把字符串分割成字符串数组。
replace(): 在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。
数组常用方法
push(n): 从数组的尾部添加一个元素n,返回这个添加的元素n
pop(): 从数组的尾部删除一个元素,返回这个删除的元素,不接收参数
unshift(n): 从数组的头部添加一个元素n,返回这个添加的元素n,原数组发生改变
shift(): 从数组的头部删除一个元素,返回这个删除的元素,不接收参数
slice(): 返回数组中提取的子数组。
splice(i,n,m,m,m,…): 从数组下标i开始,删除项目n个,然后添加项目m。
reverse(): 数组翻转,该方法会改变原来的数组,而不会创建新的数组
sort(): 无参数默认从小到大排序,判断方式:按位判断
concat(): 数组拼接
join(‘-’): 将数组转化为为字符串,以括号内的拼接
两=与三=的不同
两=直接判断值是否相等
三=会判断类型是否相同
判断类型的方法有哪些
//typeof
typeof ""; //string
typeof 1; //number
typeof false; //boolean
typeof undefined; //undefined
typeof function(){}; //function
typeof []; //object
typeof {}; //object
typeof Symbol(); //symbol
typeof null; //object
typeof new Date(); //object
typeof new RegExp(); //object
//instanceof
{} instanceof Object; //true
[] instanceof Array; //true
[] instanceof Object; //true
"123" instanceof String; //false
new String(123) instanceof String; //true
//constructor
"".constructor //ƒ String() { [native code] }
"".constructor == String //true
1.constructor //报错 Invalid or unexpected token
new Number(1).constructor //ƒ Function() { [native code] }
new Number(1).constructor == Number //false
new Date().constructor //ƒ Date() { [native code] }
new RegExp().constructor //ƒ RegExp() { [native code] }
//Object.prototype.toString()
Object.prototype.toString.call(""); //[object String]
Object.prototype.toString.call(1); //[object Number]
Object.prototype.toString.call(false); //[object Boolean]
Object.prototype.toString.call(undefined); //[object Undefined]
Object.prototype.toString.call(function(){}); //[object Function]
Object.prototype.toString.call([]); //[object Array]
Object.prototype.toString.call({}); //[object Object]
Object.prototype.toString.call(Symbol()); //[object Symbol]
Object.prototype.toString.call(null); //[object Null]
Object.prototype.toString.call(new Date()); //[object Date]
Object.prototype.toString.call(new RegExp()); //[object RegExp]
封装返回数据类型的方法:
function dataType(obj){
//return Object.prototype.toString.call(obj).replace(/(\[object )|\]/g,"")
return Object.prototype.toString.call(obj).replace(/(\[object (\w+)\]/,"$1")
}
null和undefined的区别
null是一个表示"无"的对象,转为数值时为0;undefined是一个表示"无"的原始值,转为数值时为NaN。当声明的变量还未被初始化时,变量的默认值为undefined。 null用来表示尚未存在的对象
undefined表示"缺少值",就是此处应该有一个值,但是还没有定义。典型用法是:
(1)变量被声明了,但没有赋值时,就等于undefined。
(2)调用函数时,应该提供的参数没有提供,该参数等于undefined。
(3)对象没有赋值的属性,该属性的值为undefined。
(4)函数没有返回值时,默认返回undefined。
null表示"没有对象",即该处不应该有值。典型用法是:
(1)作为函数的参数,表示该函数的参数不是对象。
(2) 作为对象原型链的终点。
new操作符具体干了什么
1、创建一个空的对象
var obj=new Object();
2、让空对象的原型属性指向原型链,设置原型链
obj.__proto___=Func.prototype;
3、让构造函数的this指向obj,并执行函数体
var result=Func.call(obj);
4、判断返回类型,如果是值就返回这个obj,如果是引用类型,返回这个引用对象。
if (typeof(result) == "object"){
func=result;
}
else{
func=obj;
}
ajax原理
1、创建对象
var xhr = new XMLHttpRequest();
2、打开请求
xhr.open(‘GET’, ‘example.txt’, true);
3、发送请求
xhr.send(); 发送请求到服务器
4、接收响应
xhr.onreadystatechange =function(){}
(1)当readystate值从一个值变为另一个值时,都会触发readystatechange事件。
(2)当readystate==4时,表示已经接收到全部响应数据。
(3)当status ==200时,表示服务器成功返回页面和数据。
(4)如果(2)和(3)内容同时满足,则可以通过xhr.responseText,获得服务器返回的内容。
从输入URL到页面加载发生了什么
1、DNS解析
将域名转化为IP地址的工作。
如果浏览器有缓存,直接使用浏览器缓存,否则使用本地缓存,再没有就使用host;如果本地没有,就向dns域名服务器查询到对应的IP
由于dns的解析很耗时,当解析域名过多的时候,便会导致首屏加载变得过慢。
2、TCP连接
建立连接的三次握手
客户端:hello,你是服务端么?
服务端:hello,我是服务端,你是客户端么?
客户端:yes,我是客户端。
3、发送HTTP请求
4、服务器处理请求并返回HTTP报文
5、浏览器解析渲染页面
(1)处理HTML标记,构建DOM树;
(2)处理CSS标记,构建CSSOM树;
默认情况下,CSS是阻塞加载的,但它并不会阻塞DOM树的解析,它阻塞的是DOM的渲染,直至CSSOM树构建完成。因而为了避免看到长时间的白屏,我们尽可能早地提前加载CSS文件
(3)将DOM树和CSSOM树合并成渲染树;
(4)布局渲染树,计算各元素的尺寸、位置;
(5)绘制渲染树,绘制页面像素信息。
6、连接结束
断开连接的四次挥手
主动方:我已经关闭了向你那边的主动通道了
被动方:收到通道关闭的信息
被动方:那我也告诉你,我这边向你的主动通道也关闭了
主动方:最后收到数据,之后双方无法通信
offsetX, clientX, pageX, screenX, layerX, x
offsetX:点击位置距离当前元素左边的距离(padding处算起始点)
clientX:点击位置距离浏览器窗口左边的距离
pageX:点击位置距离文档左边的距离
screenX:点击位置距离屏幕左边的距离
layerX:在点击元素设置相对定位或绝对定位时layerX == offsetX,(layerX是从border处算起始点)
x:xclientX(其他),xlayerX(IE)
防抖和节流
1.防抖(debounce):触发高频事件 n 秒后函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间
举例:就好像在百度搜索时,每次输入之后都有联想词弹出,这个控制联想词的方法就不可能是输入框内容一改变就触发的,他一定是当你结束输入一段时间之后才会触发。
2.节流(thorttle):高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率。也可以是轮询的稀释,不需要在请求成功后立马再次发出请求。
举例:就比如抢购时,明明显示的是有产品,购买时却一直提示库存不足,过一会眼睁睁的看着它数值变成了0 。
区别:防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。
防抖示例代码
<template>
<input type="text" @input="change">
</template>
<script>
export default {
methods: {
change(){
//高频事件函数
if(this.sleeptTime){
clearTimeout(this.sleeptTime)
this.sleeptTime = null;
}
this.sleeptTime = setTimeout(()=>{
this.getList()
},1000)
},
getList(){
console.log('停止输入的1s后进入')
//模拟api请求
return new Promise((res,rej) => {
setTimeout(()=>{
res()
},500)
})
.then(res => {
console.log('请求成功')
})
}
}
}
</script>
节流示例代码
getList(){
//模拟api请求
new Promise((res,rej) => {
setTimeout(()=>{
res()
},500)
})
.then(res => {
console.log('请求成功')
//请求成功后间隔1s后重新发起请求
setTimeout(()=>{
this.getList();
},1000)
})
}
常见的pc端浏览器兼容
1.事件对象创建的兼容:
var e=e || event
2.鼠标事件对象的属性
Button属性 左键为0 右键为2 滚轮为1;
IE 左键为1 右键为2 滚轮为4;
3.键盘事件对对象的属性
Keycode 获取键盘的按键值 值为AscII码值
兼容:e.keycode||e.which;
4.阻止事件冒泡兼容
e.stopPropagation?e.stopPropagation:cancleBubble=true;
5. 事件监听
绑定事件监听
addEventListener(); ie:attachEvent()
取消事件监听
removeEventListener(); ie:detachEvent()
6. 阻止浏览器的默认行为:
兼容写法 e.preventDefault?e.preventDefault():e.returnValue=false;
万能:return false(写在最后面);
7. 事件的委托
var e.target(其他)||e.srcElement(IE);
8. 获取页面滚走的距离
document.documentElement.scrollTop||document.body.ScrollTop;
9. 拖拽里面获取鼠标距离按下元素的内部偏移量:
var disx= e.offsetX||e.layerX;
10.获取某元素的非行内样式:
window.getComputedStyle?window.getComputedStyle(对象,false)[“属性”]:对象.currentStyle[“属性”]
11.获取可见区域宽高
在IE中:
document.body.clientWidth ==> BODY对象宽度
document.body.clientHeight ==> BODY对象高度
document.documentElement.clientWidth ==> 可见区域宽度
document.documentElement.clientHeight ==> 可见区域高度
document.documentElement.scrollTop =>窗口滚动条滚动高度
在FireFox中:
document.body.clientWidth ==> BODY对象宽度
document.body.clientHeight ==> BODY对象高度
document.documentElement.clientWidth ==> 可见区域宽度
document.documentElement.clientHeight ==> 可见区域高度
document.documentElement.scrollTop =>窗口滚动条滚动高度
在chrome中:
document.body.clientWidth ==> BODY对象宽度
document.body.clientHeight ==> BODY对象高度
document.documentElement.clientWidth ==> 可见区域宽度
document.documentElement.clientHeight ==> 可见区域高度
document.body.scrollTop =>窗口滚动条滚动高度
在Opera中:
document.body.clientWidth ==> 可见区域宽度
document.body.clientHeight ==> 可见区域高度
document.documentElement.clientWidth ==> 页面对象宽度(即BODY对象宽度加上Margin宽)
document.documentElement.clientHeight ==> 页面对象高度(即BODY对象高度加上Margin高
滚动到顶部 window.scrollTo(0,0)
滚动到尾部 window.scrollTo(0,document.body.clientHeight)
代码练习题
1.变量提升面试题
var a = 100;
function test(){
console.log(a);
a = 10; //去掉了var就变成定义了全局变量了
console.log(a);
}
test();
console.log(a);
2.变量提升面试题
var a = 100;
function test(){
console.log(a);
var a = 10;
console.log(a);
}
test();
console.log(a);
3.this指向面试题
var x = 3;
var y = 4;
var obj = {
x: 1,
y: 6,
getX: function() {
var x =5;
return function() {
return this.x;
}();
},
getY: function() {
var y =7;
return this.y;
}
}
console.log(obj.getX())
console.log(obj.getY())
4.this指向面试题
var obj = {
a: 1,
b: function(){
return function(){
console.log(this) //window
}
}
}
var objf = obj.b();
objf();
var obj2 = {
a: 1,
b: function(){
return ()=>{
console.log(this) //obj2
}
}
}
var objf2 = obj2.b();
objf2();
5.this指向面试题
function fun(){
getName = function(){console.log(1)};
return this
}
fun.getName = function(){console.log(2)};
fun.prototype.getName = function(){console.log(3)};
var getName = function(){console.log(4)};
function getName(){
console.log(5);
}
getName();
fun.getName();
fun().getName();
getName();
new fun().getName();
6.eventloop面试题
console.log(1)
new Promise((res) => {
res();
console.log(2);
}).then(() => {
console.log(3)
}).then(() => {
console.log(4)
})
setTimeout(() => {
console.log(5)
},0)
console.log(6)