从设计层思考JavaScript

ECMAScript与JavaScript

  • ECMAScript是JavaScript的规范,JavaScript是ECMA的具体实现
  • ECMAScript只是JavaScript的一部分
  • JavaScript由ECMAScript,文档对象模型(DOM)浏览器对象模型(BOM)组成

ECMAScript主要版本与ES6

  • ES3是各大浏览器支持度最高的一个版本,几乎所有的浏览器都支持ES3
  • ES4版本因争议巨大而放弃,一部分作为3.1发版,一部分由ES6实现
  • 09年底推出ES5,这一版是我们最熟悉的一版,因为ESMA完全支持向下兼容,所以ES5也通常是我们学习JavaScript的起点。
  • 到了2015年6月,ES6横空出世,它变革巨大、增幅很多,推动JavaScript语言向着越来越规范、严格的方向走
  • 而后ES7/ES8都是作为ES6的补充,可以理解为小版本升级

语言设计思想

  • 语言设计之灵魂拼接

    • 借鉴了C的基本语法
    • Java的数据类型/内存管理
    • scheme函数式编程语言风格
    • self的原型
  • JavaScript是函数式编程+面向对象编程糅合出的产物

    • 面向对象基本规则
      • 属性与方法的综合体
      • 不存在独立的属性和方法
    • 原型编程语言基本规则
      • 所有的数据都是对象
      • 对象会记住他的原型
      • 当请求访问对象的某个方法无法响应时,会把这个请求委托给自己的原型

区分对象

  • 本地对象: 独立于宿主环境的,ECMAScript实现提供
  • 内置对象: ECMAScript实现提供,无需实例化
  • 宿主对象: 执行JS脚本的环境提供的对象,一般是浏览器

window对象挂载来源

  • 浏览器对象模型(BOM)- 如window.location
  • 宿主环境对象提供的全局作用域 - 全局变量/方法都会成为window的对象/属性

语言的执行规则

  • 赋值:赋值变量的内存地址,包括值类型
  • 变量提升:先定义后执行,定义与执行分步进行.首先自上向下执行定义语句,其后执行赋值语句

奇怪的undefined与null

  • undefined和null都是JS的基本数据类型,但null实际是比较特殊的基本数据类型
  • undefined的数据类型为undefined,而null的数据类型是Object
  • undefined所代表的含义是缺少值
  • null是对象,存在于堆中,它是唯一的,也是原型链的终点

引用类型

  • Array和function的值都是Object
  • Array和Function是Object的派生类

你可能只需要这一个教程

webpack - 万物皆可打包

  • 打包CSS

  通过import或require引入样式文件,webpack无法直接打包,需引入css-loader将其转化
  下载依赖css-loader与style-loadernpm install --save-dev css-loader style-loader,并新增配置如下

1
2
3
4
5
6
7
8
9
10
11
12

module.exports = {
module:{
rules: [
{
test: /\.css$/,
loader: ['style-loader','css-loader'],
exclude: /node_modules/
}
]
}
};

  loader的执行顺序是从后向前,即先执行css-loader,转化后交给style-loader,将其挂载到html文件内
  css-loader负责转化css,而style-loader将转化后的css,包裹在style标签内,并将其插入到html文件的header中
  此处应注意包引入的顺序与被置入的内联样式顺序有关,重复并不会转化多次
  style-loader是以文件为单位,将其包裹为一个内部样式
  css实际打包进了js文件,并未分离出来,后面将讲解css的分离配置

  • 打包CSS预编译语言(scss/less/stylus)

  css预编译语言同样无法直接编译,需要使用相应的loader,我们以sass为例
  下载sass-loader,这里我们跟着官方文档第一个来配置
  有时官方文档也会出现更新不及时的问题,另sass-loader依赖node-sass,它在底层是用C语言编写,可能会在服务器端出现依赖包问题
  下载依赖npm install sass-loader node-sass webpack --save-dev,并配置如下

1
2
3
4
5
6
7
8
9
10
11
12

module.exports = {
module:{
rules: [
{
test: /\.s[ac]ss$/i,
loader: ['style-loader','css-loader','scss-loader'],
exclude: /node_modules/
}
]
}
};

  与css相比,我们需要先使用scss-loader将sass语言转为css语言

  • 添加CSS3前缀

  浏览器一日不统一,我们就需要一日跟兼容性抗争到底。每次写样式我们都要加一堆的兼容前缀么?自动添加兼容代码,摆脱兼容烦恼
  下载依赖npm install --save--dev postcss-loader autoprefixer
  需要在css的loader中修改,我们以sass加配置

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
module:{
rules: [
{
test: /\.s[ac]ss$/i,
loader: ['style-loader','css-loader','sass-loader','postcss-loader'],
exclude: /node_modules/
}
]
}
};

  同时我们需要在根目录下新建postcss的配置文件postcss.config.js,配置如下

1
2
3
modules.exports = {
plugins: [ require("autoprefixer") ]
};

  此处也可以直接配置在webpack中,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
module:{
rules: [
{
test: /\.s[ac]ss$/i,
loader: ['style-loader','css-loader','sass-loader',{
loader: 'postcss-loader',
options:{
plugins: [require('autoprefixer')]
}
}],
exclude: /node_modules/
}
]
}
};

  post-loader会自动加载postcss.config.js下的配置,将文件内配置的插件加载进去
  这时再打包出来的css,将会自动为我们加入兼容前缀

  • css模块化打包

  当前引入与打包生成的css样式,是全局样式,如何做到样式隔离呢
  像vue样式的scoped,如何样式隔离只针对当前工作区呢
  css模块化配置如下(scss/less/stylus等相同)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
module:{
rules: [
{
test: /\.css$/,
loader: ['style-loader',{
loader: 'css-loader',
options: {
modules: true // true/local 开启模块化机制 默认global
}
}],
exclude: /node_modules/
}
]
}
};

  之前我们引入样式的方式如下

1
import "./app.css";

  重新打包后我们发现,app.css中标签样式可以显示,而类选择器、id选择器等样式没有生效
  我们查看一下打包后生成的样式会发现样式并没有丢失,但除标签名外,其他选择器名都帮我们改为了无意义的字符串
  也就是说,如果我们想用这些样式,就要知道我们写的类名对应生成的字符串是什么,那么如何获取这些名称呢
  modules为我们抛出了一个对象,key为我们定义的选择器名,value对应生成的字符串
  我们需要这样引入:

1
2
3
4
// 个人猜测 模块化是根据原有选择器名进行编码处理 再通过export default { 选择器名1: 编码选择器名1,选择器名2: 编码选择器名2,...  }
import CSS from"./app.css";

