0%

模式

提供 mode 配置选项, 告知 webpack 使用相应的模式的内置优化

用法

只在配置中提供 mode 选项

1
2
3
module.exports = {
mode: 'production', // 生产环境
}

或者在 CLI 参数中传递

1
webpack --mode=production

选项

  • development:
    会将 process.env.NODE_ENV 的值设为 development. 启用 NamedChunksPluginNamedModulesPlugin

  • production:
    会将 process.env.NODE_ENV 的值设为 production. 启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPluginUglifyJsPlugin.

codes

mode: development

1
2
3
4
5
6
7
8
// webpack.development.config.js
module.exports = {
+ mode: 'development'
- plugins: [
- new webpack.NamedModulesPlugin(),
- new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }),
- ]
}

mode: production

1
2
3
4
5
6
7
8
// webpack.development.config.js
module.exports = {
+ mode: 'development'
- plugins: [
- new webpack.NamedModulesPlugin(),
- new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }),
- ]
}

云服务器

每个程序员都应该有一台自己的云服务器

知乎-自己拥有一台服务器可以做哪些很酷的事情

服务器厂商:

购买服务器

注: 下面流程以我自己购买的腾讯云服务器为例

点击腾讯云链接进入腾讯云首页, 点击右上角登录进行登录

在首页轮播图常年有活动, 新用户与学生一般是活动优惠力度最大的

点击轮播图活动进入后, 按照需求选择我们需要的服务器配置, 如果是个人学习、博客等使用, 最基本的即可

服务器

点击立即抢购之后, 跳入一个配置选择界面, 选择离自己所在地最近的城市, 选择机型配置, 选择 CentOS 系统

服务器

点击立即购买即可跳转付款页面, 核对无误后付款即可

控制台

在腾讯云首页右上角有一个控制台按钮, 点击即可进入控制台

服务器

点击服务器后将会跳转至实例界面(如果没有实例就创建一个)

实例

首次购买后会自动跳转至服务器控制台

登录远程服务器

以 MacOS 为例

打开终端, 输入 ssh root@xx.xx.xx.xx 回车

1
ssh root@xx.xx.xx.xx

xx.xx.xx.xx 替换为自己服务器公网 ip 地址

弹出提示要求输入密码

输入密码不会显示, 输入完成后直接回车即可

ssh

如果看到如下界面就表示密码输入成功

ssh

输入 cat /etc/redhat-release 查看系统信息

1
cat /etc/redhat-release

ssh

配置免密验证

在使用上面的命令登录远程服务器的时候, 是需要输入密码的, 而且以后每次访问都需要输入, 这样就很繁琐.

我们可以通过—ssh 免密验证

在本地生成秘钥

通过如下命令在本地机器生成秘钥

如果你使用过 git 配置, 就无需重复生成

1
2
3
# -t 指定加密算法; -b 指定生成的密钥长度; -C 一句话,一般都填邮箱地址。
# 更多参数说明可以在终端输入:ssh-keygen --help 查看
ssh-keygen -t rsa -b 4096 -C "你的邮箱地址"

这条命令执行完之后, 会提示指定公钥和私钥在本地机器上储存的位置, 因为我本地已经有秘钥了, 就从网上找了一张图

ssh

将公钥储存到远程服务器主机

输入以下命令, 然后回车, 将上一步我们在本地机器上生成的公钥写入服务器

1
2
3
4
# i后面接的参数是保存你公钥的文件(我们这里是.ssh/id_rsa.pub);
# root是远程主机的用户,这条指令会往root这个用户的主目录下的
# .ssh/authorized_keys文件写入id_rsa.pub保存的公钥
ssh-copy-id -i .ssh/id_rsa.pub root@xx.xx.xx.xx

ssh

根据提示, 输入服务器主机登录密码

ssh

如果看到如下提示, 表示添加成功

ssh

此时, 我们再使用 ssh root@xx.xx.xx.xx 对服务器进行访问, 就能直接登录, 不需要再输入密码了

ssh

loader

loader 用于对模块的源代码进行转换. loader 可以使你在 import 或 “加载” 模块时预处理文件. loader 可以将文件从不同的语言转换为 JavaScript, 或将内联图像转换为 data URL, loader 甚至允许直接在 JavaScript 模块中 import CSS 文件

