MockingBird

MockingBird 查看完整档案

深圳编辑湖南科技大学  |  信息安全 编辑Skybility  |  FE 编辑 blog.mcbird.cn 编辑
编辑

_
/ You are only young once, but you can \

\ stay immature indefinitely. /

\

\
    .--.
   |o_o |
   |:_/ |
  //   \ \
 (|     | )
/'\_   _/`\
\___)=(___/

个人动态

MockingBird 关注了问题 · 6月8日

React如何优雅的在公共组件中调用另一组件的方法?

页面布局为 上下布局
上部是工具栏A(如复制、粘贴等按钮)
下部是展示界面B -> C -> D -> E (-> 为嵌套关系)

如果在A上点击按钮时(复制)调用底层E中的方法(paste)?

现在的做法是在EcomponentDidMount方法中,将Ethis存到redux中,在点击A时调用 相应的this.paste.
有其他更好的方法吗?

关注 2 回答 0

MockingBird 回答了问题 · 6月5日

clearInterval只执行一次,执行后,setInterval也不执行了??

用函数把开始计时器的逻辑包起来,clearInterval()后在重新 invoke 即可:

// ...
function startTimer() {
    timmer=setInterval(function(){
        isOnLine();
        if(isStart){
            console.log("no",isStart);
            clearInterval(timmer);
            // 重新开始计时
            startTimer();
        }
    },120000)
}
// 初始化,开启计时器
startTimer();

关注 4 回答 3

MockingBird 回答了问题 · 6月5日

鼠标点击时,如何判断事件坐标是否在选中文字区域

通过监听 selectionchange 事件可以获取到选中区域的相关信息:

document.addEventListener('selectionchange', (e) => {
  const selection = window.getSelection();
  const range = selection.getRangeAt(0);
  console.info(range.getBoundingClientRect());
});

image.png

通过这个选中区域的信息与鼠标点击位置进行比较,基本上可以满足需求。

关注 3 回答 3

MockingBird 赞了文章 · 6月2日

webpack 打包的代码怎么在浏览器跑起来的?看不懂算我输

说点什么

最近在做一个工程化强相关的项目-微前端,涉及到了基座项目和子项目加载,并存的问题;以前对webpack一直停留在配置,也就是常说的入门级。这次项目推动,自己不得不迈过门槛,往里面多看一点。

本文主要讲webpack构建后的文件,是怎么在浏览器运行起来的,这可以让我们更清楚明白webpack的构建原理。

文章中的代码基本只含核心部分,如果想看全部代码和webpack配置,可以关注工程,自己拷贝下来运行: demo地址

在读本文前,需要知道webpack的基础概念,知道chunk 和 module的区别

本文将循序渐进,来解析webpack打包后的文件,代码是怎么跑起来的,从以下三个步骤娓娓道来:

  • 单文件打包,从IIFE说起;
  • 多文件之间,怎么判断依赖的加载状态;
  • 按需加载的背后,黑盒中究竟有什么黑魔法;

从最简单的说起:单文件怎么跑起来的

最简单的打包场景是什么呢,就是打包出来html文件只引用一个js文件,项目就可以跑起来,举个🌰:

// 入口文件:index.js
import sayHello from './utils/hello';
import { util } from './utils/util';

console.log('hello word:', sayHello());
console.log('hello util:', util);

// 关联模块:utils/util.js
export const util = 'hello utils';

// 关联模块:utils/hello.js
import { util } from './util';

console.log('hello util:', util);

const hello = 'Hello';

export default function sayHello() {
  console.log('the output is:');
  return hello;
};