// 假如我们在app.css有一个样式 .avatar{ ... } 此时我们只需要获取CSS.avatar 并将其绑定到对应DOM上,即可生效

  默认hash编码,我们还可以指定编码方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module.exports = {
module:{
rules: [
{
test: /\.css$/,
loader: ['style-loader',{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[path][name]__[local]--[hash:base64:5]',
},
}
}],
exclude: /node_modules/
}
]
}
};
  • 字体打包

  当我们使用外部字体库时,很显然直接引用,我们需要对字体进行打包处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module.exports = {
module:{
rules: [
{
test: /\.(woff|woff2|svg|eot|ttf)/, // 常用字体库
loader: [{
loader: 'file-loader', // 还记得我们的图片打包么
options: { // 若不配置,默认打包只output文件下,并修改文件名为编码后的字符串
outputPath: "font/", // 输出的文件路径
name: '[name].[ext]' // 输出的文件名 ext是文件后缀名称
}
}],
exclude: /node_modules/
}
]
}
};

手写v-model之简造

** 很多都是自己的理解,”谨”供参考,如果错误,烦请指正

什么是双向绑定

 双向绑定的概念最早是从Angularjs1.0版出现,这也是当年Angularjs能称为顶级框架的原因之一
 所谓的双向绑定,实际是为了实现数据与视图的”联动”
 它的一端连着数据仓库,另一端连着视图,而它站在中间帮你完成所有的同步(绑定)工作
 你要做的,就是向上帝管理河流一样,保证你数据格式正确、清晰它的来源和去向、以及保证每种数据变化的合理
 当然,这不代表你应该过分控制你的河流

 想要将数据仓库与试图同步起来,你要做2件事,这就是双向绑定的核心任务
    ① 数据变动,通知视图变化
    ② 视图变化,修改数据仓库

 第2件事是我们最擅长的,监听视图即事件监听,修改相应的仓库数据
 而第1件事,想要监听数据变动,我们就需要借助相应的API
 下一步,我们了解一下可以监听数据的两个API:ES5的Object.defineProperty 与ES6的Proxy

Object.defineProperty

 Object.defineProperty(obj, prop, descriptor)
   obj:要在其上定义属性的对象
   prop:要定义或修改的属性的名称
   descriptor:将被定义或修改的属性描述符
      ① configurable: 该属性描述符是否可通过delete删除,默认false
      ② enumerable: 是否可枚举,默认false(不可枚举属性无法通过for…in遍历)
      ③ value: 属性值,默认undefined
      ④ writable: 是否可被赋值运算符改变,默认false
      ⑤ get: 方法名,属性被获取时调用,默认undefined
      ⑥ set: 方法名,属性被改变时调用,默认undefined

 使用方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

// 监听对象 obj 监听属性 name
const obj = {};
Object.defineProperty(obj,'name',{
get: function () {
// 此处获取对象属性值
return this._name;
},
set: function (newValue) {
this._name = '皮一下 ' + newValue;
}
});

obj.name = '张三'';
console.log(obj.name);

 defineProperty可以劫持数据,但只能监听对象中的某个属性
 IE8以下不支持该属性
 Vue底层通过defineProperty实现双向绑定

Proxy

 new Proxy(target, handler);
   ① target: 监听对象
   ② handler: 操作对象
   ③ 返回该对象
  ES6吸收了强类型语言的精华,为JavaScript新增了代理Proxy与反射Reflect
  Proxy可以监听整个对象的变化

  用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var obj = new Proxy({},{
get (target,propKey,receiver){
console.log('获取属性'+propKey + '的值');
return Reflect.get(target,propKey,receiver);
},
set(target,propKey,value,receiver){
// target: 监听对象
// target下的改变的属性名
// 新赋值
// Proxy对象
return Reflect.set(target,propKey,'皮一下 '+value,receiver);
}
});

// 注: obj是Proxy对象

obj.name = '张三';
console.log(obj.name);

简单数据绑定

 初级数据绑定,我们只需要满足数据与视图的捆绑即可

1
2
3
4
<div id="app">
<div id="divText">{{name}}</div>
<input id="inputText" />
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

// 监听对象
const obj = { name: 'HelloWorld' };

// 获取DOM元素
const $divText = document.querySelector('#divText');
const $inputText = document.querySelector('#inputText');

// 页面渲染逻辑
function pageInit(target){
$divText.innerHTML = obj[target] || '';
$inputText.value = obj[target] || '';
}

// 初始化视图
pageInit('name');


Object.defineProperty(obj,'name',{
get(){
return obj._name;
},
set(newValue){
obj._name = newValue;

// 粗暴通知视图变化
pageInit('name');
}
});

// 监听事件
$inputText.addEventListener('input',function(){
obj.name = this.value;
},false)

仿源码建造数据绑定

 仿Vue搭建可扩展的数据绑定,调用如下

1
2
3
4
5
6
// 无需再为DOM绑定ID
<div id="app">
<div>姓名:{{name}}</div>
<div>年龄:{{age}}</div>
<input type="text" v-model=' name ' />
</div>

1
2
3
4
5
6
7
Vue({
el: '#app', // 根DOM
data: { // 绑定数据
name: '小明',
age: 18
}
})

 Vue与字符串模版相似,将根DOM下的元素经过处理后将其替换
 但不同于简单粗暴的字符串模版,Vue会通过DOM Diff算法来进行优化
 首先,我们初始化页面,将初始数据置入,并替换DOM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

// DOM结点处理
function cloneNode(node,vm) {
const flag = document.createDocumentFragment();
let child;

while(child = node.firstChild) {
compile(child,vm);
flag.appendChild(child);
}

return flag
}


// Vue 构造函数
function Vue(opts){
this.$data = opts.data || {};
const _root = document.querySelector(opts.el || '#app');
const _dom = cloneNode(_root,this);
_root.appendChild(_dom);
}


// 处理模版DOM中的绑定数据
function compile(node,vm) {
const attr = node.attributes;

// 元素为input且双向绑定
if(~~node.nodeType === 1 && node.nodeName == 'INPUT' && attr['v-model']) {
const _name = attr['v-model'].value.trim();
node.value = vm.$data[_name];
node.addEventListener('input', function () {
// 更新后的数据绑定到了vm中,而不是vm.$data;
vm[_name] = this.value;
},false);
node.removeAttribute('v-model');
}else {
// 全局替换对象
node.nodeValue = node.nodeValue.replace(/\{\{(.*)\}\}/g, function (reg,$1) {
return vm.$data[$1.trim()];
})
}
}

 我们已经绑定了input事件,及时更改了Vue实例对象中的数据值,但我们还没有做数据监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function Vue(opts){
this.$data = opts.data || {};
const _id = document.querySelector(opts.el || "#app");

observe(this.$data,this); // 新加

const _dom = cloneNode(_id,this); // 获取劫持DOM
_id.appendChild(_dom); // 将替换为劫持后的DOM
}