特性

  • loader 支持链式传递, 一组链式的 loader 按照相反的顺序执行, loader 链中的第一个 loader 返回值给下一个 loader. 在最后一个 loader 返回 webpack 所预期的 JavaScript
  • loader 可以是同步的, 也可以是异步的
  • loader 运行在 Node.js 中, 并且能够执行任何可能的操作
  • loader 接受查询参数, 用于对 loader 传递配置
  • loader 也能够使用 options 对象进行配置
  • 除了使用 package.json 常见的 main 属性, 还可以将普通的 npm 模块导出为 loader, 做法是在 package.json 里定义一个 loader 字段
  • 插件(plugin)可以为 loader 带来更多特性
  • loader 能够产生额外的任意文件

css-loader

可以使用 loader 告诉 webpack 加载 CSS 文件

先安装对应的loader

1
npm install -D css-loader

然后配置 webpack.config.js 文件, 告诉 webpack 对每个 .css 使用 css-loader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: true
}
]
}
]
}
}

入口(entry)

入口起点(entry point)指示 webpack 应该使用哪个模块, 来作为构建其内部依赖图的开始. 进入入口后, webpack 会找出有哪些模块和库是入口起点(直接和简介)依赖的

属性

  • entery: String|Array

CODE

entry 属性的单个入口语法

1
2
3
4
5
6
7
8
9
10
// 简写
module.exports = {
entry: './src/index.js'
}
// 非简写
module.exports = {
entry: {
main: './src/index.js'
}
}

entry 多入口写法 - 对象写法:

