JS执行机制

浏览器常驻的线程

js引擎线程(解释执行js代码、用户输入、网络请求)

GUI线程(绘制用户界面、与js主线程是互斥的)

http网络请求线程(处理用户的get、post等请求,并返回结果后将回调函数推入任务队列)

定时触发器线程(setTimeout、setInterval等待结束后把执行函数推入任务队列中)

浏览器事件处理线程(将click、mouse等交互事件发生后这些事件放入事件队列中)

JS引擎线程和GUI线程是互斥的:JS可以操作DOM元素,进而会影响到GUI的渲染结果,因此JS引擎线程与GUI渲染线程是互斥的。也就是说当JS引擎线程处于运行状态时,GUI渲染线程将处于冻结状态。

JS执行机制-单线程:同一时间只能做一件事情

大量数据操作怎么办?

单线程计算能力有限,大量数据需要计算渲染的话,我们可以配合后端进行操作,比如我们后期进阶班里讲到的VUE与nodejs配合,也就是传说中的SSR技术

JS执行机制

Javascript是基于单线程运行的,同时又是可以异步执行的,一般来说这种既是单线程又是异步的语言都是基于事件来驱动的,恰好浏览器就给Javascript提供了这么一个环境

DOM事件、ajax请求、setTimeout均属于异步任务,Promise的后续操作也属于异步,但存放在微队列

bind的模拟与实现

call和apply实现原理

call和apply的实现原理大致相似,可以分为三个步骤

  • 将调用的函数添加为目标this的属性
  • 以obj.fn()的方式执行该函数
  • 删除目标对象上的该属性,即所使用的函数

需注意一下几点,call的传参是通过传参列表实现的,而apply则为传入一个数组,当call和apply的参数传入为null,则this指向window

  1. Function.prototype.myCall = function(context){
  2. context = context || window;
  3. context.fn = this;
  4. let args = [];
  5. for(let i = 0; i < arguments.length; i++){
  6. args.push("arguments[" + i + "]");
  7. }
  8. var result = eval('context.fn(' + args +')');
  9. delete context.fn
  10. return result;
  11. }

使用

Func.bind(arg1, arg2, arg3……)

bind可以返回一个新的函数,this指向传入的第一个参数,后续参数可作为函数的实例参数

  1. 第一个参数为null时,this指向window
  2. 第一个参 数指向obj时,this指向obj
  3. new newShow(),this指向所创建的对象

如:

  1. var value = 0;
  2. var obj = {
  3. value: 1
  4. }
  5. function show(name, age){
  6. console.log(this.value);
  7. console.log(name, age)
  8. }
  9. var newShow = show.bind(obj "cst");
  10. newShow(18);//1 23 18

模拟

核心:改变this指向,返回一个函数,参数列表问题

  1. Function.prototype.newBind = function(target){
  2. target = target || window;
  3. //this -> show
  4. var self = this;
  5. var args = [].slice.call(arguments, 1)//从第一位开始截取传入所有参数,因为第零位为target,这里截取的args相当于Func.bind(target, arguments)中的arguments
  6. var temp = function(){};
  7. var F = function(){
  8. var _arg = [].slice.call(arguments, 0);//_arg相当于在调用bind生成的函数时所传入的参数,因此args拼接_arg即为最后所有的参数列表
  9. return self.apply(this instanceof temp ? this : target, args.concat(_arg));
  10. }
  11. temp.prototype = this.prototype;
  12. F.prototype = new temp();
  13. return F;
  14. }

纯函数

函数f的概念就是,对于输入x产生一个输出y = f(x)

纯函数的定义是,对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态,如:

  1. let num = 18;
  2. function compare(x, num){
  3. return x < num;
  4. }
  5. console.log(compare(10, num))

在向一个函数中传入参数时,如果参数为引用值,在函数中可使用深度克隆来达到纯函数的效果

Bug-守恒定律

一旦你网站或应用的代码量达到一定程度,它将不可避免的包含某种bug。这不是JavaScript特有的问题,而是一个几乎所有语言都有的通病——虽然不是不可能,但是想要彻底清除程序中的所有bug还是非常难办到的。但是,这并不意味着我们不可以通过某些代码方式来预防bug的引入。