入门级的代码,简单来讲就是入口文件依赖了两个模块: util 与 hello,然后模块hello,又依赖了util,最后运行html文件,可以在控制台看到console打印。打包后的代码长什么样呢,看下面,删除了一些干扰代码,只保留了核心部分,加了注释,但还是较长,需要耐心:

 (function(modules) { // webpackBootstrap
  // 安装过的模块的缓存
  var installedModules = {};
  // 模块导入方法
  function __webpack_require__(moduleId) {
    // 安装过的模块,直接取缓存
    if (installedModules[moduleId]) {
      return installedModules[moduleId].exports;
    }
    // 没有安装过的话,那就需要执行模块加载
    var module = installedModules[moduleId] = {
      i: moduleId,
      l: false,
      exports: {}
    };
    // 上面说的加载,其实就是执行模块,把模块的导出挂载到exports对象上;
    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
    // 标识模块已加载过
    module.l = true;
    // Return the exports of the module
    return module.exports;
  }
  // 暴露入口输入模块;
  __webpack_require__.m = modules;
  // 暴露已经加载过的模块;
  __webpack_require__.c = installedModules;
  // 模块导出定义方法
  // eg: export const hello = 'Hello world';
  // 得到: exprots.hello = 'Hello world';
  __webpack_require__.d = function (exports, name, getter) {
    if (!__webpack_require__.o(exports, name)) {
      Object.defineProperty(exports, name, {
        enumerable: true,
        get: getter
      });
    }
  };

  // __webpack_public_path__
  __webpack_require__.p = '';
  // 从入口文件开始启动
  return __webpack_require__(__webpack_require__.s = "./src/index.js");
 })({
  "./webpack/src/index.js":
  /*! no exports provided */
  (function(module, __webpack_exports__, __webpack_require__) {
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    var _utils_hello__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils/hello */ "./webpack/src/utils/hello.js");
    var _utils_util__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./utils/util */ "./webpack/src/utils/util.js");
    console.log('hello word:', Object(_utils_hello__WEBPACK_IMPORTED_MODULE_0__["default"])());
    console.log('hello util:', _utils_util__WEBPACK_IMPORTED_MODULE_1__["util"]);
  }),
  "./webpack/src/utils/hello.js":
  (function(module, __webpack_exports__, __webpack_require__) {
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    __webpack_require__.d(__webpack_exports__, "default", function() { return sayHello; });
    var _util__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./util */ "./webpack/src/utils/util.js");

    console.log('hello util:', _util__WEBPACK_IMPORTED_MODULE_0__["util"]);
    var hello = 'Hello';
    function sayHello() {
      console.log('the output is:');
      return hello;
    }
  }),

  "./webpack/src/utils/util.js":
  (function(module, __webpack_exports__, __webpack_require__) {
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    __webpack_require__.d(__webpack_exports__, "util", function() { return util; });
    var util = 'hello utils';
  })
});

咋眼一看上面的打包结果,其实就是一个IIFE(立即执行函数),这个函数就是webpack的启动代码,里面包含了一些变量方法声明;而输入是一个对象,这个对象描述的就是我们代码中编写的文件,文件路径为对面key,value就是文件中定义的代码,但这个代码是被一个函数包裹的:

/**
 * module:               就是当前模块
 * __webpack_exports__: 就是当前模块的导出,即module.exports
 * __webpack_require__:  webpack加载器对象,提供了依赖加载,模块定义等能力
**/
function(module, __webpack_exports__, __webpack_require__) {
  // 文件定义的代码
}

加载的原理,在上面代码中已经做过注释了,耐心点,一分钟就明白了,还是加个图吧,在vscode中用drawio插件画的,感受一下:

20200516121057

除了上面的加载过程,再说一个细节,就是webpack怎么分辨依赖包是ESM还是CommonJs模块,还是看打包代码吧,上面输入模块在开头都会执行__webpack_require__.r(__webpack_exports__), 省略了这个方法的定义,这里补充一下,解析看代码注释:

  // 定义模块类型是__esModule, 保证模块能被其他模块正确导入,  
  __webpack_require__.r = function (exports) {
    if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
      Object.defineProperty(exports, Symbol.toStringTag, {
        value: 'Module'
      });
    }
    // 模块上定义__esModule属性, __webpack_require__.n方法会用到
    // 对于ES6 MOdule,import a from 'a'; 获取到的是:a[default];
    // 对于cmd, import a from 'a';获取到的是整个module
    Object.defineProperty(exports, '__esModule', {
      value: true
    });
  };
  // esModule 获取的是module中的default,而commonJs获取的是全部module
  __webpack_require__.n = function (module) {
    var getter = module && module.__esModule ?
      function getDefault() {
        return module['default'];
      } :
      function getModuleExports() {
        return module;
      };
    // 为什么要在这个方法上定义一个 a 属性? 看打包后的代码, 比如:在引用三方时
    // 使用import m from 'm', 然后调用m.func();
    // 打出来的代码都是,获取模块m后,最后执行时是: m.a.func();
    __webpack_require__.d(getter, 'a', getter);
    return getter;
  };