用法: `entry: {[entruChunkName: string]: string|Array}

1
2
3
4
5
6
7
const config = {
entry: {
app: './src/app.js',
vendors: './src/vendors.js'
}
}
module.exports = config

可应用于多页面程序

出口(output)

output属性告诉 webpack 在哪里输出它所创建的 bundles, 以及如何命名这些文件, 默认值为 ./dist. 整个应用程序结构, 都会被编译到指定的输出路径的文件夹中,

属性

  • output: Object output 选项可以控制 webpack 如何向硬盘写入编译文件
    • filename: String 打包后的文件名
    • path: String 打包输出路径

CODE

单入口

1
2
3
4
5
6
7
8
9
// 借助 NodeJS 的 path 核心模块
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
}

多个入口

1
2
3
4
5
6
7
8
9
10
11
const config = {
entry: {
app: './src/app.js',
search: './src/search.js'
},
output: {
filename: '[name].js',
path: __dirname + '/dist'
}
}
// 写入到硬盘:./dist/app.js, ./dist/search.js

高级进阶

使用 CDN 和资源 hash 的复杂示例

1
2
3
4
output: {
path: '/home/proj/cnd/assets/[hash]',
publicPath: "http://cdn.example.com/assets/[hash]/"
}

在编译时不知道最终输出文件的 publicPath 的情况下,publicPath 可以留空,并且在入口起点文件运行时动态设置。如果你在编译时不知道 publicPath,你可以先忽略它,并且在入口起点设置 webpack_public_path

1
__webpack_public_path__ = myRuntimePublicPath

前提: 请确保安装了 Node.js 的最新版本

webpack

webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler) 当 webpack 处理应用程序时, 它会递归地构建一个依赖关系图(dependencygraph) 其中包含应用程序需要的每个模块, 然后将所有这些模块打包成一个或多个bundle

初始化项目

1
2
3
mkdir webpack-dmeo # 创建项目文件夹
cd webpack-demo # 进入项目目录
npm init -y # 默认配置初始化

出现 package.json 信息, 表示初始化成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// package.json
{
"name": "webpack-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11"
}
}

两种安装

全局安装

1
npm install -g webpack

本地安装

1
2
3
4
5
# 安装最新版本
npm install --save-dev webpack

# 如果使用 weboack4+ 版本 还需要安装 CLI
npm install --save-dev webpack-cli

webpack 命令使用方式

项目目录运行

1
2
3
# webpack-demo 目录中
./node_modules/.bin/webpack --sersion
# ---> 4.42.1

NPM script命令运行

添加 script 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"name": "webpack-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"build": {
"webpack": "webpack --version"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11"
}
}
1
2
npm run build
# ---> 4.42.1

全局运行

1
2
3
# 需要全局安装webpack
webpack --version
# ---> 4.42.1

实现原理

方法 jQuery.builfFragment(args, nodes, scripts)先创建一个文档片段 DocumentFragment, 然后调用方法 jQuery.clean(elems, context, fragment, scripts)将 HTML 代码转换为 DOM 元素, 并存储在创建的文档片段中

文档片段 DocumentFragment 表示文档的一部分, 但不属于文档树. 当把 DocumentFragment 插入文档树时, 插入的不是 DocumentFragment 自身, 而是他的所有子孙节点, 即可以一次向文档树中插入多个节点. 当需要插入大量节点时, 相比逐个插入节点, 使用 ocumentFragment 一次插入多个节点, 性能的提升会非常明显

如果 HTML 代码符合缓存条件, 方法 jQuery.buildFragment()还会把转换后的 DOM 元素缓存起来, 下次转换相同的代码时, 直接从缓存中读取, 不需要重复转换

方法执行步骤

jQuery.buildFragment(args, nodes, scripts)执行步骤:

  1. 如果 HTML 代码符合缓存条件, 则尝试从缓存对象 jQuery.fragments 中读取缓存的 DOM 元素
  2. 创建文档片段 DocumentFragment
  3. 调用方法 jQuery.clean(elems, context, Fragment, scripts) 将 HTML 代码转换为 DOM 元素, 并存储在创建的文档片段中
  4. 如果 HTML 代码符合缓存条件,则把转换后的 DOM 元素防褥缓存对象 jQuery.Fragments
  5. 最后返回文档片段和缓存状态 { fragment: fragment, cacheable: cacheable }

jQuery.buildFragment() 方法源码

方法定义

1
jQuery.buildFragment(args, nodes, scripts) {}

参数:

  • ages: 数组, 含有待转换的 DOM 元素的 HTML 代码
  • nodes: 数组, 含有文档对象、jQuery 对象或 DOM 元素, 用于修正创建文档片段 DocumentFragment 的文档对象
  • scripts: 数组, 用于存放 HTML 代码中的 script 元素. 方法 jQuery.Fragment() 会把该参数传给方法 jQuery.clean(), 后者把 HTML 代码转换为 DOM 元素后, 会提取其中的 script 元素并存入数组 scrtips

定义局部变量, 修正文档对象 doc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var Fragment, // 指向稍后可能创建的文档片段 Document Fragment
eacheable, // 表示 HTML 代码 是否符合缓存条件
eacheresults, // 指向从缓存对象 jQuery.ftagment 中提取到的文档片段 包含了缓存的DOM元素
doc, // 表示创建文档片段的文档对象
fitst = args[0]

/**
* 修正文档对象 doc
* nodes 可能包含一个明确的文档对象, 也可能包含jQuery对象或DOM元素
* 先尝试读取nodes[0]的属性 ownerDocuemt 并赋值给doc
* 如果不存在 则假定nodes[0]为文档对象并赋值给doc
*/
if (nodes && nodes[0]) {
doc = nodes[0].ownerDocument || node[0]
}

// 检查传入参数对象是不是文档对象 如果不是文档对象 而是 JavaScript 对象 就修正 doc 为当前文档对象 document
if (!doc.createDocumentFragment) {
doc = document
}

尝试从缓存对象 jQuery.fragments 中读取缓存的 DOM 元素

如果 HTML 代码符合缓存条件, 则尝试从缓存对象 jQuery.fragments 中读取缓存的 DOM 元素

  • 数组 args 长度为 1, 且第一个元素是字符串, 即数组 args 中只有一段 HTML 代码
  • HTML 代码长度小于 512, 否则可能会导致缓存占用内存过大
  • 文档对象 doc 是当前文档对象, 即之缓存为当前文档创建的 DOM 元素
  • HTML 代码已左尖括号开头, 即只缓存 DOM 元素, 不缓存文本节点
  • HTML 代码中不能含有 <script> <object> <embed> <option> <style>标签
  • 当前浏览器可以正确的复制单选按钮核复选框的选中状态 checked, 或者 HTML 代码中的单选按钮和复选按钮没有被选中
  • 当前浏览器可以正确的赋值 HTML5 元素, 或者 HTML 代码中不含有 HTML5 标签
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if(args.length === 1
&& typeof first === 'string'
&& first.length < 512
&& doc === document
&& first.charAt(0) === '<'
&& !rnocache.test(first)
&& (jQuery.support.checkClone || !rchecked.test(first))
&& (jQuery.support.html5Clone || !rnshimcache.text(first)){

cacheable = true; // 如果遍历为 true 必须先赋值一份再使用

/**
* 尝试从缓存对象jQuery.fragment中读取缓存的DOM元素
*/
cacheresults = jQuery.fragments[first];
if (cacheresults && cacheresults !== 1) {
fragment = cacheresults;
}
}

jQuery.fragments = {}

转换 HTML 代码为 DOM 元素

先创建一个文档片段 DocumentFragment, 然后调用方法 jQuery.clean(elems, context, fragment, scripts) 将 HTML 代码转换为 DOM 元素, 并存储在创建的文档片段中

1
2
3
4
5
6
7
// 如果为 true 表示需要直型转换过程
if (!fragment) {
// 调用原生方法 创建文档片段
fragment = doc.createDocumentFragment()
// 将片段转换为 DOM 元素
jQuery.clean(args, doc, fragment, scripts)
}

把转换后的 DOM 元素防褥缓存对象 jQuery.fragments

如果 HTML 代码符合缓存条件, 则把转换后的 DOM 元素放入缓存对象 jQuery.fragment 中

1
2
3
4
// 如果符合缓存条件 就缓存
if (cacheable) {
jQuery.fragments[first] = cacheresults ? fragment : 1
}

返回文档片段和缓存状态

1
2
3
4
5
6
7
8
/*
返回一个包含了文档片段 fragment 和缓存状态 cacheable 的对象
fragment 中包含了转换后的 DOM 元素, 缓存状态 cacheable 则指示了如果使用这些 DOM 元素
*/
return {
fragment: fragment,
cacheable: cacheable,
}

总结

1

jQuery.fn.init(selector, context, rootjQuery)

构造函数 jQuery.fn.init() 负责解析参数 selector 和 context 的类型, 并执行相应的逻辑, 最后返回 jQuery.fn.init() 的实例.

解析参数 selector 和 context 公有 12 个有效分支

jQuery.fn.init

源码分析

定义 jQuery.fn.init(selector, context, rootjQuery)

1
2
3
4
5
6
jQuery.fn = jQuery.prototype = {
constructor: jQuery,
init: function (selector, context, rootjQuery) {
var match, elem, ret, doc
},
}

init() 构造函数定义中接受三个参数 selector、context、rootjQuery

  • selector: 可以是任意类型的值, 但只有 undefined、DOM 元素、字符串、函数、jQuery 对象、普通 JavaScript 对象这几种类型有效
  • context: 可以不传入, 或者传入 DOM 元素、jQuery 对象、普通 JavaScript 对象之一
  • rootjQuery: 包含了 document 对象的 jQuery 对象, 用于 document.getElementById()查找失败、selector 是选择器表达式且未指定 context、selector 是函数的情况.

rootjQuery 定义与应用场景

1
2
3
4
5
6
7
8
9
10
11
// document.getElementById() 查找失败
return rootjQuery.find(selector)

// slector 是选择器且未指定 context
return (context || rootjQuery).find(selector)

// selector 是函数
return rootjQuery.ready(selector)

// rootjQuery 定义
rootjQuery = jQuery(doucment)

参数 selector 可以转换为 false

参数 selector 可以转换为 false, 例如: undefined、空字符串、null 等, 则直接返回 this, 此时 this 是空 jQuery 对象, 其属性 length 等于 0.

1
2
3
4
// Handle $('')、$(null) or $(undefined)
if (!selector) {
return this
}

参数 selector 是 DOM 元素

如果参数 selector 有属性 nodeType, 则认为 selector 是 DOM 元素, 手动设置第一个元素和属性 context 指向该 DOM 元素、属性 length 为 1, 然后返回包含了该 DOM 元素引用的 jQuery 对象

1
2
3
4
5
6
// Handle $(DOMELement)
if (selector.nodeType) {
this.context = this[0] = selector
this.length = 1
return this
}

参数 selector 是字符串 ‘body’

如果参数 selector 是字符串 “body”, 手动设置属性 context 指向 document 对象、第一个元素指向 body 元素、属性 length 为 1, 最后返回包含了 body 元素引用的 jQuery 对象

1
2
3
4
5
6
7
8
// The body element only exists once, optimize finding it
if (selector === 'body' && !context && document.body) {
this.context = document
this[0] = document.body
this.selector = selector
this.length = 1
return this
}

参数 selector 是其他字符串

如果参数 selector 是其他字符串, 则先检测 selector 是 HTML 代码还是 #id.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 处理参数是字符串
if (typeof selector === 'string') {
// 判断是 HTML 代码 还是 #ID
if (
selector.charAt(0) === '<' &&
selector.charAt(selector.length - 1) === '>' &&
selector.length >= 3
) {
// 如果是以 `<` 开头 `>` 结尾的 并且长度大于等于 3 跳过 正则 检查
match = [null, selector, mull]
} else {
/**
* 使用正则检查 quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/
* 依次匹配 HTML 代码和 id 如果匹配成功 match 数组第一个元素为参数 selector, 第二个元素为匹配的 HTML 代码或 undefined 第三个元素为匹配的 id 或 undefined
* quickExpr.exec('#target'); // ["#target",undefined,"target"]
* quickExpr.exec('<div>'); // ["<div>","<div>",undefined]
* quickExpr.exec('abc<div>'); // ["abc<div>","<div>",undefined]
* quickExpr.exec('abc<div>abc#id'); // ["abc<div>abc#id","<div>",undefined]
* quickExpr.exec('div'); // null
* quickExpr.exec('<div><img></div>'); // ["<div><img></div>","<div><img></div>",undefined]
*/
match = quickExpr.exec(selector)
}
}

如果 selector 是单独标签

如果 selector 是单独标签, 就调用 document.createElement() 创建标签对应的 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
/**
* 检查正则匹配结果
* 如果 match[1] 不是 undefined 即 selector 是 HTML 代码 或者 match[2] 不是 undefined, 即参数 selector 是 #id 并且未传入 context
*/
if (match && (match[1] || !context)) {
// HANDLE: $(html) -> $(array)
if (match[1]) {
context = context instanceof jQuery ? context[0] : context

doc = context ? context.ownerDocument || context : document

// 如果传入单个字符 并且是个单标签 直接创建 跳过其他
// rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,
ret = rsingleTag.exec(selector)

if (ret) {
// jQuery.isPlainObject() 用于检测对象是否是纯粹的对象 即用对象直接量 {} 或者 new Object() 创建的对象
if (jQuery.isPlainObject(context)) {
selector = [document.createElement(ret[1])]
// attr() 遇到特殊属性和事件类型时会执行同名 jQuery 方法
jQuery.fn.attr.call(selector, context, true)
} else {
selector = [doc.createElement(res[1])]
}
}
}
}

之所以把创建的 DOM 源码放入数组中, 是为了后面方便的调用 jQuery.merge() 方法, 此方法用于合并两个数组的元素到一个数组

参数 selector 是复杂 HTML 代码

如果参数 selector 是复杂 HTML 代码, 则利用浏览器 innerHTML 创建 DOM 元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
else {
/**
* 创建过程由 buildFragment 和 clean 实现
* buildFragment 返回 -> { fragment: 含有转换后的 DOM 元素的文档片段 , cacheable: HTML代码是否满足缓存条件}
*/
ret = jQuery.buildFragment([ match[1] ], [ doc ])
/**
* 如果满足缓存条件, 在使用前 先复制一份
*/
selector = (ret.cacheable ? jQuery.clone(ret.fragment): rest.fragment).childNodes
}

// 将创建的 DOM 元素 合并到当前 jQuery 对象中返回
return jQuery.merage(this, selector)
}

如果参数 selector 是 #id, 且未指定参数 context

如果参数 selector 是 #id, 且未指定参数 context, 则调用 document.getElementById() 查找含有指定 id 属性的 DOM 元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
// 查找含有指定 id 属性的 DOM 元素
elem = document.getElementById(mathc[2])

if (elem && elem.parentNode) {
// 检查 id 是否相等
if (elem.id !== match[2]) {
return rootjQuery.find(selector)
}

this.length = 1
this[0] = elem
}

this.context = document
this.selector = selector
return this
}

参数 selector 是选择器表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// HANDLE: $(expr, $(...))
} else if (!context || context.jquery) {
/**
* 如果执行了上下文 且上下文是 jQuery 对象 执行 context.find() 方法
* 如果没有指定上下文 就执行 rootjQuery.find()
*/
return (context || rootjQuery).find(selector);

// HANDLE: $(expr, context)
// (which is just equivalent to: $(context).find(expr)
} else {
// 如果指定了上下文 但上下文不是jQuery对象 则执行方法先创建一个包含 context 的 jQuery 对象, 然后调用 find() 方法
return this.constructor(context).find(selector);
}

参数 selector 是函数

1
2
3
4
// 如果是函数 就绑定 ready 事件
} else if (jQuery.isFunction(selector)){
return rootjQuery.ready(selector)
}

如果参数 selector 是 jQuery 对象

1
2
3
4
5
6
// 如果参数包含 selector 就认为是 jQuery 对象
if (selector.selector !== undefined) {
// 复制他的属性 selector 和 context
this.selector = selector.selector
this.context = selector.context
}

参数 selector 是任意其他值

1
2
3
4
5
6
7
/**
* 如果 selector 是数组或伪数组, 则添加到当前 jQuery 对象中
* 如果 selector 是 JavaScript 对象, 则作为第一个元素放入当前 jQuery 对象中
* 如果是其他类型的值, 则作为第一个元素放入当前 jQuery 对象中
* 最后返回当前 jQuery 对象
*/
return jQuery.makeArray(selector, this)

总览

jQuery.fn.init_

构造 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
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
* 自调用匿名函数
* */
(function(window, undefined) {
// 构造 jQuery
var jQuery = (function() {
var jQuery = function(selector, context) {
/**
* 返回 jQuery.fn.init 的实例
*/
return new jQuery.fn.init(selector, context, rootjQuery);
};

// ... 定义一堆变量


/**
* 覆盖构造函数 jQuery() 的原型对象
*/
jQuery.fn = jQuery.protottype = {
/**
* 覆盖原型对象的属性 constructor
*/
constructor: jQuery,
/**
* 定义原型方法 负责解析 selector 和 context 的类型并执行相应的查找
*/
init: function(selector, context, rootjQuery) {
// ... 一堆原型属性和方法
}
};

/**
* 用 jQuery 构造函数的原型对象 jQuery.fn 覆盖 jQuery.fn.init() 的原型对象
*/
jQuery.fn.init.prototype = jQuery.fn;

/**
* 合并两个或多个对象的属性到第一个对象
*/
jQuery.extend = jQuery.fn.extend = function() { ... }

/**
* 执行 jQuery.extend() 在jQuery构造器上定义了一堆静态属性和方法
*/
jQuery.extend({
// 一堆静态属性和方法
})

/**
* 返回 jQuery 构造函数并赋值给外层变量 jQuery
*/
return jQuery;
})();

/**
* 把 jQuery 、 $ 暴露给全局作用域 window
*/
window.$ = window.jQuery = jQuery;
})(window);

为什么要在构造函数 jQuery()内部用运算符 new 创建并返回另一个构造函数的实例

如果构造函数有返回值, 运算符 new 所创建的对象会被丢弃, 返回值将作为 new 表达式的值

jQuery 利用这一特性, 通过在构造函数 jQuery() 内部用运算符 new 创建并返回另一个构造函数的实例, 省去构造函数 jQuery() 前面的运算符 new

为什么执行 jQuery.fn = jQuery.prototype, 设置 jQuery.fn 指向构造函数 jQuery() 的原型对象 jQuery.prototype

jQuery.fn 是 jQuery.prototype 的简写, 方便拼写

jQuery() 构造函数返回的 jQuery 对象实际是 jQuery.fn.init() 的实例, 为什么能够在 jQuery.fn.init() 的实例上调用构造函数 jQuery()的原型方法和属性

用 jQuery()构造函数的原型对象覆盖了构造函数 jQuery.fn.init()的原型对象, 从而使得 jQuery.fn.init() 的实例也可以访问 jQuery()构造函数的原型方法核属性

1
jQuery.fn.init.prototype = jQuery.fn;

为什么在构造 jQuery()构造函数的时候, 要包裹在一个自调用匿名函数中

在自调用安徽省农户中定义了很多其他局部变量, 这些局部变量只在 jQuery 对象内部使用, 使用自调用匿名函数可以减少与其他模块的耦合度

为什么要覆盖构造函数 jQuery()的原型对象 jQuery.prototype

在原型对象 jQuery.prototype 上定义的属性和方法会被所有 jQuery 对象继承,可以有效减少每个 jQuery 对象所需的内存。事实上,jQuery 对象只包含 5 种非继承属性,其余都继承自原型对象 jQuery.prototype;在构造函数 jQuery.fn.init()中设置了整型属性、length、selector、context;在原型方法.pushStack()中设置了 prevObject。因此,也不必因为 jQuery 对象带有太多的属性和方法而担心会占用太多的内存。

构造函数 jQuery

构造函数 jQuery 有七种用法:

  • jQuery(select [,context]): 接受一个 CSS 选择器表达式和可选的选择器上下文, 返回一个包含了匹配的 DOM 元素的 jQuery 对象
  • jQuery(html,[, ownerDocument])、jQuery(html, props): 用提供的 HTML 代码创建 DOM 元素
  • jQuery(element)、jQuery(elementArray): 封装 DOM 元素为 jQuery 对象
  • jQuery(object): 封装普通对象为 jQuery 对象
  • jQuery(callback): 绑定 ready 事件监听函数, 当 DOM 结构加载完成时执行
  • jQuery(jQuery object): 接受一个 jQuery 对象, 返回该 jQuery 对象的拷贝副本
  • jQuery(): 创建一个 jQuery 对象

jQuery(select [,context])

如果传入一个字符串参数, jQuery 会检查这个字符串是选择器表达式还是 HTML 代码. 如果是选择器表达式, 则遍历文档, 查找与之匹配的 DOM 元素, 并创建一个包含了这些 DOM 元素引用的 jQuery 对象. 如果没有元素与之匹配, 则创建一个空 jQuery 对象, 其中不包含任何元素, 其属性 length 等于 0.

默认情况下, 对匹配元素的查找将从根元素 document 对象开始, 即查找范围是整个文档树, 不过也可以传入第二个参数 context 来限定查找范围

如果选择器表达式 selector 是简单的 #id, 且没有指定上下文 context, 则调用浏览器原生方法 document.getElementById() 查找属性 id 等于指定值的元素; 如果是比 #id 复杂的选择器表达式或指定了上下文, 则通过 jQuery 方法 .find() 查找, 因此 $('span', this) 等价于 $(this).find('span')

1
2
3
4
5
6
$('#app').click(function() {
// $('span', this).html(222);
$(this)
.find('span')
.html('find');
});

jQuery(html [,ownerDocument])、jQuery(html, props)

第一个参数

如果传入的字符串参数看起来像一段 HTML 代码, jQuery 则尝试用这段 HTML 代码创建新的 DOM 元素, 并创建一个包含了这些 DOM 元素引用的 jQuery 对象.

1
$('<button>CLICK ME</button>').appendTo('body');

如果 HTML 代码是一个单独标签, jQuery 会使用浏览器原生方法 document.createElement() 创建 DOM 元素

如果是多层嵌套 HTML 片段, 则利用浏览器的 innerHTML 机制创建 DOM 元素, 这个过程由 jQuery.buildFragment()jQuery.clean() 实现

第二个参数

ownerDocument 用于指定创建新 DOM 元素的文档对象, 如果不传入, 则默认为当前文档对象

如果 HTML 代码是一个单独标签, 那么第二个参数还可以是 props, props 是一个包含了属性、事件的普通对象; 在调用 document.createElement() 创建 DOM 元素后, 参数 props 会被传给 jQuery 方法 .attr(), 然后由 .attr() 负责把 props 中的属性、事件设置到新穿创建的 DOM 元素上

参数 props 的属性可以是任意的事件类型(如”click”),此时属性值应该是事件监听函数,它将被绑定到新创建的 DOM 元素上;参数 props 可以含有以下特殊属性:val、css、html、text、data、width、height、offset,相应的 jQuery 方法:.val().css().html().text().data().width().height()、`.offset()将被执行,并且属性值会作为参数传入;其他类型的属性则会被设置到新创建的 DOM 元素上,某些特殊属性还会做跨浏览器兼容(如 type、value、tabindex 等);可以通过属性名 class 设置类样式,但要用引号把 class 包裹起来,因为 class 是 JavaScript 保留字。