function observe() {
Object.keys(obj).forEach(key=>{
defineReactive(vm,key,obj[key]);
})
}


// 数据监听处理 obj--> 历史值
function defineReactive(vm,key,val) {
Object.defineProperty(vm,key,{
get(){
//
return val;
},
set(newValue){
if(newValue == value) return;
val = newValue;

// 视图更新
// 留白
}
})
}

 视图更新我们用发布-订阅者模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

// 发布者
function Dep(){
this.subs = [];
}

Dep.prototype = {
// 添加监听对象
addSub(sub){
this.subs.push(sub);
},
// 通知所有被监听者
notify(){
this.subs.forEach(sub=>sub.updata());;
},
};

// 订阅者
function Watcher(vm,node,name,nodeType){
this.vm = vm;
this.node = node;
this.name = name;
this.nodeType = nodeType;
};

Watcher.prototype = {
// 更新视图
update(){
// 获取最新数值
this.value = this.vm[this.name];
// 更新相应的DOM
}
};

 最后就是何时订阅,何时发布了
 DOM处理数据时->订阅,监听到数据变化是->发布
 此外,将监听数据全部扩展到vm中
 完整代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
function Dep(){
this.subs = [];
}

Dep.prototype = {
addSub(sub){
this.subs.push(sub);
},
notify(){
this.subs.forEach(sub=>sub.update());
},
};


function Watcher(vm,node,name,nodeType){
this.vm = vm;
this.node = node;
this.name = name;
this.nodeType = nodeType;
};

Watcher.prototype = {
update(){
this.value = this.vm[this.name];
if(this.nodeType == 'input'){
this.node.value = this.value;
}else{
this.node.nodeValue = this.value;
}
}
};

let dep = new Dep();

function cloneNode(node,vm) {
const flag = document.createDocumentFragment();
let child;

while(child = node.firstChild) {
compile(child,vm);
flag.appendChild(child);
}

return flag;
}

function compile(node,vm) {
const attr = node.attributes;
let nodeType = 'text';
let _name;

// 元素为input且双向绑定
if(~~node.nodeType === 1 && node.nodeName == 'INPUT' && attr['v-model']) {
_name = attr['v-model'].value.trim();
node.value = vm.$data[_name];
node.addEventListener('input', function () {
// 更新后的数据绑定到了vm中,而不是vm.$data;
vm[_name] = this.value;
},false);
node.removeAttribute('v-model');
nodeType = 'input'
}else {
// 全局替换对象
node.nodeValue = node.nodeValue.replace(/\{\{(.*)\}\}/g, function (reg,$1) {
_name = $1.trim();
return vm[$1.trim()];
})
};

dep.addSub(new Watcher(vm,node,_name,nodeType));

}


function observe(obj,vm){
Object.keys(obj).forEach(key=>{
defineReactive(vm,key,obj[key]);
})
}

function defineReactive(vm,key,val) {
Object.defineProperty(vm,key,{
get(){
return val;
},
set(newValue){
if(newValue == val) return;
val = newValue;
dep.notify();
}
})
}


function Vue(opts){
this.$data = opts.data || {};
const _root = document.querySelector(opts.el || '#app');

observe(this.$data,this);


const _dom = cloneNode(_root,this);
_root.appendChild(_dom);
}

手写Redux之简造

** 很多都是自己的理解,”谨”供参考,如果错误,烦请指正

单向数据流

 React是单向数据流,首先我们讲讲单向数据流
 何为单向数据流,顾名思义,就是数据像河流一样只能从一端流向另一端,而且是地势较高的一端(顶级/父级)-向地势低的一端(子级)流动
 这种数据传输特点保证了数据的纯净性,当我们想”追根溯源”,可以轻易检测到问题症结
 当然,React或Vue都可以通过$emit,使子组件通知父组件变更数据,但它依然不会破坏数据的单一流动

Content

 基于单向数据流的处理方式,使我们想要从顶层将数据传给底层子组件,需要中间跨越层层组件
 假使我们嵌套了N层,意味着我们的数据需要经过N层组件,中间N-2个组件都是毫无意义的数据传声筒
 而这样的情况有很多,像我们的用户数据,公共配置,主题…等等
 Content就是帮我们做全局数据处理的,我们常用的React-redux/React-router都是基于Content
 还记得redux是怎么”劝”我们的嘛?

    - “如果你不知道是否需要 Redux,那就是不需要它。”
    - “只有遇到 React 实在解决不了的问题,你才需要 Redux 。”

 redux解决了我们的公共数据问题,但redux只是工具,没有redux我们一样可以实现功能
 是的没错,就是自己使用Context实现

 我们先来明确React不同数据对象的职责:
    - state: 维护组件内部”产生数据”的信息(即本组件内所用的数据)
    - props: 维护组件外部”传入数据的信息”(即来自父组件的数据)
    - content: 维护跨域组件信息的传递(即全局数据)

 关于Redux语法

  • Content创造者:
    • 在产生参数的最顶级组件中,使用childContextTypes静态属性来定义需要放入全局参数的类型。
    • 在父组件中,提供状态,来管理数据
    • 声明子组件获取全局参数的方式
  • Content消费者: 任何需要使用Content的组件
    • 在子组件中,使用contentTypes静态属性,声明需要获取父组件放入全局content中的参数类型
    • 在子组件需要的地方,获取全局参数:this.content.全局属性名
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37

      // 创造者
      import React,{Component} from 'react';
      import PropTypes from 'prop-types';

      class App extends Component{
      state = {
      title : 'Content使用Demo',
      };

      static childContextTypes = {
      title: PropTypes.string,
      };

      getChildContext(){
      return {
      title: this.state.title,
      }
      }
      }


      // 消费者
      import React,{Component} from 'react';
      import PropTypes from 'prop-types';

      class ChildComponent extends Component {
      static contextTypes = {
      title: PropTypes.string,
      };

      render() {
      return (
      <div>{ this.context.title }</div>
      );
      }
      }

Redux用法

 Store: 数据存储仓库
    - createStore: 方法,产生Store的实例对象
    - store.getState(): 返回Store对象中的所有数据

1
2
3
4
import {createStore} from 'redux';
const store = createStore(reducer);

const state = store.getState();

    - store.subscribe(),对store数据监听,一旦发生变化就自动执行
    - store.subscribe()返回一个函数,调用此方法可以解除监听

  Action:触发Store中数据变化的接口及处理方式
    - Action是一个对象,其中type属性是必须的

