这篇文章只是笔者平常学习时,记得一些零散的知识点,可以当作是做笔记吧。所以内容会有些杂乱,并且会持续更新
1: 函数的作用域是由函数创建时候的位置决定的
var a = 1;
function fn () {
console.log(a);
}
function fn2 () {
var a = 2;
fn();
}
fn2(); // 输出 1
2: 变量名只是对象存在栈中的一个指向堆中的内存地址
var obj = {
a: 1,
b: 2,
c: 3,
}
var obj1 = obj, obj2 = obj;
obj1.a = 4;
console.log(obj.a); // 4
console.log(obj1.a); // 4
console.log(obj2.a); // 4
// 我们可以操作栈中的数据,但不可以操作堆中的数据,堆中的数据由浏览器的垃圾回收机制来处理
obj = null; // 可以解除变量obj的指向,但不会影响obj1和obj2的指向
console.log(obj1.a); // 4
3: 闭包
定义:内部函数可以访问外部函数变量,被闭包引用的变量直到闭包被销毁时(闭包执行完毕),才会被垃圾回收机制销毁。
作用:因此我们可以通过使用闭包达到函数的封装性,使一个函数拥有私有变量、私有状态、不会污染全局变量。
体现:这一点在ajax请求,事件处理,插件(公共)方法封装上有很多体现。
缺陷:滥用闭包,会导致大量函数的私有变量不会被垃圾回收机制回收,从而导致内存漏(胡扯芝麻叶,当现在还是IE6呢???)。
举例:
(function autorun(){
let x = 1;
fetch("http://").then(function log(){
console.log(x);
});
})();
/**
变量 x 将一直存活到接收到后端返回结果,回调函数被执行。
*/
4: display:none 和 visibility:hiddlen
共同点:二者都会使元素不可见;
区 别:
a: display:none元素消失后,所占的空间(宽高)也会被消除,再次改为display:block时,会引起浏览器重排与重绘
b: visibility:hiddlen元素消失后,所占的空间(宽高)依然存在,再次改为visibility:visible时仅引起浏览器的重绘
5: script标签的 defer 与 async 属性
a:加了defer 属性的script标签会在浏览器解析完html之后才会执行,defer脚本是在load事件和DOMContentLoaded之前执行的,奇葩的是只有IE支持该属性。
ps: DOMContentLoaded事件是在html结构,样式,脚本执行完毕之后立即执行,load是在在html结构,样式,脚本执行完毕,DOMContentLoaded执行完毕,图片加载完成,页面加载完毕之后才执行。
b: 加了async属性的script标签是在脚本执行完毕之后执行,会在load事件之前,但是不保证在DOMContentLoaded事件之后
6:浏览器缓存
a: 强缓存
在浏览器请求头中设置了Expires或者Cache-Control,当请求有该标示的资源时,
状态码会返回200同时标注from disk cache
(从硬盘取的,速度慢,资源大)或from memory cache(从内存取的,速度块,资源小)。
Expires或者Cache-Control二者区别:
(1):首先二者区别不大,都是强缓存策略;
(2):Expires 是http1.0的产物,Cache-Control是http1.1的产物,
所以推荐使用Cache-Control
(3):和Expires配合的请求头字段只有max-age(最大缓存时间,受本地时间影响,
改变本地时间可能导致缓存过期),还需结合Last-modified(资源最后更新时间)使用
(4):和Cache-Control配合的字段有一大堆
public -- 客户端和代理服务器均可缓存
private -- 只有客户端可以缓存
max-age -- 最大缓存时间
no-store -- 不缓存任何响应
no-cache -- 资源缓存但立即失效下次请求会验证资源是否过期,过期重新请求,
不过期接着用
等等吧,懒得写了
b: 协商缓存
就是在强缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识
决定是否使用缓存的过程,
主要有以下两种情况:
(1):协商缓存失效,返回304和Not Modified
(2):协商缓存生效,返回200和请求结果
协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified (资源最后更新时间)
和 ETag (是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),
只要资源有变化,Etag就会重新生成)。
ETag优于Last-Modified,根据时间判断可能会有瑕疵(时间不准确,哪怕差几毫秒),
ETag是唯一标示绝对可靠,更便于服务器判断资源是否发生改变,要不要启用缓存。
7: 错误监控
a:: try { ... } catch(e){ .... };首先try catch 会捕获什么样的错误??
答:try catch会捕获进入语句中的错误,在try 之前和catch之后的错误,都不会被捕获
(ps: try catch不要瞎写,一定要有依据,不要想当然,你以为你是黄教主??)
b: window.onerror
window.onerror = function (message, url, line, column, error) {
console.log('log---onerror:',message, url, line, column, error);
}
window.onerror 只会捕获代码运行时错误,不会捕获资源加载时错误
c: 捕获资源加载时错误,怎么做??
window.addEventListener("error", function(e){
console.log('捕获了错误:', e)
}, true)
通过window.addEventListener监听error事件,来捕获资源加载时错误
8: call() ,apply(), bind()
call()和apply()都是用来改变this指向(借用其他对象的方法),所以第一个参数都是一个对象(this的指向,当第一个参数为null或underfind时,this指向window), 区别在于call()和apply()是返回一个方法且立即执行该方法,bind()是返回一个方法,但不会立即执行该方法,只有当该方法被调用时才会执行。
call和apply区别:
call(obj, a, b, c) (面试主要说用来继承其他对象的属性就OK了),参数用“,”隔开
function fn () {
this.a = 1;
this.print = function () {
console.log(this.a);
}
}
function fn2 () {
fn.call(this);
this.print();
}
fn2(); // 1
fn2继承了fn,但是这样继承不到fn原型对象上(prototype)的方法,想要继承原型对象上的方法:
fn2.prototype = Object.create(fn.prototype);
fn2.prototype.constructor = fn2;
apply(obj,[a, b ,c])借用其他对象的方法,参数必须是数组
apply()的妙用:
console.log(Math.max.apply(null, [1,2,3]));// 输出 3
借用数学对象的最大值方法,找出数组中的最大值
bind()的使用
function fn (a, b, c){
console.log(a+b+c);
}
var fn2 = fn.bind(null, 1,2,3); // fn2通过bind函数借用了fn方法,但是fn2此时并未被调用执行;
fn2(); // 通过使用圆括号()调用fn2方法,输出6;
原生js实现bind()
Function.prototype.mybind = function (context = window, ...Args) {
var fn = this;
return function (...args) {
return fn.call(context, ...Args, ...args)
}
}
// 验证
console.log(Math.max.bind(null, 1,2,3)()); // 3
console.log(Math.max.mybind(null, 1,2,3)()); // 3
9: 防抖和节流
共同点:二者都是利用setTimeout来控制程序在一定时间内只执行一次,防止重复触发。
区 别:防抖--事件n秒内重复触发的话,时间会重新计算(第二次调用时,就把第一次响应的给清除掉,响应第二次的,以此类推);节流--事件在n秒内不管怎么触发,都会只执行一次(只响应第一次的,后续的不管了)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>节流</title>
<link rel="stylesheet" href="">
</head>
<body>
<button onclick="fn()">点我</button>
<script type="text/javascript">
const clickTimer = [];
function clickThrottle(type = 'default', time = 3000) {
if (clickTimer[type]) {
console.log(`多次快速点击屏蔽:${type}`);
return false;
}
clickTimer[type] = setTimeout(() => {
clickTimer[type] = null;
}, time);
return true;
};
// 三秒内快速点击,只会输出一次1
function fn() {
if(!clickThrottle('click')) {
return;
}else {
console.log(1);
}
}
</script>
</body>
</html>
10: require 和 import的区别(common.js 和ECMA Script语法的区别)
共同点:两者都是用来引用模块的方法,两者都不会循环引用(都会缓存已引用的对象,再次引用时使用缓存)。
区 别:
(1)require是node.js遵从common.js规范得来的,import是ECMA Script的语法规范
(2)require引用的是模块的备份,在引用模块时会先将模块复制一份再引用备份的对象;import 不会备份,而是直接引入目标
(3)import写法更多样,import *as xx from '...' import {xx, xx, xx} from '...' import xx from '...' import xx, {xx} from '...' 写法比require更灵活。
(4)import 引用的值会做静态分析,a引用b,随后b改变了,再之后a会引用出b改变后的值;但是require引用不到改变后的b的值,原因就在于require引用的对象是复制的。
(5)使用TypeScript时,引用没有@types的js包时,会报错;原因在于ts会默认获得隐式的any对象不符合ts的语法规范,所以在引用没有@types的js包时,不想写xxx.d.ts的话)(xxx必须与目标文件同名),只能用require不能用import.
(6)require会在项目启动时一次性引用所有的对象,vue使用动态import结合promise配置路由时会按需加载,只有在使用到该对象时,才会引用它,所以import可以起到性能优化的作用。同时vue引入组件时,结合improt,可以实现组件的异步加载。
11:http和https以及http1.0,http1.1, http2.0超文本数据传输协议
(1)http--超文本数据传输协议,人如其名使用文本格式来进行数据传输
(2)https相比http就多了一个s--SSl加密证书,使文本数据在传输时以一种蒙面(加密)的姿态进行。服务器和客户端都掌握密钥,起到防止数据泄密的作用
(3)http1.0第一代协议没啥好说的,使用过气的Expires来进行强缓存策略。
(4)http1.1第一代的优化版本,使用Cache-Control等一系列配置优化了强缓存策略。同时设定请求头部必须包含host字段以区分同一个物理主机中的不同虚拟主机的域名;默认开启持久链接--在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟
(5)http2.0牛逼的第二代数据传输协议,可惜似乎现在应用不广,虽然出来好久了...相比于第一代它有很多优点:
a: 采用二进制(0和1)格式传输数据,方便且强大。完美的解决了文本传输数据满足场景
(对象,数组,表单,图片)时实现过于复杂的问题。
b: 多路复用,一个TCP链接可以并发多个请求,且请求相互独立。即便是http1.1的持久链接,请求也是一个接一个的发送。
c: 头部压缩,缓存不变的请求头数据,不用每次都将同样的请求头数据重复发送。
d: 服务端推送:把css、js、img资源和html一起发给客户端
12:浏览器的事件循环队列
(1)浏览器的事件循环队列理解:
是浏览器响应、处理事件的唯一方式。事件对列就好比一队人从1-10的排列表着,点名时排在前面的人总是比排在后面的先被点名。浏览器处理事件也是这样,从前往后的一个个处理队列里面的事件。
(2)队列中事件排序的优先级(那些事件排在前面,那些事件排在中间,那些事件排在最后):
a.首先排在前面的是主流程的代码,按照编写的先后顺序,一个接一个的处理。
b.其次排在中间的是微宏队列--promise,async-awit这些常用异步处理方法。
c.排在最后的是主宏队列---setTimeout,setInterval这些延时、轮询操作。
(3)事件就是按照上面顺序一件件排序处理的,所以当上一个事件处理时间较长,下一个事件等待时间相应就会很长,这是由于Js单线程的特性决定的。所以有时我们使用setTimeout设置某一事件event在3秒后执行,但如果前面事件处理的时间超过了3秒,event就不会在期待3秒后触发,而是等待前面所有的事件处理结束后再触发。
13:手动实现promise
promise核心三要素:
(1)status: pending, onfulfilled, onrejected
(2)resolve()和 reject()
(3)then() 实现一个自己的promise必须包含以上三要素哦。
<!DOCTYPE html>
<html>
<head></head>
<body></body>
<script>
var MyPromise = (function () {
function MyPromise(resolver) {
if (typeof resolver !== "function") {
//resolver必须是函数
throw new TypeError(
"MyPromise resolver " + resolver + " is not a function"
);
}
if (!(this instanceof MyPromise)) return new MyPromise(resolver);
var self = this; //保存this
self.callbacks = []; //保存onResolve和onReject函数集合
self.status = "pending"; //当前状态
function resolve(value) {
setTimeout(function () {
//异步调用
if (self.status !== "pending") {
return;
}
self.status = "resolved"; //修改状态
self.data = value;
for (var i = 0; i < self.callbacks.length; i++) {
self.callbacks[i].onResolved(value);
}
});
}
function reject(reason) {
setTimeout(function () {
//异步调用
if (self.status !== "pending") {
return;
}
self.status = "rejected"; //修改状态
self.data = reason;
for (var i = 0; i < self.callbacks.length; i++) {
self.callbacks[i].onRejected(reason);
}
});
}
try {
resolver(resolve, reject); //执行resolver函数
} catch (e) {
reject(e);
}
}
function resolvePromise(promise, x, resolve, reject) {
var then;
var thenCalledOrThrow = false;
if (promise === x) {
return reject(new TypeError("Chaining cycle detected for promise!"));
}
if (x !== null && (typeof x === "object" || typeof x === "function")) {
try {
then = x.then;
if (typeof then === "function") {
then.call(
x,
function rs(y) {
if (thenCalledOrThrow) return;
thenCalledOrThrow = true;
return resolvePromise(promise, y, resolve, reject);
},
function rj(r) {
if (thenCalledOrThrow) return;
thenCalledOrThrow = true;
return reject(r);
}
);
} else {
return resolve(x);
}
} catch (e) {
if (thenCalledOrThrow) return;
thenCalledOrThrow = true;
return reject(e);
}
} else {
return resolve(x);
}
}
MyPromise.prototype.then = function (onResolved, onRejected) {
//健壮性处理,处理点击穿透
onResolved =
typeof onResolved === "function"
? onResolved
: function (v) {
return v;
};
onRejected =
typeof onRejected === "function"
? onRejected
: function (r) {
throw r;
};
var self = this;
var promise2;
//promise状态为resolved
if (self.status === "resolved") {
return (promise2 = new MyPromise(function (resolve, reject) {
setTimeout(function () {
try {
//调用then方法的onResolved回调
var x = onResolved(self.data);
//根据x的值修改promise2的状态
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
//promise2状态变为rejected
return reject(e);
}
});
}));
}
//promise状态为rejected
if (self.status === "rejected") {
return (promise2 = new MyPromise(function (resolve, reject) {
setTimeout(function () {
try {
//调用then方法的onReject回调
var x = onRejected(self.data);
//根据x的值修改promise2的状态
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
//promise2状态变为rejected
return reject(e);
}
});
}));
}
//promise状态为pending
//需要等待promise的状态改变
if (self.status === "pending") {
return (promise2 = new MyPromise(function (resolve, reject) {
self.callbacks.push({
onResolved: function (value) {
try {
//调用then方法的onResolved回调
var x = onResolved(value);
//根据x的值修改promise2的状态
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
//promise2状态变为rejected
return reject(e);
}
},
onRejected: function (reason) {
try {
//调用then方法的onResolved回调
var x = onRejected(reason);
//根据x的值修改promise2的状态
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
//promise2状态变为rejected
return reject(e);
}
},
});
}));
}
};
//获取当前Promise传递的值
MyPromise.prototype.valueOf = function () {
return this.data;
};
//由then方法实现catch方法
MyPromise.prototype.catch = function (onRejected) {
return this.then(null, onRejected);
};
//finally方法
MyPromise.prototype.finally = function (fn) {
return this.then(
function (v) {
setTimeout(fn);
return v;
},
function (r) {
setTimeout(fn);
throw r;
}
);
};
MyPromise.prototype.spread = function (fn, onRejected) {
return this.then(function (values) {
return fn.apply(null, values);
}, onRejected);
};
MyPromise.prototype.inject = function (fn, onRejected) {
return this.then(function (v) {
return fn.apply(
null,
fn
.toString()
.match(/\((.*?)\)/)[1]
.split(",")
.map(function (key) {
return v[key];
})
);
}, onRejected);
};
MyPromise.prototype.delay = function (duration) {
return this.then(
function (value) {
return new MyPromise(function (resolve, reject) {
setTimeout(function () {
resolve(value);
}, duration);
});
},
function (reason) {
return new MyPromise(function (resolve, reject) {
setTimeout(function () {
reject(reason);
}, duration);
});
}
);
};
MyPromise.all = function (promises) {
return new MyPromise(function (resolve, reject) {
var resolvedCounter = 0;
var promiseNum = promises.length;
var resolvedValues = new Array(promiseNum);
for (var i = 0; i < promiseNum; i++) {
(function (i) {
MyPromise.resolve(promises[i]).then(
function (value) {
resolvedCounter++;
resolvedValues[i] = value;
if (resolvedCounter == promiseNum) {
return resolve(resolvedValues);
}
},
function (reason) {
return reject(reason);
}
);
})(i);
}
});
};
MyPromise.race = function (promises) {
return new MyPromise(function (resolve, reject) {
for (var i = 0; i < promises.length; i++) {
MyPromise.resolve(promises[i]).then(
function (value) {
return resolve(value);
},
function (reason) {
return reject(reason);
}
);
}
});
};
MyPromise.resolve = function (value) {
var promise = new MyPromise(function (resolve, reject) {
resolvePromise(promise, value, resolve, reject);
});
return promise;
};
MyPromise.reject = function (reason) {
return new MyPromise(function (resolve, reject) {
reject(reason);
});
};
MyPromise.fcall = function (fn) {
// 虽然fn可以接收到上一层then里传来的参数,但是其实是undefined,所以跟没有是一样的,因为resolve没参数啊
return MyPromise.resolve().then(fn);
};
MyPromise.done = MyPromise.stop = function () {
return new MyPromise(function () {});
};
MyPromise.deferred = MyPromise.defer = function () {
var dfd = {};
dfd.promise = new MyPromise(function (resolve, reject) {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
};
try {
// CommonJS compliance
module.exports = MyPromise;
} catch (e) {}
return MyPromise;
})();
// 验证输出:2,0,1
var mp = new MyPromise((rsv, rej)=>{
setTimeout(()=>{
console.log(1);
},10)
rsv();
});
mp.then(()=>{
console.log(2);
}).then(()=>{
console.log(0);
})
</script>
</html>
代码原文参考于Promise原理与实现和Promises/A+规范 膜拜上面大佬。
老实说,我感觉现场手撸一个完整的promise对于一般人来说还是有非常大的难度的。但是能写出一个基本的架构还是必须的:resolve和reject用来将状态由pending改为onfulfilled或者onrejected,then方法负责响应不同状态下的结果,最重要的resolvePromise(Promise 解决过程)将整个流程形成闭环。
// 示例:基本的promsie架构,没有promise的功能
function Promise(executor) {
var self = this;
self.status = 'pending'; //promise当前的状态
self.data = undefined; //promise的值
self.onResolvedCallback = [];
//promise状态变为resolve时的回调函数集,可能有多个
self.onRejectedCallback = [];
//promise状态变为reject时的回调函数集,可能有多个
function resolve(value) {
if(self.status === 'pending') {
self.status = 'resolved';
self.data = value;
for(var i = 0; i < self.onResolvedCallback.length; i++) {
self.onResolvedCallback[i](value);
}
}
}
function reject(reason) {
if(self.status === 'pending') {
self.status = 'rejected';
self.data = reason;
for(var i = 0; i < self.onRejectedCallback.length; i++) {
self.onRejectedCallback[i](reason);
}
}
}
try {
executor(resolve, reject);
} catch (e){
reject(e);
}
};
Promise.prototype.then = function (onResolve, onReject) {
this.onResolvedCallback.push(onResolve);
this.onRejectedCallback.push(onReject);
};