1
2
3
4
5
6
7
$('<div />', {
text: '哈哈',
class: 'active',
click: function() {
console.log((this.innerHTML = '嘻嘻'));
}
}).appendTo('body');

jQuery(element)、jQuery(elementArray)

如果传入一个 DOM 元素或 DOM 元素数组, 则把 DOM 元素封装到 jQuery 对象中并返回

这个功能常见于事件监听函数, 即把关键字 this 因用那个的 DOM 元素封装为 jQuery 对象, 然后在该 jQuery 对象上调用 jQuery 方法

1
2
3
$('div').click(function() {
$(this).slideUp();
});

jQuery(object)

如果传入一个普通 JavaScript 对象, 则把该对象封装到 jQuery 对象中并返回

这个功能可以方便的在普通 JavaScript 对象上实现自定义事件的绑定和触发

1
2
3
4
5
var $foo = $({ foo: 'bar', hello: 'worldd' });
$foo.on('custom', function() {
console.log(this.foo);
});
$foo.trigger('custom'); // -> foo

jQuery(callback)

如果传入一个函数, 则在 document 上绑定一个 ready 事件监听函数, 当 DOM 结构加载完成时执行. ready 事件的触发要早于 load 事件.

ready 事件并不是浏览器原生事件, 而是 DOMContentLoaded 事件、onreadystatechange 事件和函数 doScrollCheck() 的统称