纯函数非常容易进行单元测试,因为不需要考虑上下文环境,只需要考虑输入和输出

用处:更好的管理状态,使得可预测性增强,降低代码管理的难度,但是前端基本上都是在和副作用打交道,所有函数都是纯函数这种愿望不可强求

柯里化

在数学和计算机科学中,柯里化时一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术

前端使用柯里化的用途主要就应该是简化代码结构,提高系统的维护性,一个方法,只有一个参数,强制了功能的单一性,很自然就做到了功能内聚,降低耦合。

柯里化的优点就是降低代码的重复,提高代码的适用性

前奏

  1. function add(a, b, c, d){
  2. return a + b + c + d;
  3. }
  4. function FixedParamsurry(fn){
  5. var _arg = [].slice.call(arguments, 1);
  6. return function(){
  7. var newArg = _arg.concat([].slice.call(arguments, 0));
  8. return fn.apply(this, newArg);
  9. }
  10. }

柯里函数

思路,判断当前所传入参数列表的长度是否为原函数所需参数的长度,若未达到,则采用递归的形式继续查看后续传入的参数,直到总参数个数达到目标数为止,返回一个函数。

  1. function FixedParamsCurry(fn){
  2. var _arg = [].slice.call(arguments, 1);
  3. return function(){
  4. var newArg = _arg.concat([].slice.call(arguments, 0));
  5. return fn.apply(this, newArg);
  6. }
  7. }
  8. function Curry(fn, length){
  9. var length = length || fn.length;
  10. return function(){
  11. if(arguments.length < length){
  12. var combined = [fn].concat([].slice.call(arguments, 0));
  13. return Curry(FixedParamsCurry.apply(this, combined), length - arguments.length);
  14. }else{
  15. return fn.apply(this, arguments);
  16. }
  17. }
  18. }

JS中函数的length属性表示函数有多少个必须传入的参数

可用于Ajax请求中逐个传入参数,或保存传入某个参数后返回的函数,利用返回的函数更方便的发出Ajax请求

节流

在前端开发中有一部分的用户行为会频繁的触发事件执行,而对于DOM操作、资源加载等耗费性能的处理,很可能导致界面卡顿,甚至浏览器的崩溃。函数节流(throttle)和函数防抖(debounce)就是为了结局类似需求应运而生的

函数节流就是预定一个函数只有大于等于执行周期时才执行,周期内调用不执行。好像水滴攒到一定重量才会落下一样。

场景:窗口调整(resize)、页面滚动(scroll)、抢购疯狂点击(mousedown)

  1. const oBtn = document.getElementsByTagName('button')[0];
  2. const oDiv = document.getElementsByTagName('div')[0];
  3. let timer = null;
  4. oBtn.onclick = throttle(buy, 1000)
  5. function buy(){
  6. console.log(this)
  7. oDiv.innerHTML = parseInt(oDiv.innerHTML) + 1;
  8. }
  9. function throttle(handler, time){
  10. let lastTime = 0;
  11. return function(e){
  12. let nowTime = new Date().getTime()
  13. if (nowTime - lastTime > time){
  14. handler(this, arguments);
  15. lastTime = nowTime;
  16. }
  17. }
  18. }

防抖

在前端开发中有一部分的用户行为会频繁的触发事件执行,而对于DOM操作,资源加载等耗费性能的处理,很可能导致界面的卡顿,甚至浏览器的崩溃。函数节流(throttle)和函数防抖(debounce)就是为了解决类似需求应运而生的

函数防抖就是在函数需要频繁触发情况时,只有够空闲的事件,才执行一次。好像公交司机会等人都上车后才出站一样

  1. const ipt = document.getElementsByTagName('input')[0];
  2. let timer = null;
  3. ipt.oninput = debounce(ajax, 1000);
  4. function debounce(fn, time){
  5. let timer = null;
  6. return function(){
  7. let _self = this;
  8. let _arg = arguments;
  9. clearTimeout(timer);
  10. timer = setTimeout(function(){
  11. fn.apply(_self, _arg)
  12. }, time);
  13. }
  14. }
  15. function ajax(e){
  16. console.log(this.value, e);
  17. }