1
2
3
4
const action = {
type: 'ADD_TODO',
payload: 'Learn Redux'
};

    - 改变数据的唯一方法,就是触发Action
    - store.dispatch就是外界触发Action的唯一方法

1
2
3
4
5
6
7
import { createStore } from 'redux';
const store = createStore(fn);

store.dispatch({
type: 'ADD_TODO',
payload: 'Learn Redux'
});

 Reducer: 新数据更新规则
    - Reducer函数一定要是个纯函数

1
2
3
4
5
6
7
8
  const reducer = function (state, action) {

switch (action.ntype) {
case 'action':
return Object.assign({},state,action.payload);
default: return state;
}
};

简造

 第一步 创造一个基本Store

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    
// 定义一个createStore函数 来管理全局状态数据
const createState = ()=> {
let state = {
title: '简造一个redux'
};

// 抛出获取state数据的方法
const getStore = ()=>state;

// 提供一个dispatch方法,
const dispatch = (action)=>{
switch (action.type) {
case 'DO': state.title=action.payload;break;
default: break;
}
}

return {getStore,dispatch};
};



// 使用Store
const store = createState();
store.getState(); // 获取state数据
store.dispatch({
type :'DO',
payload: '一次修改'
})

 第二步 创造一个reducer并传入Store

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 创建reducer
const changeStateFn = (state,action)=>{
switch (action.type) {
case 'DO': state.title=action.payload;break;
default: break;
}
};



// 修改Store 数据更改不再写死
const createState = (changeState)=> {
let state = {
title: '简造一个redux'
};

const getStore = ()=>state;

// dispatch返回changeState方法,并将数据state和dispatch的参数传入
const dispatch = (action)=>changeState(state,action);
return {getStore,dispatch};
};


// 调用Store 将Reducer传入State
const store = createState(changeStateFn);

 第三步 Store新增数据监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const createState = (changeState)=>{
let state = {
title: '简造一个redux'
};

// 收集监听方法
let listeners = [];

// 将所有监听方法存入监听数组
const subscribe = (listener)=> {listeners.push(listener)};

const dispatch = (action)=>{
// 触发数据更新
changeState(state,action);
// 调用所有监听方法
listeners.forEach(listener=>listener());
};

// 抛出方法
return {dispatch,subscribe};

}

 第四步 优化数据更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 返回新state
const changeStateFn = (state,action)=>{
switch (action.type) {
case 'DO': return {
...state,
...action.payload
}
default: break;
}
};


const createState = (changeState)=> {
let state = {
title: '简造一个redux'
};

let listeners = [];

const dispatch = (action)=>{
// 更改state值
sate = changeState(state,action);
listeners.forEach(listener=>listener());
};
};

 简造版redux打造完毕
 稍作整理,将unsubscribe也加入如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
const createStore =  (reducer)=> {
let state = {};
let listeners = [];

const getStore = ()=>state;
const unsubscribe = (index)=>{
if(listeners.length <= index) return;
return ()=> listeners.splice(index,1);
};
const subscribe = (listener)=> {
const length = listener.length;
listeners.push(listener);
return unsubscribe(length);
};
const dispatch = (action)=>{
state = reducer(state,action);
listeners.forEach(listener=>listener());
};

return {getStore,subscribe,dispatch};
};

const changeStateFn = (state,action)=>{
switch (action) {
case 'DO': return Object.assign({},state,action.payload);
default: return state;
}
};

// 使用
const store = createStore(changeStateFn);


const oldState = store.getStore();
store.subscribe(()=>{
const newState = store.getStore();
if(oldState.title !== newState.title) {
console.log('change the title');
}
});

store.dispatch({
type: 'DO',
payload: {title: '修改title'}
});

 下一步,我们去浅析源码

前端常用学习网站

@(welcome)[前端菜鸟|掘金中]

###技术网站

###资源整合

###美工集锦

###全栈修炼

###官方文档

###前端工具

~~~~

###JS库

###JS规范

###jQuery插件及特效

###插件库

###H5 和 CSS3

###代码优化

###快速搭建项目

###深度好文

###前端搭建工具

###前端服务器

###待学习

###关于工具

###关于mac

###编辑软件

###前后端通道

###company

###微信小程序

###兼职orwork

###后端步步为营

上古利器jQuery源码剖析(一)

上古利器 虽经沧海变革 宝剑尚锋

 随着数据驱动时代的到来,jQuery不可避免的跌下神坛,但应记得它曾何等辉煌
 我们学习jQuery,不是学习它的用法,而是学习它的架构,它的思想,来深刻理解JS
 优秀的jQuery还未落伍,像当下最火着的axios/fetch等都源自对ajax的吸纳,11年就出现的异步解决方法deferred,在Promise里处处是它的影子
 之前并没有怎么看过别人的源码教程,第一次写源码稍显吃力,给自己加油吧

  * 首先,你应该知道

 $ 只是一个 jQuery的一个别名
 jQuery是一个构造函数,可以理解为就是一个普通的构造函数
 但是我们在调用jQuery的时候,并没有使用new关键字呢.其实这里,jQuery帮我们做了一层语法糖的封装,即调用$()的时候,它自动帮我们创建了jQuery的实例对象
 即$()时就为我们实现了new

  * 经典的jQuery框架

 首先我们创造一个自执行函数,传入this,通过root来接收
 在匿名函数里 我们创建一个叫jQuery的变量,jQuery变量拿到的是一个匿名函数的引用
 这个匿名函数,我们会把它当成构造函数
 因为我们是在全局环境下传入的this,所以这里的this指的就是window对象
 给环境变量root扩展一个叫做jQuery的属性,让它指向jQuery变量,同时再给root扩展一个叫做$的别名
 在随手给jQuery设置一下原型方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(function(root){
var jQuery = function () { // 构造函数

};

jQuery.prototype = {
init: function() {

}
};

//root.jQuery = jQuery;
root.$ = root.jQuery = jQuery;
})(this); // 环境变量this 因为是全局环境 所以这里指的是window

 上面的代码看起来一点都不复杂,甚至还有一点小简单,但是别忘了,我们刚才说过jQuery是一个构造函数,$()时为我们实现了new
 让jQuery返回new jQuery(),就可以完美解决!图样图破森,那样会死循环
 我们再造一个构造函数,让jQuery返回这个构造函数不就好了,这个构造函数就是init