1
2
3
$(function() {
console.log('DOM 加载完毕');
});

jQuery(jQuery object)

如果传入一个 jQuery 对象, 则创建该 jQuery 对象的一个副本并返回, 副本与传入的 jQuery 对象引用完全相同的 DOM 元素

1
2
var $app = $('#app');
$app.html('xuxu'); // -> $('#app').html('xuxu')

jQuery()

如果不传入任何参数, 则返回一个空的 jQuery 对象, 属性 length 为 0.

这个功能可以用来复用 jQuery 对象

总体架构

jQuery 的模块总体分为三大部分:

  • 入口模块(构造 jQuery 对象模块)
  • 功能模块
  • 底层支持模块

模块导图

入口模块

入口模块主要功能是构造 jQuery 对象, 如果在调用构造函数 jQuery() 创建 jQuery 对象时传入了选择器表达式, 则会调用功能模块中的选择器 Sizzle 遍历文档, 查找与之匹配的 DOM 元素, 并创建一个包含了这些 DOM 元素引用的 jQuery 对象

功能模块

在功能模块中, 事件系统提供了统一的事件绑定、响应、手动触发和移除机制, 它并没有将事件直接绑定到 DOM 元素上, 而是基于数据缓存模块来管理事件

Ajax 模块

Ajax 模块允许从服务器上加载数据, 而不用刷新页面, 它基于异步队列模块来管理核触发回调函数