最常见的:多文件引入的怎么执行

看完最简单的,现在来看一个最常见的,引入splitChunks,多chunk构建,执行流程有什么改变。我们常常会将一些外部依赖打成一个js包,项目自己的资源打成一个js包;

还是刚刚的节奏,先看打包前的代码:

// 入口文件:index.js
+ import moment from 'moment';
+ import cookie from 'js-cookie';
import sayHello from './utils/hello';
import { util } from './utils/util';

console.log('hello word:', sayHello());
console.log('hello util:', util);
+ console.log('time', moment().format('YYYY-MM-DD'));
+ cookie.set('page', 'index');
// 关联模块:utils/util.js
+ import moment from 'moment';
export const util = 'hello utils';

export function format() {
  return moment().format('YYYY-MM-DD');
}

// 关联模块:utils/hello.js
// 没变,和上面一样

从上面代码可以看出,我们引入了moment与js-cookie两个外部JS包,并采用分包机制,将依赖node_modules中的包打成了一个单独的,下面是多chunk打包后的html文件截图:

20200516132542

再看看async.js 包长什么样:

// 伪代码,隐藏了 moment 和 js-cookie 的代码细节
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["async"],{
  "./node_modules/js-cookie/src/js.cookie.js": (function(module, exports, __webpack_require__) {}),
  "./node_modules/moment/moment.js": (function(module, exports, __webpack_require__) {})
})

咋一样看,这个代码甚是简单,就是一个数组push操作,push的元素是一个数组[["async"],{}], 先提前说一下,数组第一个元素数组,是这个文件包含的chunk name, 第二个元素对象,其实就和第一节简单文件打包的输入一样,是模块名和包装后的模块代码;

再看一下index.js 的变化:

(function(modules) { // webpackBootstrap
  // 新增
  function webpackJsonpCallback(data) {
    return checkDeferredModules();
    };
    
  function checkDeferredModules() {
  }

  // 缓存加载过的模块
  var installedModules = {};
  // 存储 chunk 的加载状态
  // undefined = chunk not loaded, null = chunk preloaded/prefetched
  // Promise = chunk loading, 0 = chunk loaded
  var installedChunks = {
    "index": 0
  };
  var deferredModules = [];
  // on error function for async loading
  __webpack_require__.oe = function(err) { console.error(err); throw err; };

  // 加载的关键
  var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
  var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
  jsonpArray.push = webpackJsonpCallback;
  jsonpArray = jsonpArray.slice();
  for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
  var parentJsonpFunction = oldJsonpFunction;

  // 从入口文件开始启动
  - return __webpack_require__(__webpack_require__.s = "./src/index.js");
  // 将入口加入依赖延迟加载的队列
  + deferredModules.push(["./webpack/src/index.js","async"]);
  // 检查可执行的入口
  + return checkDeferredModules();
})
({
  // 省略;
})

从上面的代码看,支持多chunk执行,webpack 的bootstrap,还是做了很多工作的,我这大概列一下:

  • 新增了checkDeferredModules,用于依赖chunk检查是否已准备好;
  • 新增webpackJsonp全局数组,用于文件间的通信与模块存储;通信是通过拦截push操作完成的;
  • 新增webpackJsonpCallback,作为拦截push的代理操作,也是整个实现的核心;
  • 修改了入口文件执行方式,依赖deferredModules实现;

这里面文章很多,我们来一一破解:

### webpackJsonp push 拦截