1
2
3
4
5
6
7
8
9
10
11
12
13
(function(root){
var jQuery = function () { // 别忘了 jQuery我们也是当做构造函数的
return new jQuery.prototype.init(); // 返回构造函数init的实例对象
};

jQuery.prototype = {
init: function(){ // init是一个匿名对象,在这里我们将它当做一个构造函数

},
};

root.$ = root.jQuery = jQuery;
})(this);

  * 共享原型

 新的问题来了,$()返回了init方法,而init方法的原型对象上什么都没有,如何将方法扩展进来呢?
 如果init的原型对象是jQuery.prototype,那jQuery.prototype的所有方法都会挂载到init的实例中,也就是$()中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(function(root){
var jQuery = function () { // 别忘了 jQuery我们也是当做构造函数的
return new jQuery.prototype.init(); // 返回构造函数init的实例对象
};

jQuery.prototype = {
init: function(){ // init是一个匿名对象,在这里我们将它当做一个构造函数

},
};

jQuery.prototype.init.prototype = jQuery.prototype; // init的原型对象指向为jQuery的原型原型对象

root.$ = root.jQuery = jQuery;
})(this);

 init是jQuery.prototype的一个方法,init的原型对象又是jQuery.prototype,那么init拥有了jQuery.prototype的所有方法(包括它自己)
 设计的思路是这样的,我们有两个函数jQuery和init,$()就等同于实例化jQuery即new jQuery(),但们无法直接让jQuery函数返回new jQuery,这样会形成死循环,于是我们实例化了另一个构造函数init
 我们将init方法的原型也指向了jQuery的方法原型,实现了原型共享
 因此,jQuery.prototype原型对象里封装的所有属性和方法,jQuery与init都是可以调用的
 最反思维的地方就在于了,init是Query.prototype原型对象的一个方法,可以想想计算机的自举~

* 工具函数

 jQuery框架非常重要的一个函数-工具函数,它的作用是将jQuery所有的自身对象或者实例对象的属性和方法,全部都剥离开来,进行分类管理
 jQuery中一共有两个对象,一个是实例对象,一个是jQuery本身的对象
 起手式中我们实现了实例对象,而jQuery除了是一个构造函数,它也是一个函数对象,我们也有很多函数对象是挂载到jQuery本身上的,例如$.ajax/$.each等
 我们给jQuery添加一个静态属性fn,我们让fn等于jQuery的原型对象,这样我们在外部扩展时,至需要扩展jQuery.fn即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(function(root){
var jQuery = function () {
return new jQuery.prototype.init();
};

jQuery.fn = jQuery.prototype = { // jQuery的静态方法等于jQuery的原型对象,原型对象也是对象
init: function(){},
};

jQuery.fn.init.prototype = jQuery.fn; // 替换

root.$ = root.jQuery = jQuery;

})(this);

 外部扩展时,只需要$.fn.新方法名 = function(){} ->插件编写的形式
 通过调用静态属性扩展到jQuery的实例对象上

* extend

 extend在jQuery函数内部是作为一个工具函数存在,用它来管理不同对象(jQuery本身以及它的原型对象)的扩展
 extend在外部可以给 ①任意对象扩展/浅拷贝/深拷贝 ②jQuery本身扩展 ③jQuery实例对象扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
(function(root){
var jQuery = function () {
return new jQuery.prototype.init();
};
jQuery.fn = jQuery.prototype = {
init: function(){},
};


// jQuery与 jQuery.prototype的原型都扩展了一个extend属性
jQuery.extend = jQuery.prototype.extend = function () {
// 方法调用时,我们有可能给任何对象/jQuery对象/jQuery原型对象进行扩展
// 我们通过参数来进行区分
// 参数1 是我们要扩展的对象 参数2 是我们要变量的对象
// 分析 有1个参数 可能给jQuery对象/jQuery原型对象扩展 有2个及以上 给任意对象arguments[0]扩展

var target = arguments[0] || {}; // 参数处理: 参数为空? 赋值对象不是引用类型?
var length = arguments.length; // 参数个数
var i = 1; // 默认参数个数
var option;
var name;

if(typeof target !== "object"){
target = {};
}

// 1个参数
if(length == i){

}

// 2个及以上参数
for(;i<length;i++){
if((option = arguments[i]) !== null){
for(name in option){

}
}
}
};



jQuery.fn.init.prototype = jQuery.fn;
root.$ = root.jQuery = jQuery;

})(this);



从0基础到大牛-webpack深入浅出超强完整版(四)

你可能只需要这一个教程

webpack - 昆仑山上光明顶

  loader和plugin都是webpack重要的组成部分,loader主要用于文件内的处理,而plugin主要用于文件间的处理,有时他们需要共同协作
  plugin的执行顺序与书写顺序无关,它通常是一个构造函数,挂载到webpack不同的生命周期中,等待执行

  • html-webpack-plugin

  html-webpack-plugin是我们最长的Plugin之一,它会帮我们管理html,
  下载插件npm install --save--dev html-webpack-plugin,配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {},
output: {},
mode: "development",
module:{},
plugins: [

// new HtmlWebpackPlugin()
// 默认在output文件下生成一个标题为Webpack App的html文件,并引入entry内的所有文件

new HtmlWebpackPlugin({
template : "./src/index.html"
})
// 指定html模板文件 会自动复制当前文件到output文件下,并引入entry内的所有口文件
]
};

  • clean-webpack-plugin

  想要每次新打包前清除output文件,以防止废弃文件占用内存空间,我们可以使用clean-webpack-plugin
  下载插件npm install --save--dev clean-webpack-plugin,配置如下

1
2
3
4
5
6
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
plugins: [
new CleanWebpackPlugin(),
]
};

  • 指定服务器地址

  如有需要指定打包的服务器地址,可配置如下

1
2
3
4
5
6
7
module.exports = {
output: {
filename: '[name].js',
path: path.resolve(__dirname,'dist'),
publicPath: "http://xieyuxuan.cc" // 服务器地址
}
};

  打包后html引入的入口文件,都将会是publicPath的值+文件名

  • 错误调试 - sourceMap

  早期webpack打包后如有代码错误无法直接定位到源码,仅显示打包后的文件错误位置,这给开发调试造成了很大障碍
  sourceMap是一个映射关系表,能够将我们所有的源文件的映射到打包后的文件上。
  通过配置devtool,满足不同场景的调试需求