动画模块

动画模块用于向网页中添加动画效果, 它基于队列模块来管理和执行动画函数

属性操作模块

属性操作模块时用于对 HTML 属性和 DOM 属性进行读取、设置和移除操作

DOM 遍历模块

DOM 遍历模块用于在 DOM 树中遍历父元素、子元素和兄弟元素

DOM 操作模块

DOM 操作模块用于插入、移除、复制和替换 DOM 元素

样式操作模块

样式操作模块用于获取计算样式或设置内联样式

坐标模块

坐标模块用于读取或设置 DOM 元素的文档坐标

尺寸模块

尺寸模块用于获取 DOM 元素的高度和宽度

底层支持模块

底层支持模块又分为诸多子模块:

  • 工具方法: Utilities
  • 回调函数列表: Callbacks Object
  • 异步队列: Deferred Object
  • 浏览器功能测试: Support
  • 数据缓存: Data
  • 队列: Queue
  • 选择器: Sizzle

工具方法模块

工具方法模块提供了一些编程辅助方法, 用于简化对 jQuery 对象、DOM 元素、数组、对象、字符串等的操作, 其他模块都会用到工具方法模块

回调函数列表模块

回调函数列表模块用于增强对回调函数的管理,支持添加、移除、触发、锁定、禁用回调函数等功能;