// 检查window["webpackJsonp"]数组是否已声明,如果未声明的话,声明一个;
var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
// 对webpackJsonp原生的push操作做缓存
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
// 使用开头定义的webpackJsonpCallback作为代码,即代码中执行indow["webpackJsonp"].push时会触发这个操作
jsonpArray.push = webpackJsonpCallback;
// 这不操作,其实就是jsonpArray开始是window["webpackJsonp"]的快捷操作,现在我们对她的操作已完,就断开了这个引用,但值还是要,用于后面遍历
jsonpArray = jsonpArray.slice();
// 这一步,其实要知道他的场景,才知道他的意义,如果光看代码,觉得这个数组刚声明,遍历有什么用;
// 其实这里是在依赖的chunk 先加载完的情况,但拦截代理当时还没生效;所以手动遍历一次,让已加载的模块再走一次代理操作;
for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
// 这个操作就是个赋值语句,意义不大;
var parentJsonpFunction = oldJsonpFunction;

直接写上面注释了,webpackJsonpCallback在后面会解密。

代理 webpackJsonpCallback 干了什么

function webpackJsonpCallback(data) {
  var chunkIds = data[0];
  var moreModules = data[1];
  var executeModules = data[2];

  // add "moreModules" to the modules object,
  var moduleId, chunkId, i = 0, resolves = [];
  for(;i < chunkIds.length; i++) {
    chunkId = chunkIds[i];
    // 下一节再讲
    installedChunks[chunkId] = 0;
    
  }
  for(moduleId in moreModules) {
    if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
      // 将其他chunk中的模块加入到主chunk中;
      modules[moduleId] = moreModules[moduleId];
    }
    }
  // 这里才是原始的push操作
  if(parentJsonpFunction) parentJsonpFunction(data);
  while(resolves.length) {
    // 下一节再讲
  }
  // 这一句在这里没什么用
  deferredModules.push.apply(deferredModules, executeModules || []);

  // run deferred modules when all chunks ready
  return checkDeferredModules();
};

还记得前面push的数据是什么格式吗:

window["webpackJsonp"].push([["async"], moreModules])

拦截了push操作后,其实就做了三件事:

  • 将数组第二个变量 moreModules 加入到index.js 立即执行函数的输入变量modules中;
  • 将这个chunk的加载状态置成已完成;
  • 然后checkDeferredModules,就是看这个依赖加载后,是否有模块在等这个依赖执行;

checkDeferredModules 干了什么

function checkDeferredModules() {
  var result;
  for(var i = 0; i < deferredModules.length; i++) {
    var deferredModule = deferredModules[i];
    var fulfilled = true;
    for(var j = 1; j < deferredModule.length; j++) {
      // depId, 即指依赖的chunk的ID,,对于入口‘./webpack/src/index.js’这个deferredModule,depId就是‘async’,等async模块加载后就可以执行了
      var depId = deferredModule[j];
      if(installedChunks[depId] !== 0) fulfilled = false;
    }
    if(fulfilled) {
        // 执行过了,就把这个延迟执行项移除;
        deferredModules.splice(i--, 1);
        // 执行./webpack/src/index.js模块
      result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
    }
  }
  return result;
}

还记得入口文件的执行替换成了: deferredModules.push(["./webpack/src/index.js","async"]), 然后执行checkDeferredModules。
这个函数,就是检查哪些chunk安装了,但有些module执行,需要依赖某些
chunk,等依赖的chunk加载了,再执行这个module。上面的那一句代码就是./webpack/src/index.js这个模块执行依赖async这个chunk。

小总结

到这里,似乎多chunk打包,文件的执行流程就算理清楚了,如果你能想明白在html中下面两种方式,都不会导致文件执行失败,你就真的明白了:

<!-- 依赖项在前加载 -->
<script type="text/javascript" data-original="async.bundle_9b9adb70.js"></script>
<script type="text/javascript" data-original="index.4f7fc812.js"></script>

<!-- 或依赖项在后加载 -->
<script type="text/javascript" data-original="index.4f7fc812.js"></script>
<script type="text/javascript" data-original="async.bundle_9b9adb70.js"></script>

按需加载:动态加载过程解析