js module.exports = { devtool: 'none' // 默认值是none // source-map 会在output文件下生成一个 [output.filename].map的 映射文件 // inlne-source-map 将映射文件打入[output.filename] 内最底部 // cheap-module-source-map 生成环境中最常使用 生成[output.filename].map的 映射文件 // cheap-module-eval-source-map 开发环境中最常使用 // ... };

  • webpack-dev-server - 本地服务器

  webpack –watch命令可以实现文件更新检测,但这种方式无法满足我们的开发需要
  webpack-dev-server主要用于本地开发,满足我们的常规开发需求,大大提升我们工作效率
  它为我们开启一个本地服务器,可以实现热更新、跨域(服务器端没有跨域)、重定向等功能
  下载webpack-dev-server依赖npm install --save--dev webpack-dev-server,并在webpack.config.js中配置devServer属性

1
2
3
4
5
6
7
8
9
module.exports = {
entry: {},
output: {},
mode: "development",
module:{},
devServer:{
contentBase: './dist', // 服务器根文件地址
}
};

  在package.json中添加脚本,实现npm run dev开启本地服务

1
2
3
4
5
6
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack-dev-server"
}
}

  终端执行npm run dev后,webpack会告诉我们webpack-dev-server为我们开启了一个地址为http://localhost:8080的服务
  此时观察dist文件,该目录下文件清空,但页面显示正常,文件更改后会自动刷新页面并重新执行
  dist下的文件不再存放在磁盘中,而是被放置在内存中

  webpack-dev-server其它常用配置如下

1
2
3
4
5
6
7
8
module.exports = {
devServer:{
contentBase: './dist', // 服务器根文件地址
open: true, // 自动打开页面
host: 'localhost', // 指定服务器地址 默认 localhost
port: 8080 // 指定端口号 默认8080
}
};

  实现HMR(热模块更新)

1
2
3
4
5
6
7
8
9
10
11
12
const webpack = require('webpack');

module.exports = {
devServer:{
contentBase: './dist',
open: true,
hot: true // 开启热模块更新
},
plugins: [
new webpack.HotModuleReplacementPlugin() // 配置热更新
]
};

  • TreeShaking

  有时我们定义了N个方法,但仅有部分方法被调用,又不想让所有的方法都被打入,这时候我们就需要shaking一下
  将未使用的 exported member 标记为 unused 同时不再导出
  这里必须使用ESmodule的引入方式
  webpack.config.js新增配置如下

1
2
3
4
5
6
7
8
module.exports = {
entry: {},
output: {},
mode: "development",
optimization: {
usedExports: true, // 开去tree shaking
},
};

  同时需要在package中新增配置,取消副作用

1
2
3
4
5
{
"name": "webpack-demo",
"version": "1.0.0",
"slideEffects": false
}

  虽然webpack设置了tree shaking,但是比如包中有一些修改、设置、日志等产生的副作用,依然会被打入
  如果你不需要这些副作用,可以设置slideEffects为false,或者通过数组指定
  我们在设置副作用时应当明确,这个包没有副作用或者不需要它的副作用
  webpack会在线上shaking掉,开发环境制作包分析

  • 开发模式 和 生产模式

  开发模式 development 本地编写代码
  生产模式 production 用于打包完成后交给线上服务
  区分两种模式,实现不同环境的打包,现我们新建webpack.dev.js做开发配置,并将所有开发环境的配置保留,可参考如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpack = require('webpack');


module.exports = {
entry: { 'app' : './src/app.js' },
output: {
filename: '[name].js',
path: path.resolve(__dirname,'../dist'),
},
devtool: 'cheap-module-eval-source-map',
mode: "development",
devServer:{
contentBase: './dist',
hot: true,
},
optimization:{
usedExports: true,
},
module:{}, // 此处省略
plugins: [
new HtmlWebpackPlugin(
{template : "./src/index.html"}
),
new CleanWebpackPlugin(),
new webpack.HotModuleReplacementPlugin()
]
};

  新建webpack.prod.js,做线上配置,并将所有线上环境的配置保留,可参考如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
entry: {'main' : './src/main.js'},
output: {
filename: '[name].js',
path: path.resolve(__dirname,'../dist'),
},
devtool: 'cheap-eval-source-map',
mode: "production",
optimization:{
usedExports: true
},
module:{}, // 此处省略
plugins: [
new HtmlWebpackPlugin(
{template : "./src/index.html"}
),
new CleanWebpackPlugin(),
]
};

  将webpack配置文件都放入根目录下的config文件夹内
  配置package.json,通过不同的命令执行不同的打包环境

1
2
3
4
5
6
7
8
{
"name": "webpack-demo",
"version": "1.0.0",
"scripts": {
"dev": "webpack-dev-server --config config/webpack.dev.js",
"build": "webpack --config config/webpack.prod.js"
}
}

  通过命令npm run dev与命令npm run build我们实现了不同环境下的打包
  我们已经可以区分环境做处理了,下一步我们进行代码优化,避免重复定义
  新建webpack.common.js做公共配置文件,并将两种环境下共同部分迁移进来,可参考如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
entry: { 'app' : './src/app.js' },
output: {
filename: '[name].js',
path: path.resolve(__dirname,'../dist'),
},
optimization:{
usedExports: true,
},
module:{}, // 此处省略
plugins: [
new HtmlWebpackPlugin(
{template : "./src/index.html"}
),
new CleanWebpackPlugin(),
]
};

  修改环境配置项,仅保留公共配置不包含的,可参考如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// webpack.dev.js