异步队列模块

异步队列模块用于解耦异步任务和回调函数, 他再回调列表的基础上为回调函数增加了状态, 并提供多个回调函数列表, 支持传播任意同步或异步回调函数的成功或失败状态

浏览器功能测试模块

浏览器功能测试模块提供了针对不同浏览器功能和 bug 的测试结果, 其他模块基于这些测试结果来解决浏览器之间的兼容性问题

数据缓存模块

数据缓存模块用于为 DOM 元素和 JavaScript 对象附加任意类型的数据

队列模块

队列模块用于管理一组函数, 支持函数的入队核出队操作, 并确保函数按顺序执行, 它基于数据缓存模块实现

选择器模块

选择器 sizzle 是一个纯 JavaScript 实现的 CSS 选择器引擎, 用于查找与选择表达式匹配的元素集合

源码结构

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
(function(window, undefined) {
var jQuery = (
var jQuery = (function(selector, context) {
return new jQuery.fn.init(selector, context, rootjQuery)
})()
return jQuery
)()

// 工具方法

// 回调函数列表

// 异步队列

// 浏览器功能测试

// 数据缓存

// 属性操作

// 事件系统

// 选择器

// DOM 遍历

// DOM 操作

// 样式操作 CSS

// 异步请求

// 动画

// 坐标
window.jQuery = window.$ = jQuery
})(window)