等多包加载理清后,再看按需加载,就没有那么复杂了,因为很多实现是在多包加载的基础上完成的,为了让理论更清晰,我添加了两处按需加载,还是那个节奏:

// 入口文件,index.js, 只列出新增代码
let count = 0;

const clickButton = document.createElement('button');

const name = document.createTextNode("CLICK ME");

clickButton.appendChild(name);

document.body.appendChild(clickButton);

clickButton.addEventListener('click', () => {
  count++;
  import('./utils/math').then(modules => {
    console.log('modules', modules);
  });

  if (count > 2) {
    import('./utils/fire').then(({ default: fire }) => {
      fire();
    });
  }
})

// utils/fire
export default function fire() {
  console.log('you are fired');
}

// utils/math
export default function add(a, b) {
  return a + b;
}

代码很简单,就是在页面添加了一个按钮,当按钮被点击时,按需加载utils/math模块,并打印输出的模块;当点击次数大于两次时,按需加载utils/fire模块,并调用其中暴露出的fire函数。相对于上一次,会多打出两个js 文件:0.bundle_29180b93.js 与 1.bundle_42bc336c.js,这里就列其中一个的代码:

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[1],{
"./webpack/src/utils/math.js":
  (function(module, __webpack_exports__, __webpack_require__) {})
}]);

格式与上面的async chunk 格式一模一样。

然后再来看index.js 打包完,新增了哪些:

 (function(modules) { 
  // script url 计算方法。下面的两个hash 是否似曾相识,对,就是两个按需加载文件的hash值
  // 传入0,返回的就是0.bundle_29180b93.js这个文件名
     function jsonpScriptSrc(chunkId) {
         return __webpack_require__.p + "" + ({}[chunkId]||chunkId) + ".bundle_" + {"0":"29180b93","1":"42bc336c"}[chunkId] + ".js"
  }
  // 按需加载script 方法
  __webpack_require__.e = function requireEnsure(chunkId) {
         // 后面详讲
     }; 
 })({
  "./webpack/src/index.js": (function(module, __webpack_exports__, __webpack_require__) {
    // 只列出按需加载utils/fire.js的代码
    __webpack_require__.e(/*! import() */ 0)
    .then(__webpack_require__.bind(null, "./webpack/src/utils/fire.js"))
    .then(function (_ref) {
        var fire = _ref["default"];
        fire();
    });
  }
})

在上一节的接触上,只加了很少的代码,主要涉及到两个方法jsonpScriptSrcrequireEnsure,前者在注释里已经写得很清楚了,后者其实就是动态创建script标签,动态加载需要的js文件,并返回一个Promise,来看一下代码:

__webpack_require__.e = function requireEnsure(chunkId) {
  var promises = [];
    var installedChunkData = installedChunks[chunkId];
  // 0 意为着已加载.
  if(installedChunkData !== 0) {
      // a Promise means "currently loading": 意外着,已经在加载中
      // 需要把加载那个promise:(即下面new的promise)加入到当前的依赖项中;
    if(installedChunkData) {
      promises.push(installedChunkData[2]);
    } else {
      // setup Promise in chunk cache:new 一个promise
      var promise = new Promise(function(resolve, reject) {
        installedChunkData = installedChunks[chunkId] = [resolve, reject];
        });
      // 这里将promise本身记录到installedChunkData,就是以防上面多个chunk同时依赖一个script的时候
      promises.push(installedChunkData[2] = promise);

      // 下面都是动态加载script标签的常规操作
      var script = document.createElement('script');
      var onScriptComplete;
      script.charset = 'utf-8';
      script.timeout = 120;
      if (__webpack_require__.nc) {
        script.setAttribute("nonce", __webpack_require__.nc);
      }
      script.src = jsonpScriptSrc(chunkId);

      // 下面的代码都是错误处理
      var error = new Error();
      onScriptComplete = function (event) {
          // 错误处理
      };
      var timeout = setTimeout(function(){
        onScriptComplete({ type: 'timeout', target: script });
      }, 120000);
      script.onerror = script.onload = onScriptComplete;
      // 添加script到body
      document.head.appendChild(script);
    }
  }
  return Promise.all(promises);
};

相对来说requireEnsure的代码实现并没有多么特别,都是一些常规操作,但没有用常用的onload回调,而改用promise来处理,还是比较巧妙的。模块是否已经加装好,还是利用前面的webpackJsonp的push代理来完成。

现在再来补充上面一节说留着下一节讲的代码:

function webpackJsonpCallback(data) {
  var chunkIds = data[0];
  var moreModules = data[1];
  var executeModules = data[2];

  var moduleId, chunkId, i = 0, resolves = [];
  for(;i < chunkIds.length; i++) {
    chunkId = chunkIds[i];
    if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
      // installedChunks[chunkId] 在这里加载时,还是一个数组,元素分别是[resolve, reject, promise],这里取的是resolve回调;
      resolves.push(installedChunks[chunkId][0]);
    }
    installedChunks[chunkId] = 0;
    // moreModules 注入忽略
    while(resolves.length) {
      // 这里resolve时,那么promise.all 就完成了
             resolves.shift()();
    }
  }
}

