前端冷知识

343 阅读14分钟

这篇文章只是笔者平常学习时,记得一些零散的知识点,可以当作是做笔记吧。所以内容会有些杂乱,并且会持续更新

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);
};