const webpack = require('webpack');
module.exports = {
devtool: 'cheap-module-eval-source-map',
mode: "development",
devServer:{
contentBase: './dist',
hot: true,
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
};


// webpack.prod.js
module.exports = {
devtool: 'cheap-eval-source-map',
mode: "production",
};

  如何将配置文件合并呢,我们需要webpack-merge,下载依赖npm install --save--dev webpack-merge
  通过webpack-merge返回的方法实现合并(类似于Object.assign,实现对象的合并),如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// webpack.dev.js
const webpack = require('webpack');
const merge = require('webpack-merge');
const commConfig = require('./webpack.common.js');

const devConfig = {
devtool: 'cheap-module-eval-source-map',
mode: "development",
devServer:{
contentBase: './dist',
hot: true,
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
};


module.exports = merge(commConfig,devConfig);

// webpack.prod.js

const merge = require('webpack-merge');
const commConfig = require('./webpack.common.js');

const prodConfig = {
devtool: 'cheap-eval-source-map',
mode: "production",
}

module.exports = merge(commConfig,prodConfig);

从0基础到大牛-webpack深入浅出超强完整版(二)

你可能只需要这一个教程

webpack小试牛刀 - ES6+ 转 ES5

  • 全局垫片 polyfill

  使用babel,需要下载 babel-loader @babel/core @babel/preset-env
  babel-loader 下载 npm install -D babel-loader @babel/core @babel/preset-env webpack
  配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {
module: {
rules: [
{
test: /\.js$/, // 正则 匹配规则
exclude: /node_modules/, // 依赖文件已做过处理,无需再处理,节省打包时间
loader: "babel-loader", // 使用的loader
option: { // 配置 option的配置应参照使用的loader配置列表
presets: ["@babel/preset-env" ]
}
}
]
}
};

  打包生成的文件中,ES6语法将会转为ES5语法
  但并不是所有的ES6都可以被转化,ES6中一些内置API和功能(Promise set Map)、Object.assign等无法转化
  如果想要实现上述这些方法,我们需要使用垫片库(polyfill)npm install -D @babel/polyfill
  直接引入垫片库,会将所有方法都加载进来

1
2
3
4
5

import "@babel/polyfill";
const arr = [new Promise(()=>{}),new Promise(()=>{})];

// 此时 Object.assign Set Map等方法也会被打入,生成文件骤然变大

  如何实现按需加载呢?需要配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
module.exports = {
module: {
rules: [
{
rule: /\.js$/,
exclude: /node_modules/,
loader: "@babel-loader",
options: {
presets: [
["@babel/preset-env",{
"useBuiltIns" : "usage",
// usage: 按需加载 页面无需引入"@babel/polyfill"
// entry: 按需加载 页面需手动引入 import "@babel/polyfill"
// 自测时发现 entry比usage打出的包更轻量

"target": { // 目标 针对浏览器版本打包
ie: "9"
}
}]
]
}
}
]
}
};

  • babel的全局配置
      webpack中options的配置可以全部放置到babel全局配置文件中,其作用和用法完全一致。首先我们先将webpack中babel的options清除
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    module.exports = {
    module: {
    rules: [
    {
    rule: /\.js$/,
    exclude: /node_modules/,
    loader: "@babel-loader",
    }
    ]
    }
    };

  根目录下新建babel配置文件: .babelrc 并配置如下(此处注意JSON写法)

1
2
3
4
5
6
7
8
{
"presets" : [["@babel/preset-env",{
"useBuiltIns": "entry",
"targets": {
"chrome": 67
}
}]]
}

  • 局部垫片 transform-runtime

  polyfill是一个全局的垫片,会将方法全部扩展到原型上。如果是开发库或者框架,就需要使用局部垫片。
  下载transform-runtime npm install --save-dev @babel/plugin-transform-runtimenpm install --save @babel/runtime
  .babelrc配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"presets" : ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": false,
"helpers": true,
"regenerator": true,
"useESModules": false
}
]
]
}

  transform-runtime默认按需加载,无需配置
  corejs可取值false,2,3或{ version: 2 | 3, proposals: boolean },默认为false详见文档

  • 更改文件打包地址
1
2
3
4
5
6
7
const path = require("path"); // node
module.exports = {
output:{
filename: '[name].js',
path: path.resolve(__dirname,'dist'), // __dirname为当前文件路径 dist可更改为要打包的路径名 默认dist
}
}
  • 转换TS文件

  webpack支持Typescript打包,需先下载npm install --save-dev typescript ts-loader
  根目录下新建ts配置文件:tsconfig.json 并配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"compilerOptions": {
"module": "es6", // 模块的引入方式 es6|commonjs
"target": "es5", // 目标语法
"allowJs": true // 是否允许JS语法
},
"include": [ // 包含文件列表
"./src/",
"./"
],
"exclude": [ // 不包含的文件
"node_modules"
]
}

  并在webpack配置中新增loader

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
module: {
rules: [
{
test: /\.ts$/,
loader: "ts-loader",
exclude: /node_modules/,
}
]
}
};

  TypeSearch类型约束查找地址

  • 图片打包

  下载file-loader npm install file-loader --save-dev
  webpack中新增图片转换的loader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module.exports = {
module : {
rules : [
{
test: /\.(png|jpe?g|gif)/,
use: {
loader: "file-loader",
options:{
name: "[name]-[hash:5].[ext]", // name:图片名 ext:图片后缀名 hash:5 取Hash前5位值 默认文件名为hash:5
outputPath: "images/" // 输出文件夹(output.path)下 打包输出地址
}
},
exclude: /node_modules/
}
]
}
}

  注:loader有多种引入写法
  另一个插件url-loader包含file-loader,比file-loader更强大。npm install -D url-loader
  将上述配置file-loader改为url-loader,将不再生成image文件,会自动将图片转为base64位并注入js中。现在我们修改配置,限制转Base64大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module.exports = {
module : {
rules : [
{
test: /\.(png|jpe?g|gif)/,
use: {
loader: "file-loader",
options:{
name: "[name]-[hash:5].[ext]",
outputPath: "images/",
limit: 1024 * 1 //1024 = 1kb 低于1kb生成base64位,以减少http请求
}
},
exclude: /node_modules/
}
]
}
}

从0基础到大牛-webpack深入浅出超强完整版(一)

你可能只需要这一个教程

webpack介绍

1.webpack发展历程

发布时间

  • webpack   v1.x   2014.02
  • webpack   v2.x   2017.01
  • webpack   v3.x   2017.06
  • webpack   v4.0   2018.05   稳定版4.32.2
  • webpack   v5.x   2019.xx   目前不是稳定的版本

模块化历程
 单页面应用(Vue React)都在用模块化

命名空间

 蛮荒时代的模块化

1
2
3
4
5
6
7
8
9
var obj = {};
obj.type = obj.type || {};
obj.type.method = function() {};
obj.type.add = function() {};
obj.type.sub = function(){};

obj.addin = obj.addin || {};
obj.addin.remove = function() {};
obj.addin.append = function() {};

 AMD与CMD的中原逐鹿

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// AMD 异步加载 依赖前置 先加载后使用
// 需要库文件 - require.js
// 定义AMD模块 文件名amd
define(function(a,b) {
'use strict';
return {
add: function(a,b) {
return a + b;
},
sub: function(a,b) {
return a - b;
}
}

});