所以上面的代码做的,还是利用了这个代理,在chunk加载完成时,来把刚刚产生的promise resolved 掉,这样按需加载的then就继续往下执行了,非常曲折的一个发布订阅。

总结

自此,对webpack打包后的代码执行过程就分析完了,由简入难,如果多一点耐心,还是比较容易就看懂的。毕竟wbepack的高深,是隐藏在webpack自身的插件系统中的,打出来的代码基本是ES5级别的,只是用了一些巧妙的方法,比如push的拦截代理。

如果有什么不清楚的,推荐clone项目,自己打包分析一下代码:demo地址: webpack项目

(https://github.com/closertb/c...

查看原文

赞 27 收藏 20 评论 1

MockingBird 收藏了文章 · 2019-12-13

SegmentFault 技术周刊 Vol.3 - 前后端分离与前端工程化

weekly-vol003.jpg

小编:听了这么多前后端分离,今天小编就来和各位好好聊一聊。
编小:请问小编,前后端分离是什么?为什么要这么做?具体怎么做?
小编:咳咳,前后端分离的问题啊。简单的说,就是前端负责浏览器端用户交互界面和逻辑等,显示数据;后端负责数据的处理和存储等,提供数据。要详细说……还是要具体到项目里理解,再编就编不下去了 还是看我们这一期的周刊吧。

本期周刊,是社区的小伙伴关于前后端分离的内容,包括何为前后端分离,前后端分离的意义,以及各家在前后端分离上做过的尝试等。我们从前端技术发展,来看这几年越来越成熟的前端工程,组件化和模块化的大势所趋,或许会更好地理解为何前后端分离越来越重要。

前后端分离探索之路

n͛i͛g͛h͛t͛i͛r͛e͛ - 前后分离架构的探索之路

探索前后分离并不是像很多旁观者说的“为了分离而分离”,反而是“为了更好的理解 web 开发这回事而分离。”

劳卜 - 我们为什么要尝试前后端分离

  • 项目一开始制作前端页面的时候,我不再需要后台给我配置服务器环境了

  • 项目的前端文件可以在需要调用后台接口的时候丢进服务器就好了,完全不需要事先放进去

  • 增加一个项目页面需要配置路由的时候不再需要让后台同事给我加了,自己前端搞定

  • 页面跳转比之前更加流畅了,局部渲染局部加载非常快速

  • 页面模板可以重复使用了,前端组件化开发提高了开发效率

  • ……

好处太多了。

戴嘉华 - Web 前后端分离开发思路

在原始的前后端分工下,会经常有这样的疑惑:既然前端数据是由后端提供,那么后端数据接口还没有完成,前端是否就无法进行编码?怎么样才能做到前后端独立开发?

你会说,可以本地前后端连接一下,使用 API 模拟数据请求,那又出现新的问题,难道每写一个 API 都要把前后端连接测试一遍吗?而且,我如果需要测试某个 API,而你的这个 API 还没写好,那我这个功能模块的进度就“阻塞”了。

后面还有那么多 API 需要写,所以不能这么做。

arccode - 前后端完全分离之 API 设计

前后端完全分离后,前端和后端如何交互呢?答案是,通过双方协商好的 API。本文详细介绍 API 的设计及异常处理。

Justin_lu - 前后端分离实践 — 如何解决跨域问题

“后端提供 API,前端消费 API”。而即使这样让分工更专注,跨域也还是一个需要解决的重要问题,本文就来说说如何解决跨域。

更多阅读:

还有前端工程呢

“现在的前端早已不是几年前的前端,再也不是 jQuery 加一个插件就能解决问题的时代。”

kuitos - 前端工程化知识要点回顾&思考

「一套好的工程化解决方案,能在提高开发效率(包括代码编写的舒适度及多人协作)的同时,确保整个系统的伸缩性(各种不同的部署环境)及健壮性(安全),同时在性能上又能有一个很优异的表现(主要上各种缓存策略加载策略等),而且这套方案又应该是对工程师无感知(或感知很小)趋于自动化的一套方案。」

lixiang - 公司前端开发架构改造

统一多端的问题、组件化开发、模块化开发和打包、自动更新缓存……看作者如何利用 Gulp 和 Webpack 这两个工具,对公司前端开发进行改造,对团队整个开发的思想与模式进行改造。

keelii - 京东单品页前端开发那些不得不说的事儿

以京东单品页的前端开发为例,从前端发展历史说起,介绍了单品页前端模块的结构与划分、前端的技能树、前端工程化的应用、程序设计以及产品等多块知识,这是一篇不可错过的前端学习图谱。

dmyang - 基于 webpack 搭建前端工程解决方案探索

  • 提供开发所需的一整套运行环境,这和 IDE 作用类似

  • 资源管理,包括资源获取、依赖处理、实时更新、按需加载、公共模块管理等

  • 打通研发链路的各个环节,debug、mock、proxy、test、build、deploy 等

  • ……

前端工程,起码需要这些基本开发环节的问题,本文以开发一个多页面型 Web App 为例,介绍 webpack 的基本原理,以及基于 webpack 搭建纯静态页面型前端项目工程化解决方案的思路,给出以上问题的解决方案。

xiaoyann - 使用 webpack + react + redux + es6 开发组件化前端项目

标题就能看出来,这是一篇真·实战,文章最后还有对使用 webpack 的问题及性能优化作出的总结。

fwon - gulp + webpack 构建多页面前端项目

这篇是最近很流行的工程中工具化的代表,使用 gulp 和 webpack 工程化构建多页面项目,还有比这适用性更高的么?

kuitos - 基于 git hooks 的前端代码质量控制解决方案

当前的 code review 过程总是不尽人意,你是否尝试过基于 git 的 hook 功能来做一些自动化的事情呢?作者就想这个办法,通过 git hooks 里配置预处理脚本,让每次提交代码时做 code check,如果没有通过直接拒绝接收 push。(是不是够狠…

更多阅读:

(本期完)

往期周刊传送门:Vol.1 - Vue.js 起手式 | Vol.2 - 666,ES6


# SegmentFault 技术周刊 #

「技术周刊」是社区特别推出的技术内容系列,一周一主题。周刊筛选的每篇内容,是作者的独到见解,踩坑总结和经验分享。

每周二更新,欢迎「关注」或者「订阅」。大家也可以在评论处留言自己感兴趣的主题,推荐主题相关的优秀文章。

查看原文

MockingBird 分享了头条 · 2018-05-29

这个 Github repo 涵盖了非常全面的数据结构、算法的 javascript 现实。并提供相关说明以及进一步阅读和 YouTube 视频。

赞 0 收藏 5 评论 0

MockingBird 发布了文章 · 2018-05-23

Webpack 是如何加载模块的

Webpack 在前端开发中作为模块打包工具非常受开发者的青睐,丰富的 loader 使它可以实现各种各样的功能。本文将通过 webpack 来打包一个 js 文件,看看 webpack 是如何加载各个模块的。

两个简单的源文件

为了方便分析 webpack 加载模块的原理,我们准备了两个文件:

hello.js

const hello = {
    say: arg => {
        console.info('hello ' + arg || 'world');
    }
};

export default hello;

index.js

import Hello from './hello';

Hello.say('man');

index.js 作为入口文件,引用了 hello.js 模块。

Webpack 打包

在命令行执行 webpack index.js bundle.js 对入口文件进行打包,生成 bundle.js ,大体结构为(为了方便阅读,我删除了部分多余的代码):
2018-05-23-17-33-27-2018523173328

可以看到,最终生成的文件以 (function (modules) {})([模块1, 模块2]) 的方式启动,我们定义的模块被包装成一个个匿名函数,然后以数组的形式传递个一个匿名函数 function (modules) {},在这个匿名函数中定义了一个 __webpack_require__() 函数,用来加载模块,最后,通过 return __webpack_require__(__webpack_require__.s = 0); 来加载第一个模块 index.js

__webpack_require__() 函数

该函数接收一个 moduleId 作为参数,这个参数就是各个模块在数组中的索引,

function __webpack_require__(moduleId) {
        /******/
        /******/ // Check if module is in cache
        /******/
        if (installedModules[moduleId]) {
            /******/
            return installedModules[moduleId].exports;
            /******/
        }
        /******/ // Create a new module (and put it into the cache)
        /******/
        var module = installedModules[moduleId] = {
            /******/
            i: moduleId,
            /******/
            l: false,
            /******/
            exports: {}
            /******/
        };
        /******/
        /******/ // Execute the module function
        /******/
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
        /******/
        /******/ // Flag the module as loaded
        /******/
        module.l = true;
        /******/
        /******/ // Return the exports of the module
        /******/
        return module.exports;
        /******/
    }

其中 installedModules 是用来缓存执行过的模块。通过 modules[moduleId].call() 来执行模块,最后返回模块的 exports。

模块接受的参数

hello.js 模块为例

    (function (module, __webpack_exports__, __webpack_require__) {

        "use strict";
        const hello = {
            say: arg => {
                console.info('hello ' + arg || 'world');
            }
        };

        /* harmony default export */
        __webpack_exports__["a"] = (hello);

        /***/
    })

webpack 会向模块传递 module, __webpack_exports__, __webpack_require__ 三个参数,前两个是用来导出模块内的变量,第三个参数为前面介绍的 __webpack_require__() 的引用,用来导入其它模块。

查看原文

赞 1 收藏 1 评论 0

MockingBird 回答了问题 · 2018-05-21

解决React.createElement()生成的元素怎么添加绑定事件

createElement()的调用方式如下:

React.createElement(
  type,
  [props],
  [...children]
)

绑定事件写在[props]中,例如:

var target = React.createElement('button', {
  onClick: () => { alert('lol') },
}, 'Click me');

ReactDOM.render(
        target,
        document.getElementById('root')
);

DEMO:
https://codepen.io/CodingMonk...

关注 2 回答 1

MockingBird 回答了问题 · 2018-05-21

解决禁止非数字输入,但搜狗输入法中文状态下仍可以输入字母e

keypress事件的兼容性不好,不建议监听这个事件。
clipboard.png

可以监听input事件,然后用正则判断字符串是否合法:

var inputer = document.getElementById("inputer");

var lastValue = '';
inputer.addEventListener("input", function(e) {
  if (!/^\d*$/.test(e.target.value)) {
    this.value = lastValue;
  }
}, false);

DEMO:
https://codepen.io/CodingMonk...

关注 3 回答 2

MockingBird 回答了问题 · 2018-05-21

解决关于react输入框的onChange事件的触发问题

setState() 是一个异步操作,后面的 console.log() 执行的时候, state 还没更新完,所以输出的是之前的值。

关注 7 回答 5

认证与成就

  • 获得 252 次点赞
  • 获得 39 枚徽章 获得 1 枚金徽章, 获得 12 枚银徽章, 获得 26 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2014-03-27
个人主页被 1.3k 人浏览