// 加载AMD模块
require(['moduleA','moduleB','amd'],function(a,b,amd) {
console.log(a); // a模块
console.log(b); // b模块

amd.add(1,2); // 调用amd
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// CMD 同步加载 依赖后置 使用时引入 
// 需要库文件 - sea.js
// 定义CMD模块 文件名cmd
define(function(require,exports,module) {
var moduleA = require('moduleA'); // 需要时引入

exports = {
add: function(a,b) {
return a+b;
},
sub: function(a,b) {
return a - b;
}
}
});

// 加载CMD模块
seajs.use(['cmd'],function(cmd) {
cmd.add(1,2);
cmd.sub(2,3);
});

 commonJS - Node.js主要践行者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 一个文件就是一个模块 文件名common
var a = 10;
var b = 20;
function fun() {

};

// 导出方式 一
modules.export = {
a : a,
b : b,
fun : b
};

// 导出方式 二
// export.add = function(){}; ...


// 通过关键字require引入文件【相对路径/绝对路径引入】
var common = require('common');
console.log(common.a); // 10

 官方科普最硬核 - ES Module(ES6的模块化)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 使用方式 一
// 导出 文件名es1
export var a = 10;
export var add = function(a,b) {
return a + b;
}


// 引入
import { a,add } from 'es1';
add(3,5); // 调用

// 使用方式 二
// 导出 文件名es2
export default const b = {
return 20;
};

// 引入
import b from 'es2';
console.log(b);

*CSS模块化

  • OOCSS(面向对象)
  • AMCSS(属性模块)

webpack 4.32.2核心组成

  • entry : 命令行中写入口,配置文件中
  • output
  • module
  • plugins

 准备工作
  卸载旧的webpack版本 npm uninstall webpack -g
  下载最新的版本 npm install webpack -g
  检查当前webpack版本 webpack --version
  需要在本地文件内下载webpack,初始化package.json文件 npm init -y
  去下载 webpack 和 webpack-cli,3.x 的版本是不需要 webpack-cli,webpack 中集成了 webpack-cli, 4.X以后没有集成
  webpack4.x 以下的使用 webpack app.js 是可以的,4.x以后因为增加了一个多入口命令打包,需要指定出口文: webpack app.js -o bundle.js
  如果不加 –mode development 参数,默认是生产环境,会自动压缩代码
  当前可下载的webpack版本 npm info webpack webpack-5.x-bate(公测的版本)

 起手式
  根目录下新建配置文件,默认配置文件为 webpack.config.js
  你的第一个配置文件

1
2
3
4
5
6
7
8
9
module.exports = {
// 入口 相对文件地址
entry: './app.js',
// 出口
output: {
filename: 'bundle.js'
},
mode: "development"
};

  执行命令webpack后会生成 /dist/bundle.js 文件
  使用自定义名称的配置文件my-webpack-setting.js打包,请执行 webpack --config my-webpack-setting.js

  • 多入口打包

    1
    2
    3
    module.exports = {
    entry: ['app.js','app1.js','app2.js']
    }
  • 自定义出口文件名打包

    1
    2
    3
    4
    5
    6
    7
    8
    module.exports = {
    entry: {
    main: './app1.js',
    },
    output: {
    filename: '[name].js',
    }
    }

  将生成 /dist/main.js 文件

  • 多入口多出口文件打包
    1
    2
    3
    4
    5
    6
    7
    8
    9
    module.exports = {
    entry: {
    main: './app.js',
    select: './my_select.js'
    },
    output: {
    filename: '[name].js',
    }
    }

  将在dist文件夹下生成main.js与select.js
  注:多入口对应多出口,多入口对应单一出口配置时将会报错

  • ES6 module 打包
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //  导出文件 es6.js
    export const add = function(a,b) {
    return a + b;
    }

    // 导入文件 app.js
    import { add } from './es6.js';
    console.log(add(4,7)); // 11


    // webpack配置文件
    module.exports = {
    entry: {
    app: './app.js',
    },
    output: {
    filename: '[name].js',
    },
    mode: "development"
    }

  结果生成 /dist/app.js 文件, 文件内包含 es6.js 与 app.js 代码

  • CommonJS 打包
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 导出 common.js
    module.exports = {
    add : function(a,b) {
    return a + b;
    },
    sub: function(a,b){
    return a - b;
    }
    };

    // 导入 app.js
    const method = reqiore('./common,js');
    console.log(method.add(4,7)); // 11
    console.log(method.sub(8,3)); // 5

    // webpack配置文件 同上

  结果生成 /dist/app.js 文件, 文件内包含 common.js 与 app.js 代码

  • amd模块 打包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 导出 amd.js
define(function(require,factory) {
return {
mul: function(a,b) {
return a * b;
}
}
});

// 导入
require(['./amd.js'],function(method) {
console.log(method.mul(10,4)); // 40
});

// webpack配置文件 同上

  dist文件夹下将生成 app.js与 0.js
  0.js异步加载的内容(amd.js内容)

前端设计模式之依赖倒置原则(DIP)

前端原型的个人猜想

  相信每个学习javaScript的同僚,一定对一个说法非常熟悉『一切皆对象』.

  这个说法很好的将我们从现实社会连接进了程序世界,同时也解释了程序界很多古怪无法理解的东西.

  而事实真是这样么?在我写了这么久代码之后,我反而对javaScript最基本的东西产生了越发的迷茫和混乱.

  像隐式转换、原型链的继承这种更是完全无法想通,当我看到null和undefined时鬼知道我的脑子会爆炸出什么,最后扭合到一起,变成一堆的问号…

  现在我希望彻底能搞清祖师爷在创造javaScript是究竟是怎样的思路,这对我来说很难,但我知道当我搞清楚这个问题时,会有一个很大的飞跃,我不应该继续『不了解但不影响工作』在的舒适区混下去.

  感谢jQuery的源码,让我有了共享原型的概念,不然我一定会强制要求原型链必须是个有唯一顶点的树状结构。

  *请注意下面代码中,一定要将Object.prototype和Function.prototype当成是独立的一部分

1
Object.prototype.__proto__ == null   // true

  这里我们知道了Object.prototype的原型链向上指向是null,注意我没有说它继承自null,因为null是个很特别的类型,我们都知道原型的本质是个对象,所以它应该继承自对象.
  那这里怎么会是个null呢?我是猜想浏览器帮我们指向了null.
  太好了,至少有一个封闭的点了!我们继续.

1
Object.__proto__ == Function.prototype  // true

  大声尖叫,Object竟然继承自Function.prototype,『一切皆对象』并不是最后的真像!就好比我们好不容易发现了真菌细菌这些肉眼看不见的小东西,转眼我们又发现了分子原子,难道大世界无限的在嵌套我们不知道的小世界么?

  好在程序世界的构成元素就是这几个,根本不存在宇宙这种无休无止的构成一说,但程序世界与现实世界还有一个最大的区别,叫做『自举』,这里我们可以简单理解为『我可以创造我自己』.

  有了上面代码,就下面代码的成立.

1
Object instanceof Function == true

  但是我们还知道

1
Object instanceof Object == true

  这里是『我创造了我自己』么?不是的,这里完全是多绕了一步

1
2
Object.__proto__ == Function.prototype   // true
Function.prototype.__proto__ == Object.prototype // true

  目前还没有出现Object,我们又回到了Object.prototype,而Object.prototype.proto等于null这件事我们在第一步就已经证实过了.

  问题出在哪呢?当然还是Object.prototype的继承问题!刚刚说过了Object.prototype很有可能是Object的实例,并保留了其继承关系,而浏览器对其赋值为null.

  这完全出自我的个人想法,因为如果不是这样,这条原型链的解释则都不成立.

  再看这个

1
Function.__proto__ == Function.prototype  // true

  Function继承自自己

/* 这里非常明确了Object继承自Function.prototype */


/*我们知道了上面代码的关系 下面这句就理所当然了*/


/* 但我们还知道*/