前端学习之路


  • 首页

  • 归档

  • 分类

  • 标签

  • 搜索
close
小朱

小朱

前端学习之路

168 日志
37 分类
37 标签
RSS
GitHub
友情链接
  • 极客学院
  • caniuse
  • codepen
  • JS Bin
  • Babel在线编译
  • Iconfinder图标
  • 在线JSON格式化
  • 智能图像压缩

Lodash 之 debounce

发表于 2018-01-26   |   分类于 JavaScript

了解 Throttling(防抖) 和 Debouncing(节流)

参考:
The Difference Between Throttling and Debouncing
Debouncing and Throttling Explained Through Examples

应用场景:一个典型的应用场景是浏览器窗口中 scrolling 和 resizing,如设置了滚动的监听函数,在滚动 5000px的时候可能会触发 100 次以上的监听事件,如果监听事件做了大量计算或操作很多 DOM 元素,可能就会遇到性能问题。即时搜索也有同样的问题。

相同点:它们是为了解决性能问题而限制基于 DOM 事件的 JavaScript 的执行次数的两种方式,这是在事件和函数执行之间加的控制,因为 DOM 事件的触发频率是无法控制的。

不同点:Throttling 是限制一个函数能够被执行的最大时间间隔,保证了函数至少每隔 X 毫秒会被调用一次,如每隔 100ms 执行一次函数。Debouncing 是限制一个函数距上次调用达到一定时间间隔才会被再次调用,相当于连续的事件被分成了一组,只触发一次函数调用,如距上次调用达到 100ms 才会再次执行。

了解 requestAnimationFrame

window.requestAnimationFrame(callback) 方法告诉浏览器执行动画并请求浏览器在下一次重绘之前调用函数 callback 来更新动画,返回一个 long 整数的 ID,可以通过传此值到 window.cancelAnimationFrame() 来取消回调函数的执行,注意只是在下一次重绘时调用回调函数。

requestAnimationFrame 的优势,在于充分利用显示器的刷新机制,比较节省系统资源。显示器有固定的刷新频率(60Hz 或 75Hz),requestAnimationFrame 的基本思想就是与这个刷新频率保持同步,利用这个刷新频率进行页面重绘。此外,使用这个API,一旦页面不处于浏览器的当前标签,就会自动停止刷新,这就节省了CPU、GPU和电力。注意 requestAnimationFrame 是在主线程上完成,这也意味着,如果主线程非常繁忙,requestAnimationFrame 的动画效果会大打折扣。

requestAnimationFrame 是限制函数执行次数的另一种方式,可以被认为是 _.throttle(dosomething, 16),但是是高保真的,会针对不同设备本身的性能而更精确一些,浏览器内部决定渲染的最佳时机,它可以作为 throttle 的替换。

如果浏览器标签不是激活状态,就不会被执行,虽然对滚动、鼠标或键盘事件没有影响。还有需要考虑浏览器兼容性,node.js 中也没有提供该 API。

最佳实践:使用 requestAnimationFrame 进行重新绘制、计算元素位置或直接改变属性的操作,使用 _.debounce 或 _.throttle 进行 Ajax 请求或添加、移除 class(可以触发 CSS 动画),这时可以设置一个低一些的频率,如 200ms。

lodash 之 debounce 源码

这里不再描述 throttle 了,其实 throttle 就是设置了 maxWait 的 debounce,lodash 源码中对 throttle 的实现就是调用了 wait 和 maxWait 相等的 debounce。

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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
/*
* root 为全局变量,浏览器下为 window,node.js 下为 global
* isObject 函数判断传入参数是否为一个对象
* 创建一个 debounced 函数并返回,该函数延迟 func 在距离上一次调用达到 wait 时间之后再执行,如果在这期间内又调用了函数则将取消前一次并重新计* 算时间
* options.leading 函数在每个等待延迟的开始被调用
* options.trailing 函数在每个等待延迟的结束被调用
* options.maxWait 函数被调用的最大等待时间,实现 throttle 效果,保证大于一定时间后一定能执行
* 如果 leading 和 trailing 都设置为 true 了,只有函数在 wait 时间内被执行一次以上才会执行 trailing
*/
function debounce(func, wait, options) {
// 变量初始化
let lastArgs,
lastThis,
maxWait,
result,
timerId,
lastCallTime;
let lastInvokeTime = 0;
let leading = false;
let maxing = false;
let trailing = true;
// 如果 wait = NaN 并且当前是浏览器环境 requestAnimationFrame 存在时,返回 true
const userRAF = (!wait && wait !== 0 && typeof root.requestAnimationFrame === 'function');
// 传入参数的验证
if (typeof func != 'function') {
throw new TypeError('Expected a function');
}
wait = +wait || 0; // 将传入的 wait 转为数字,如果没有传入值默认赋值为 0
if (isObject(options)) {
leading = !!options.leading;
maxing = 'maxWait' in options;
// maxWait 为设置的 maxWait 值和 wait 值中最大的,因为如果 maxWait 小于 wait,debounce 就失效了,相当于只有 throttle 了
maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait;
trailing = 'trailing' in options ? !!options.trailing : trailing;
}
function invokeFunc(time) {
// 进入 debounced 函数时对 lastArgs、lastThis 进行的赋值,在这里执行完函数后,对 lastArgs、lastThis 进行了重置
// 个人认为这样做的原因,是保证通过计时的方式执行函数最多只能执行一次
const args = lastArgs;
const thisArg = lastThis;
lastArgs = lastThis = undefined;
lastInvokeTime = time;
result = func.apply(thisArg, args);
return result;
}
function startTimer(pendingFunc, wait) {
if (userRAF) {
return root.requestAnimationFrame(pendingFunc);
}
return setTimeout(pendingFunc, wait);
}
function cancelTimer(id) {
if (userRAF) {
return root.cancelAnimationFrame(id);
}
clearTimeout(id);
}
function leadingEdge(time) {
// TODO 不明白为什么这里需要更新 lastInvokeTime,进入 leadingEdge 函数不一定会真的触发函数的执行
lastInvokeTime = time;
// 为 trailingEdge 触发函数调用设置定时器
timerId = startTimer(timerExpired, wait);
// 如果 leading 为 true,会触发函数执行,否则返回上一次执行结果
return leading ? invokeFunc(time) : result;
}
// 主要作用就是触发 trailingEdge
function timerExpired() {
const time = Date.now();
// 在 trailingEdge 且时间符合条件时,调用 trailingEdge函数,否则重启定时器
if (shouldInvoke(time)) {
return trailingEdge(time);
}
// 重启定时器,保证下一次时延的末尾触发
timerId = startTimer(timerExpired, remainingWait(time));
}
function remainingWait(time) {
// 距离上次函数被调用的时间
const timeSinceLastCall = time - lastCallTime;
// 距离上次函数被执行的时间
const timeSinceLastInvoke = time - lastInvokeTime;
// wait - timeSinceLastCall 为距离下一次 trailing 的位置
const timeWaiting = wait - timeSinceLastCall;
// maxWait - timeSinceLastInvoke 为距离下一次 maxing 的位置
// 有maxing:比较出下一次 maxing 和下一次 trailing 的最小值,作为下一次函数要执行的时间
// 无maxing:在下一次 trailing 时执行 timerExpired
return maxing ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke) : timeWaiting;
}
function trailingEdge(time) {
timerId = undefined;
// 有 lastArgs 才执行,意味着只有 func 已经被 debounced 过一次,也就是被调用过一次,以后才会在 trailingEdge 执行
if (trailing && lastArgs) {
return invokeFunc(time);
}
// 每次 trailingEdge 都会清除 lastArgs 和 lastThis,目的是避免最后一次函数被执行了两次
// 举个例子:最后一次函数执行的时候,可能恰巧是前一次的 trailing edge,函数被调用,而这个函数又需要在自己时延的 trailing edge 触发,导致触发多次
lastArgs = lastThis = undefined;
return result;
}
function shouldInvoke(time) {
const timeSinceLastCall = time - lastCallTime;
const timeSinceLastInvoke = time - lastInvokeTime;
return (
lastCallTime === undefined // 第一次调用
|| (timeSinceLastCall >= wait) // 距离上次被调用已经超过 wait
|| (timeSinceLastCall < 0) //系统时间倒退
|| (maxing && timeSinceLastInvoke >= maxWait) //超过最大等待时间
);
}
// 取消函数延迟执行
function cancel() {
if (timerId !== undefined) {
cancelTimer(timerId);
}
lastInvokeTime = 0;
lastArgs = lastCallTime = lastThis = timerId = undefined;
}
// 触发函数立即执行
function flush() {
// 如果前面没有定时任务在执行,也就是没有前面没有调用过函数,返回最后一次执行的结果,否则才会触发一次函数执行
return timerId === undefined ? result : trailingEdge(Date.now());
}
// 检查当前是否在计时中
function pending() {
return timerId !== undefined;
}
// 返回的控制函数真正调用频率的函数
function debounced(...args) {
const time = Date.now();
const isInvoking = shouldInvoke(time);
lastArgs = args;
lastThis = this;
// 更新上次函数调用时间
lastCallTime = time;
// 无 timerId 的情况有两种:1.首次调用 2.trailingEdge执行过函数
if (isInvoking) {
if (timerId === undefined) {
return leadingEdge(lastCallTime);
}
if (maxing) {
// Handle invocations in a tight loop.
timerId = startTimer(timerExpired, wait);
return invokeFunc(lastCallTime);
}
}
// 负责一种 case:trailing 为 true 的情况下,在前一个 wait 的 trailingEdge 已经执行了函数;
// 而这次函数被调用时 shouldInvoke 不满足条件,因此要设置定时器,在本次的 trailingEdge 保证函数被执行
if (timerId === undefined) {
timerId = startTimer(timerExpired, wait);
}
return result;
}
debounced.cancel = cancel;
debounced.flush = flush;
debounced.pending = pending;
return debounced;
}

Windows-开发环境配置

发表于 2017-11-14   |   分类于 开发环境

安装 Python

下载

安装教程

安装 Node

下载

安装教程

注意: 通篇看完一遍教程和下面注意事项后再进行安装。

安装路径:X:\Program Files\nodejs,X是你自己的盘符,其它路径名称不要改,可能出问题
执行完安装教程后,npm -v 查看版本号如果没有反映,请删除C:\Users{账户}\下的.npmrc文件
NODE_PATH 环境变量为:X:\Program Files\nodejs\node_global
path 环境变量为: X:\Program Files\nodejs\;F:\Program Files\nodejs\node_global;
X:\Program Files\nodejs\node_modules\npm 路径下,npmrc文件第二行添加 registry = https://registry.npm.taobao.org 配置淘宝镜像
npm 升级,执行 npm install npm -g
X:\Program Files\nodejs\node_global\node_modules\npm 路径下,npmrc文件第二行添加 registry = https://registry.npm.taobao.org

安装 Git

下载
安装教程

注意 Adjusting your PATH environment 选择 Run Git from the Windows Command Prompt.

安装 TortuiseGit

下载

Sublime Text 3

下载
注册码
安装Package Control

前端代码运行

npm install babel-cli -g

git clone http://xxx.git

cd xxx

npm install

npm update

npm start

后端代码运行

在 eclipse 工作空间执行:git clone http://yyy.git

解压 ide-setting.zip 到当前目录

将项目导入到 Eclipse 中

确定 Build Path 里没有问题,jdk 用1.7或1.8的

右键项目,Run As ——> Run on Server 进行项目启动,如果没起起来,看一下Server.xml中内容是否如下,最后将端口改为8088

注意: 数据库配置在 /WebRoot/WEB-INF/db.properties 中

Mac-开发环境配置

发表于 2017-11-14   |   分类于 开发环境

Web端环境

  • 安装 Homebrew

    /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

  • 安装 Node

    brew install node
    brew install nvm
    vim ~/.profile,添加以下命令

    1
    2
    export NVM_DIR=~/.nvm
    source $(brew --prefix nvm)/nvm.sh

    source ~/.profile

    1
    2
    3
    4
    $ nvm ls-remote 查看 所有的node可用版本
    $ nvm install xxx 下载你想要的版本
    $ nvm use xxx 使用指定版本的node
    $ nvm alias default xxx 每次启动终端都使用该版本的node
  • 安装 Yarn

    brew install yarn

  • 全局安装 babel-cli

    yarn global add babel-cli

  • 安装 Mongo

    brew install mongo

  • 安装 git

    brew install git

  • 注册服务并启动

    brew services start mongodb

  • 导入数据库文件 xx.ar

    cat xx.ar| mongorestore -h localhost --port 27017 --drop --gzip --archive -vvvv --stopOnError

  • 安装 Robo 3T,创建连接

  • 安装 Java

    1
    2
    3
    brew cask search java
    brew cask info java
    brew cask install java
  • 下载 elasticsearch解压

    1
    2
    cd elasticsearch
    ./bin/elasticsearch (这个窗口不能关)
  • 安装 VSCode,使用Setting Sync,安装插件

  • 在 VSCode 中,alt + command + p 打开命令面板,输入 shell command 找到:Install‘code’ command in PAH,即可在终端中通过 code . 打开所在目录

移动端环境

  • 安装工具

    1
    2
    3
    npm install -g yarn react-native-cli
    brew install watchman
    brew install flow
  • 安装 Xcode

    虽然一般来说命令行工具都是默认安装了,但你最好还是启动Xcode,并在Xcode -> Preferences -> Locations 菜单中检查一下是否装有选中的Command Line Tools。

  • 安装 Android Studio

    下载 SDK 1.8,注意不要使用 JDK 9.0,启动项目时会报错,如果安装了多个 JDK,要用如下指令指定使用哪个版本。
    export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home

    Android Studio 中,File -> Project Structure 中, SDK Location 中,JDK location 取消勾选“Use embedded JDK”,输入框中写入上面导出对应的路径,为 /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home。

    下载 Android Studio,按步骤安装,注意需要翻墙安装 SDK。

    如果启动项目报如图所示错误,修改 android/build.gradle 文件如下:

    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
    buildscript {
    repositories {
    /*
    解决图中所示错误
    */
    maven { url 'https://maven.google.com' }
    jcenter()
    }
    dependencies {
    classpath 'com.android.tools.build:gradle:3.0.0'
    }
    }
    allprojects {
    repositories {
    mavenLocal()
    jcenter()
    /*
    解决找不到
    import com.facebook.react.ReactApplication;
    import com.facebook.react.ReactNativeHost;
    */
    maven {
    url "$rootDir/../node_modules/react-native/android"
    }
    }
    }

Yarn 常用命令

发表于 2017-10-28   |   分类于 NPM

全局命令

  • yarn global <add|list|remove|upgrade> <package...> 全局安装
  • yarn global dir 查看 yarn 全局安装目录
  • yarn config get registry 查询当前配置的镜像
  • yarn config set registry http://registry.npm.taobao.org/ 设置镜像

以下yarn所有操作都会更新 package.json 和 yarn.lock 文件

  • yarn init [--yes|-Y]:创建项目,初始化包的开发环境
  • yarn add [package]@[version|tag]:添加依赖项,添加到dependencies
    • yarn add --dev|-D 添加到 devDependencies
    • yarn add --peer|-P 添加到 peerDependencies
    • yarn add --optional|-O 添加到 optionalDependencies
  • yarn upgrade [package]@[version|tag]:升级依赖项
  • yarn remove [package]:移除依赖项

以下yarn所有操作都不会更新 package.json 和 yarn.lock 文件

  • yarn [install]:从package.json安装项目全部依赖并写入yarn.lock
    • yarn intall --flat 安装一个包的单一版本
    • yarn install --force 强制重新下载所有包
    • yarn install --production 只安装生产环境依赖
  • yarn check:验证package.json里的依赖版本是否和yarn.lock否匹配
  • yarn check --integrity:验证package.json里依赖版本和hash值是否和yarn.lock是否匹配

查看命令

  • yarn info <package>@[version] [<field>]:拉取远程包的信息并返回树格式,可通过field指定哪部分被返回,也可将filed指定为readme
  • yarn licenses list:按字母顺序列出所有被yan install安装的包
  • yarn list [package]:列出项目安装的依赖
  • yarn outdated [package...]:检查过时的包依赖
  • yarn why <query>:显示有关一个包为何被安装的信息

《Web全栈工程师的自我修养》笔记

发表于 2017-09-23   |   分类于 Reading Notes

最近看了《Web全栈工程师的自我修养》,以下是个人感觉书中比较好的内容,记录下来方便以后查看。


服务器、数据库、服务器编程语言、HTML、CSS、JavaScript等组合在一起就是一个“栈”,这个“栈”是用来制作Web站点的,所以又叫Web栈(Web-Stack)。

服务器、数据库、服务器编程语言、iOS或者Android开发技术组合在一起称为App栈。

软件工程师事业的3个关键词,分别是技术、成长和声望。

全栈工程师是指,一个能处理数据库、服务器、系统工程和客户端的所有工作的工程师。根据项目的不同,客户需要的可能是移动栈、Web栈,或者原生应用程序栈。全栈工程师除了在一个专精知识领域有深入研究之外,还以知识广博和解决问题能力强著称,可以采用“先精后广,一专多长”的流学习程来成为全栈工程师。从工作中锻炼出来的发现问题、洞察需求、设计解决方案并开发出初始版本产品的能力,是全栈工程师最大的优势。全栈工程师应该关注用户体验,并且掌握用户体验相关的知识。

如果一个公司需要招聘“全栈工程师”,可能要求的三个那能力就是一专多长、关注商业目标、关注用户体验。

全栈工程师的目标往往是快速解决商业问题,不一定需要长期完美的方案。使用方便好用的框架能大大节省学习成本和开发时间,所以有些时候技术选型的步骤是:先选择框架,然后选择语言。

一个代表性的全栈框架————MEAN,它是MongoDB-Express-AngularJs-Node.js的缩写,是从数据库、服务器到前端页面的一个完整技术栈。

最常使用的服务器是基于Linux的。Web发布使用Apache,数据库使用MySQL,服务器端编程语言使用PHP的组合,它们往往一起统称为LAMP(Linux-Apache-MySQL-PHP)整体解决方案。


前端工程师:产品视觉稿在得到产品经理和交互设计师等多方确认之后,会交给前端工程师,由前端工程师制作页面,实现视觉稿以及交互功能。从头衔上的变化就可以看出,这时候才真正开始编码。前端工程师需要非常熟悉HTML、CSS和JavaScript,以及性能、语义化、多浏览器兼容、SEO、自动化工具等广泛的知识。关于网络性能和HTTP协议,作为大公司的前端工程师是非常看重的。现在前端开发岗位开始要求有移动端页面开发的经验,或者熟悉响应式页面开发。

招聘初级工程师时,一般会考察:

  • 对浏览器兼容性的了解
  • 对HTML/CSS/JavaScript语法和原理的理解
  • 对编辑器和插件的熟悉程度
  • 对调试工具的了解程度
  • 对版本管理软件的熟悉和应用经验
  • 对前端库/框架的使用
  • 标准/规范

招聘中级工程师时,一般会考察:

  • 对代码质量、代码规范的理解
  • 对JavaScript单元测试的熟悉
  • 对性能优化的应用和理解
  • 对SEO的应用和理解
  • 代码部署
  • 移动Web

招聘高级工程师时,除了上面考察点以外,还会考察:

  • 代码架构
  • 安全
  • 对自动化测试的理解

越接近高级工程师,越考察对某个点的本质理解,以及在项目和团队中的引导作用,而不是对某工具的使用经验。

前端工程师要有一个基本常识,那就是结构、表现和行为要分离。技术层面上讲,网站的内容使用语义化的HTML标签,而不掺杂任何表现和逻辑;网站样式表现用CSS来描述,既能在多个页面之间复用,也可以根据不同用户来分别定义外观;页面行为逻辑用JavaScript来实现,这样保证浏览器在禁用JavaScript的时候,页面也能正常渲染和使用。

建议,在大三或者大四的方向课程设计上,或者选修课设计上增加与时俱进的前端开发课程,使用业界最新的编程语言去教学,这样对毕业生、对用人单位都是好事。


一个库是一系列对象、方法等代码,应用程序可以把这个库“链接”进来。库起到了重用代码的作用,省下了重写这部分代码的工作量。

一个框架是一个软件系统中可重用的一部分,它可能包含子程序、库、胶水语言、图片等一些“资源”,这些资源一起组成了软件项目。框架不像库,可能包含多种语言,某些功能可能通过API的方式让主程序调用。

GASP库发现jQuery动画慢的问题,就重点优化JavaScript动画部分,它号称动画速度比jQuery快20倍,而且能开启硬件加速,在某些情况下比CSS动画性能还要好。

互联网领域发展很快,问题的优先级永远都是在动态变化的。所以团队往往每半年或者三个月就要回顾一下当前形势,并制定新的工作计划。如果新计划不是您擅长的,怎么办?您应该马上开始学习新的技术,这就是所说的关注问题,而不是醉心技术。

如何创造更大的影响力?影响力就是跨界解决问题。高级工程师可以选择往上下游去扩展自己的能力,并承担更多的责任,给公司带来更大的收益,也给自己带来更大的成长空间。

永远从商业目标的角度来决定学习哪些东西,而不是纯粹为了锻炼技术能力而去学习。在开源项目中贡献代码,说明您有能力阅读和编写好的代码,这是公司直接需要的技能,此外,这还能说明您有能力与他人协作:开源代码总是需要协作的。开源项目还能表明您对新鲜事物有热情,表明您也许英语能力不错,有查阅文档的能力…简直是一箭N雕。

Web性能优化:

  • 压缩源码和图片
  • 选择合适的图片格式
  • 合并静态资源
  • 开启服务器端的Gzip压缩
  • 使用CDN
  • 延长静态资源缓存时间,不过需要通过改文件名的方式确保资源更新时用户会拉取新资源
  • 把CSS放在页面的头部,把JavaScript放在页面的底部

在最开始,硅谷之所以名字当中有一个“硅”字,是因为当地企业多数是从事加工制造高浓度硅的半导体行业和电脑工业。随后,互联网公司和软件公司渐渐取代传统的硬件公司,让硅谷获得了新的生命,但硅谷这个名字保留了下来。在硅谷从诞生到发展壮大的整个生命周期中,斯坦福大学起到了很大的最用,称之为硅谷的母亲也不为过。

https://www.smashingmagazine.com/
https://tutsplus.com/

沉淀和总结是很重要的,在腾讯,设计师做完一次设计定稿之后,就会把设计的思路,包括整体的设计风格、设计规范和色彩的确定等都总结成一封邮件或者PPT,发送给部门同事。

为什么服务器对并发请求数这么敏感?虽然服务器的多个进程看上去是同时运行,但是对于单核CPU的架构来说,实际上是计算机系统同一段时间内,以进程的形式,将多个程序加载到存储器中,并借由时间共享,以在一个处理器上表现出同时运行的感觉。由于在操作系统中,生成进程、销毁进程、进程间切换都很消耗CPU和内存,因为当负载高时,性能会明显降低。

Apache通过模块化的设计来适应各种环境,其中一个模块叫做多处理模块(MPM),专门用来处理多请求的情况。Apache安装在不同系统上的时候会调用不同的默认MPM,我们不用关心具体的细节,只需要了解Unix上默认的MPM是prefork。为了优化,可以改成worker模式。prefork和worker模式的最大区别就是,prefork的一个进程维持一个连接,而woker的一个线程维持一个连接。

Nginx是Apache服务器不错的替代品或者补充:一方面是Nginx更加轻量级,占用更少的资源和内存;另一方面是Nginx处理请求是异步非阻塞的,而Apache则是阻塞型的,在高并发下Nginx能保持低资源、低消耗和高性能。由于Apache和Nginx各有所长,所以经常的搭配是Nginx处理前端并发,Apache处理后台请求。

DDoS是Distributed Denial of Service的缩写,DDoS攻击翻译成中文就是“分布式拒绝服务”攻击。

HTTP 1.1引入分块传输编码,允许服务器为动态生成的内容维持HTTP持久链接。如果一个HTTP消息(请求消息或应答消息)的Transfer-Encoding消息头的值为chunked,那么消息体由数量不确定的块组成————也就是说想发送多少块发送多少块————并以最后一个大小为0的块为结束。

通过Chrome开发者工具中的PageSpeed工具,可以快速获得关于站点性能优化的建议。

缓存有这样几种功效:

  • 存储频繁访问的数据
  • 内存缓存减少磁盘I/O
  • 保存耗时的操作,以便下次使用

可以开启MySQL查询缓存来提高速度,并且减少系统压力。可以通过修改MySQL安装目录中的my.ini来设置查询缓存。

memcached支持集群,而且有诸多优点,所以可以有效利用多台机器的内存,提高命中率。

表征性状态传输(Representational State Transfer,REST)是Roy Fielding博士在2000年发表的博士论文中提出来的一种软件架构风格。Restful的目的是定义如何正确地使用Web标准,优雅地使用HTTP本身的特性。原则上是对资源、集合、服务(URL)、get、post、put、delete(操作)的合理使用。

浏览器缓存设置最佳实践:

  • 对于动态生成的HTML页面使用HTTPS头:Cache-Control:no-cache。
  • 对于静态HTML页面使用HTTPS头:Last-Modafied。
  • 其它所有的文件类型都设置Expires头,并且在文件内容有所修改的时候修改Query String。

风投在评估一个创业项目是否会成功的时候,有一个指标就是创始人是否是自己产品的目标用户。

混合模式App同时使用Web技术与原生程序语言开发。App启动后,它的全部页面或者部分界面中,使用网络视图(WebView)技术来实现。WebView能加载显示网页,可以将其视为一个浏览器,它一般使用WebKit渲染引擎加载显示网页。常用的对WebView进行性能优化的方法:

  • 把WebView的部分或者所有资源打包在App中。需要网络数据时,可以通过网络请求json或者体积比较小的数据格式,然后通过本地页面模板和资源来渲染。这种方式的缺点是,App发布包体积会变大。
  • 把需要加载的资源设置好预先加载。可以在App启动时从后台下载需要的资源,并缓存在手机沙盒中备用。这种方法的好处是不会增加包体积,不过第一次访问的时候可能因为没有预加载资源而导致等待时间比较久。
  • 使用HTML5 Manifest技术实现资源缓存。HTML5引入了应用程序缓存,这意味着Web App可进行缓存,并可在没有互联网连接时访问。这种方法的好处是,缓存所有资源到本地之后,如果希望更新WebView,可以在服务器上更新资源列表和Manifest文件。App检测到Manifest文件的修改,就知道资源已经更新,可以开始下载新的资源了。
  • 不要把整个App的主要逻辑都使用WebView来实现。要结合原生技术和WebView各自的优缺点,根据不同的场景选择合适的技术。原生技术的优点在于能很好地操作App存储数据;实现页面间切换、高性能动画、大量数据的界面。WebView的优点在于开发快、技术简单;前端开发者能够利用已有的CSS3和JavaScript知识;页面能够从服务器端更新;能够分享到社交平台;在多个平台上共用等。
  • 设计得更像一个App,而不是一个网页。在这种做法诞生初期,还会把App分为原生App和混合模式App,不过从2014年以来,已经不这样区分了。一般认为一个App中有一些HTML页面是非常自然的事情,所以这个概念在渐渐淡化。

点击WebView中的一个按钮,我希望弹出一个原生的警告框,应该怎么实现呢?在Android中,可以使用WebView.addJavascriptInterface方法来实现互相通信。在iOS中,需要使用shouldStartLoadWithRequest委托。

CVS有以下几个缺点:

  • CVS不支持文件重命名,如果重命名一个文件,之前的修订历史记录就会丢失
  • CVS没有原子性提交,如果提交很多文件,提交到一半的时候出错,那就麻烦了
  • CVS只支持文本文件,无法提交二进制文件

SVN解决了这几个问题,并加入了一些新特性:

  • SVN对二进制文件的版本管理,使用了节省空间的保存方法,只保存和上一版本的不同之处
  • 目录也有版本历史。重命名、复制和删除也会保存在版本历史记录中,当然,要用SVN的重命名来操作
  • 分支的开销比较小

关于npm这个词的意义,一般人可能以为是Node Package Manager的缩写,但实际不是这样的。npm是“npm is not an acronym”(npm不是一个缩写)这个递归定义的简写。

所有的脚本语言都是直接运行,不需要编译成可执行代码,只不过Python等脚本运行在服务器,用户看不到源代码,而JavaScript运行在浏览器,所有人都能看到源码。

好的架构有以下几个要素:

  • 有合适的分离粒度
  • 最小知识原则
  • DRY(Don’t Repeat Yourself,不要重复您自己)
  • 最小化预先设计,只设计必需的内容
  • 通过良好的层级,让文件易于找到
  • 在代码层面,有一致且可执行的命名规则

将编程语言编译成机器码有两种传统的方法:使用编译器(compiler)预先编译,或者使用解释器(interpreter)一边编译一边运行。编译器的工作方式是,通过编译程序直接将我们的源代码翻译成当前系统环境下CPU可执行的机器码,下次只需执行这些翻译完成后的机器代码即可。这种方式的好处是执行效率非常高,缺点就是可移植性很差。解释器的工作方式是,将工程师编写的代码,一句一句解释给CPU执行,每次运行程序时都要先转成另一种语言再作运行,因此解释器的程序运行速度比较慢。这种方式的好处是平台无关性,以及这个“编辑-解释-除错”的循环通常比“编辑-编译-运行-除错”的循环省时许多。

传统JavaScript引擎通常先把JavaScript代码编译成字节码,然后再通过字节码转译为机器码。v8引擎直接把JavaScript代码编译成机器码,所以性能得到了很大提高。

有些语言被设计为可以在任何领域使用,比如C、Java、Python和XML,它们被成为“通用语言”。

有些语言被设计为特定领域专用,比如HTML和CSS,称为为“特定领域语言”。

KISS原则是“Keep it simple, stupid”的缩写,意思是说软件设计当中应该注重简约的原则。这一原则认为,大部分系统的设计越简单越好,有不必要的复杂性都应该避免。如果一个系统非常复杂,就应该分解为多个简单的组件,做好足够的分解和抽象。

虚拟专用服务器(Virtual Privte Server,VPS)是把一台服务器分割成多个虚拟专享服务器的优质服务。

设计模式的关注点在于:

  • 高效编写代码
  • 高可复用性
  • 抽象带来的可读性

23种软件设计模式,分为三大类:创建型模式、结构型模式、行为模式。创建型模式,就是用来创建对象的模式,它对实例化的过程进行了抽象,帮助一个系统独立于如何创建、组合和表示它的那些对象,如单例模式、惰性初始化模式、工厂方法、抽象工厂、建造模型、原型模式、对象池模式和多例模式等。结构型模式,主要解决类、对象、模块之前的耦合关系,如适配器模式、桥接模式、组合模式、装饰模式、外观模式、享元模式和代理模式等。行为模式,用来识别对象之前的常用交流模式并加以实现,如此,可在进行这些交流活动时增强弹性,如观察者模式、黑板、责任链、命令、解释器、迭代器、中介者、备忘录、空对象、模板方法和访问者等。

MVC的本质是代码的分离,它不光在Web开发中大放异彩,在桌面软件和移动端软件开发中也随处可见。

DRY是“Don’t Repeat Yourself”的缩写,意思是说,在一个系统里,对于任何数据或者变量,都应该配置在有且只有一个地方,其它的地方都应该引用这里的数据。这样,需要改动数据的时候,只需调整这一处,所有的地方就都变更过来了。

推荐使用英文搜索的原因:

  • 英文的技术资料更多
  • StackOverflow有完善的鼓励机制
  • Google的搜索能力非常强
  • 英语世界的语言风格比较严谨

Jeff Atwood提出了著名的Atwood定律:“任何能够用JavaScript实现的应用系统,最终都必将用JavaScript实现”。

自我意识,是指您能够精确地觉察自己的情绪波动。

社会意识,是指您能感知周围人的情绪,并敏锐捕捉到周围发生的事情。

自我管理,是指您能根据自我情绪的感知,灵活积极地调控自身行为。

关系管理,是指您感知到周遭的“情绪场”之后,能够掌控自我情绪并把握他人情绪值,来让双方进行更好的互动。

JavaScript-小代码

发表于 2017-08-31   |   分类于 FJ
  • 实现Array的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
    26
    27
    28
    29
    30
    31
    32
    33
    Array.prototype.map = function(callback, thisArg) {
    if (this == null) {
    throw new TypeError("this is null or not defined");
    }
    if (Object.prototype.toString.call(callback) != "[object Function]") {
    throw new TypeError(callback + " is not a function");
    }
    // 将O赋值为调用map方法的数组. 防止callback方法中修改原数组.
    var originArr = Object(this);
    // 将len赋值为数组O的长度. 如果length未定义就取0.
    var len = originArr.length >>> 0;
    var copyArg;
    if (thisArg) {
    copyArg = thisArg;
    }
    var result = new Array(len);
    var k = 0;
    while(k < len) {
    var kValue, mappedValue;
    // 遍历O,k为原数组索引
    if (k in originArr) {
    // kValue为索引k对应的值.
    kValue = originArr[k];
    // 执行callback,this指向copyArg.
    // originArr.map((kValue, k, originArr) => {});
    mappedValue = callback.call(copyArg, kValue, k, originArr);
    // 返回值添加到新数组A中.
    result[k] = mappedValue;
    }
    k++;
    }
    return result;
    };
  • js 实现快排

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    function quickSort(arr){
    // 如果数组<=1,则直接返回
    if(arr.length <= 1){
    return arr;
    }
    var pivotIndex = Math.floor(arr.length/2);
    // 找基准,并把基准从原数组删除
    var pivot = arr.splice(pivotIndex, 1)[0];
    // 定义左右数组
    var left = [];
    var right = [];
    // 比基准小的放在left,比基准大的放在right
    for(var i = 0; i < arr.length; i++){
    if(arr[i] <= pivot){
    left.push(arr[i]);
    }
    else{
    right.push(arr[i]);
    }
    }
    // 递归
    return quickSort(left).concat([pivot], quickSort(right));
    }
  • 随机选取10–100之间的10个且不重复的数字,存入一个数组并排序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    function randomRange(start, end, count) {
    // 升序排序
    function sortFunc(a, b) {
    return a - b;
    }
    const randoms=[];
    // 跳出while循环时 randoms数组有count个元素
    while (randoms.length < count)
    {
    // 获取一个10–100范围的数
    var random = Math.floor(Math.random()*(end - start + 1) + start);
    // 判断当前随机数是否已经存在
    if (!randoms.includes(random)) {
    randoms.push(random);
    }
    }
    randoms.sort(sortFunc);
    return randoms;
    }
    randomRange(10, 100, 10);
  • 获取url后面的查询参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // 获取url后面指定名称的查询参数
    function getUrlQueryString(name) {
    var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
    // window.location.search 从?开始的部分,包括?
    var r = window.location.search.substr(1).match(reg);
    if (r) {
    return unescape(r[2]);// 对通过escape()编码的字符串进行解码
    }
    return null;
    }
    // 获取url的查询参数组成的对象
    function getUrlQueryObj() {
    //获取url中"?"符后的字串
    var url = location.search;
    var queryObj = {};
    if (url.indexOf("?") > -1) {
    var str = url.substr(1);
    strs = str.split("&");
    strs.forEach((item) => {
    queryObj[item.split("=")[0]] = unescape(item.split("=")[1]);
    });
    }
    return queryObj;
    }
  • 找字符串中出现最多的字符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    function getMost(str){
    var maxNum = 0;
    // 存放出现字数最多的字符数组,因为出现最多的字符可能有多个
    var maxCharArr = [];
    while(str){
    var char = str.charAt(0);
    var arr = str.split(char);
    // char 字符出现的个数
    var n = str.length - arr.join('').length;
    // str 中去掉全部 char 字符
    str = arr.join('');
    if(n > maxNum){
    maxNum = n;
    maxCharArr = [char]; // 放置数组元素
    }else if( n === maxNum){
    maxCharArr.push(char); //出现次数相同的字符 存入数组里
    }
    }
    return {'maxNum': maxNum, 'maxCharArr': maxCharArr};
    }
  • a 和 b 不使用临时变量进行交换

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 方式一 通过计算
    function swape(a, b) {
    console.log("进入:", a, ' ', b);
    if (a === b) {
    // 不用交换
    }
    if (a < b) {
    a = b + (b - a);
    b = b - (a - b);
    a = b + (a - b)/2;
    } else {
    b = a + (a - b);
    a = a - (b - a);
    b = a + (b - a)/2;
    }
    console.log("离开:", a, ' ', b);
    }
    // 方式二 es5
    a = [b, b = a][0];
    // 方式三 es6
    [a, b] = [b, a];
  • 字符串的字节长度

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 方式一 将双字节字符替换为2个单字节字母
    str.replace(/[\u0931-\uFFE5]/g, 'aa').length;
    // 方式二 把中文替换成两个a
    str.replace(/[^\x00-\xff]/g, 'aa');
    // 方式三 循环遍历
    function getStrLength (str) {
    // 获得字符串实际长度,中文2,英文1
    var realLength = 0, len = str.length, charCode = -1;
    for (var i = 0; i < len; i++) {
    charCode = str.charCodeAt(i);
    if (charCode >= 0 && charCode <= 128)
    realLength += 1;
    else {
    realLength += 2;
    }
    }
    return realLength;
    };
  • 字符串反转

    1
    str.split('').reverse().join('');
  • 数组去重

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 方式一 使用es6的set
    arr = Array.from(new Set(arr));
    // 方式二 使用indexOf和lastIndexOf判断
    function uniqueArray(arr) {
    for(let i = 0; i < arr.length; i++) {
    let start = arr.indexOf(arr[i]);
    let end = arr.lastIndexOf(arr[i]);
    while(start !== end) {
    arr.splice(end, 1);
    end = arr.lastIndexOf(arr[i]);
    }
    }
    }
  • 数组扁平化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 方式一 递归
    function flat(array, newarray) {
    for (var i = 0; i < array.length; i++) {
    if(typeof array[i] == "number"){
    //类型为number, 则放入到新数组中
    newarray.push(array[i]);
    } else {
    //否则, 继续分解
    flat(array[i], newarray);
    }
    }
    }
    var array = [1,[3,[4],[5,[6,7]],8],[9,[10]]];
    var newarray = [];
    flat(array, newarray);
    // 方式二 toString(),但是条件受限
    var array = [1,[3,[4],[5,[6,7]],8],[9,[10]]];
    var newarray = array.toString().split(',');

// 方式三 使用es6的generator

1
2
3
4
5
6
7
8
9
10
11
12
13
var array = [1,[3,[4],[5,[6,7]],8],[9,[10]]];
function* iterTree(tree) {
if (Array.isArray(tree)) {
for(let i=0; i < tree.length; i++) {
yield* iterTree(tree[i]);
}
} else {
yield tree;
}
}
for(let x of iterTree(array)) {
console.log(x);
}

  • 同一事件有多个监听方法,可分别删除监听方法

    当同一个对象使用.onclick的写法触发多个方法的时候,后一个方法会把前一个方法覆盖掉。而用addEventListener事件监听则不会有覆盖的现象,支持多重加载与冒泡捕获,每个绑定的事件都会被执行。

    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
    // onclick绑定事件
    window.onload = function() {
    var btn = document.getElementById("yuanEvent");
    btn.onclick = function() {
    alert("第一个事件");
    };
    btn.onclick = function() {
    alert("第二个事件");
    }
    };
    // addEventListener绑定事件
    var eventOne = function() {
    alert("第一个监听事件");
    }
    function eventTwo() {
    alert("第二个监听事件");
    }
    window.onload = function() {
    var btn = document.getElementById("yuanEvent");
    // addEventListener:绑定函数
    btn.addEventListener("click", eventOne);
    btn.addEventListener("click", eventTwo);
    // removeEventListener:取消绑定
    btn.removeEventListener("click",eventOne);
    }

    解决onClick的写法触发后一个方法会把前一个方法覆盖的问题。

    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
    //统计添加事件监听的个数,0作为预留位
    var eventHandlesCounter = 1;
    function addEvent(obj, evt, fn) {
    if(!fn.__EventID) {
    fn.__EventID = eventHandlesCounter++;
    }
    if(!obj.__EventHandles) {
    obj.__EventHandles = [];
    }
    if(!obj.__EventHandles[evt]) {
    obj.__EventHandles[evt] = [];
    // 这里记录已经使用onClick直接绑定的事件
    if(obj["on" + evt] instanceof Function) {
    obj["on" + evt].__EventID = 0;
    obj.__EventHandles[evt][0] = obj["on" + evt];
    }
    obj["on" + evt] = handleEvents;
    }
    obj.__EventHandles[evt][fn.__EventID] = fn;
    function handleEvents() {
    var fns = obj.__EventHandles[evt];
    for (var i = 0; i < fns.length; i++) {
    if(fns[i] instanceof Function) {
    fns[i].call(this);
    }
    }
    }
    }
    function delEvent(obj, evt, fn) {
    if(!obj.__EventHandles || !obj.__EventHandles[evt] || !fn.__EventID){
    return false;
    }
    if(obj.__EventHandles[evt][fn.__EventID] == fn){
    delete obj.__EventHandles[evt][fn.__EventID];
    }
    }
  • img标签,获取图片的base64编码

    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Document</title>
    </head>
    <body>
    <script type="text/javascript">
    function getBase64Image(img) {
    var canvas = document.createElement("canvas");
    canvas.width = img.width;
    canvas.height = img.height;
    var ctx = canvas.getContext("2d");
    ctx.drawImage(img, 0, 0, img.width, img.height);
    var dataURL = canvas.toDataURL("image/png");
    return dataURL;
    }
    function main() {
    var img = document.createElement('img');
    // 注意这个图片需要是同源的
    img.src = './1.png';
    img.onload =function() {
    var data = getBase64Image(img);
    console.log("图片的base64编码:", data);
    }
    document.body.appendChild(img);
    }
    main()
    </script>
    </body>
    </html>
  • 实现String.prototype.trim函数的实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 使用正则表达式
    String.prototype.trim = function() {
    return this.replace(/(^\s*)|(\s*$)/g, "");
    }
    String.prototype.trimLeft = function() {
    return this.replace(/(^\s*)/g, "");
    }
    String.prototype.trimRight = function() {
    return this.replace(/(\s*$)/g, "");
    }
  • 实现一个函数fn,只有一个参数,实现参数求和功能,例如console.log(parseInt(fn(0)(1)(2)(3)(4)(5))),可以在控制台输出15。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function fn(value) {
    function returnFn(value2) {
    value = value + value2;
    return returnFn;
    }
    returnFn.toString = returnFn.valueOf = function() {
    return value;
    }
    return returnFn;
    }
  • 使用基于组件的开发模式,开发一个转盘抽奖组件,要求组件功能有:①可以设置旋转圈数②组件可复用③其它自行设计。设计对应的dom,css及js代码,最多可使用jQuery和zepto辅助。

    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
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>简单转盘效果</title>
    <style>
    #RotateDiv, #RotateDiv2 {
    width: 50px;
    height: 50px;
    color: #fff;
    text-align: center;
    line-height: 50px;
    background: #444;
    position: relative;
    margin: 20px;
    /* 指针效果 */
    border-radius: 50px 0 50px 50px;
    }
    </style>
    </head>
    <body>
    <p>简单转盘:</p>
    <p> <button id="RotateBtn">开始抽奖</button> </p>
    <div id="RotateDiv"></div>
    <p>默认转动:</p>
    <p> <button id="RotateBtn2">开始抽奖2</button> </p>
    <div id="RotateDiv2"></div>
    <script type="text/javascript">
    window.iRotate = (function(w, d){
    function R(obj, json){
    this.obj = (typeof obj === 'object') ? obj : d.querySelector(obj);
    this.startTime = Date.now();
    this.timer = null;
    this.rotate(json);
    };
    R.prototype = {
    rotate: function(json) {
    var self = this;
    var times = json['time'] || 1000;
    clearInterval(self.timer);
    self.timer = setInterval(function() {
    var changeTime = Date.now();
    // 当前消耗时间
    var timing = Math.min(times, changeTime - self.startTime);
    var tweenFun = Tween[json['easing'] || 'easeOut'];
    // 根据当前时间计算转动角度
    var value = tweenFun(
    timing,
    +json['start'] || 0,
    json['end'] - (+json['start'] || 0),
    times
    );
    self.obj.style['transform'] = 'rotate(' + value%360 + 'deg)';
    self.obj.style['-webkit-transform'] = 'rotate(' + value%360 + 'deg)';
    self.obj.setAttribute('data-rotate', value%360);
    // 停止旋转
    if(timing == times){
    clearInterval(self.timer);
    json.callback && json.callback.call(self.obj);
    }
    }, 10)
    },
    stop: function(fn) {
    clearInterval(this.timer);
    fn && fn.call(this.obj);
    }
    };
    return R;
    })(window, document);
    var Tween = {
    // 匀速转动
    linear: function (t, b, c, d){
    return c*t/d + b;
    },
    // 逐渐变慢
    easeOut: function(t, b, c, d){
    return -c *(t/=d)*(t-2) + b;
    }
    };
    (function(){
    // 点击转动
    var off = true;
    RotateBtn.onclick = function(){
    if(!off) return; // 判断是否在旋转
    off = false;
    new iRotate('#RotateDiv', {
    end: 45 + 1800,
    time: 5000,
    callback : function(){ // 回调函数
    this.innerHTML = this.getAttribute('data-rotate');
    off = true;
    }
    });
    }
    //默认转动
    var r = null;
    var off2 = true;
    function rotate2(){ // 递归持续旋转
    r = new iRotate('#RotateDiv2', {
    start: 0,
    end: 360,
    time: 1000,
    easing: 'linear',
    callback: function(){
    rotate2();
    }
    });
    }
    rotate2();
    RotateBtn2.onclick = function(){
    if(!off2) return; // 判断是否在旋转
    off2 = false;
    r.stop(); // 停止之前的旋转
    new iRotate('#RotateDiv2', {
    start: RotateDiv2.getAttribute('data-rotate'), // 如果不加这个会从0角度开始旋转,有抖动
    end: 65 + 1800,
    time: 5000,
    callback: function(){ // 回调函数
    this.innerHTML = this.getAttribute('data-rotate');
    off2 = true;
    }
    });
    }
    })();
    </script>
    </body>
    </html>
  • 股票利润:假如一个数组中存储了一个股票在一天交易窗口内各时间点的股票价格(正整数),只允许一次买入和一次卖出,计算出通过卖出和买入可以得到的最大利润。

    思路:只要找出输入数组中最大的差值即可。遍历一遍数组,求出每个元素前面的最小值,每个元素与对应最小值的差即为该元素对应的收益,所有元素收益的最大值就是所求值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    function maxProfit(prices) {
    var maxProfit = -1;
    if (!prices || prices.length === 0) {
    return maxProfit;
    }
    var minPrices = [];
    for(var i = 0; i < prices.length; i++) {
    if (i === 0) {
    // 第一个元素前的最小值是自己
    minPrices.push(prices[i]);
    } else {
    /*
    当前元素如果大于前一个最小值,则当前元素最小值为前一个的最小值,否则当前元素的最小值为当前元素
    */
    minPrices.push(minPrices[i-1] <= prices[i] ? minPrices[i-1] : prices[i]);
    }
    }
    for(var i = 0; i < prices.length; i++) {
    if (prices[i] - minPrices[i] > maxProfit) {
    maxProfit = prices[i] - minPrices[i];
    }
    }
    return maxProfit;
    }
  • 实现图片墙的效果,要求所有图片显示的宽度相同,整个图片墙的高度尽可能小。

    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
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    <!DOCTYPE HTML>
    <html lang="en-US">
    <head>
    <meta charset="UTF-8">
    <title>瀑布流</title>
    <style>
    * {
    margin:0;
    padding:0;
    }
    #main {
    position:relative;
    }
    .box {
    padding:15px 0 0 15px;
    float:left;
    }
    .pic {
    padding:10px;
    border:1px solid #ccc;
    border-radius:5px;
    box-shadow:0 0 5px #ccc;
    background:#FFF;
    }
    img {
    width:236px; // 固定
    height:auto;
    opacity:1;
    }
    .pic:hover img {
    opacity:0.7;
    }
    </style>
    </head>
    <body>
    <div id="main"></div>
    <script type="text/javascript">
    // 如果没有加载,就可以加载
    var loading = false;
    // 发起请求加载图片
    putDate();
    // 滚轮加载, 符合条件时, 当滚轮滚到最后一个div的一半时请求新的数据
    window.onscroll = function () {
    if (getMore()) {
    // 这里是 ajax 请求, 如果没有加载,就可以加载, 加载完成后就设置为false未加在状态
    if (!loading) {
    loading = true;
    // 发起请求加载图片
    putDate();
    }
    }
    };
    // 排序方法
    function waterfall(parent, box) {
    // 获取大盒子. 获取小盒子
    let parentBox = document.getElementById(parent);
    let theBox = document.getElementsByClassName(box);
    if (theBox.length === 0) {
    return;
    }
    // 计算大盒子里能放几列小盒子
    let mainWidth = document.documentElement.clientWidth;
    let contentWidth = theBox[0].offsetWidth;
    let col = Math.floor(mainWidth/contentWidth);
    // 给大盒子设置宽度
    parentBox.style.cssText = `width: ${contentWidth*col}px; margin: 0 auto;`;
    /*
    计算 哪个盒子所在的位置的 offsetTop 最小
    创建一个数组, 把现在屏幕宽度能设置的列数,比如最大时4列, 把前4 个div的高度放进数组中,
    然后超过 4 的开始计算,
    1: 数组中谁最小,
    2: 获取他的值 作为这个将要定位的div 的 top值, 下标*contentWidth 作为 left的定位
    3: 更改数组, 把这个div的 offsetHeight + 最小值 更新这个值
    */
    let arr = [];
    [...theBox].map((item,index) =>{
    if (index < col) {
    arr.push(item.offsetHeight)
    } else {
    // 获取最小值
    let getMinNum = Math.min.apply( null, arr);
    // 获取最小值所在的Index
    let getMinNumIndex = arr.findIndex(function(item) {
    return item === getMinNum
    });
    theBox[index].style.cssText = `position: absolute; top: ${getMinNum}px; left: ${getMinNumIndex*contentWidth}px`;
    arr[getMinNumIndex] += theBox[index].offsetHeight;
    }
    });
    }
    /*
    继续加载的条件, 当滚动到最后一个div的中间位置, 就加载
    即 div.offsetTop + div.offsetHeight/2 小于 scrollTop(滚动到上面看不到的距离) + clientHeight (现在可视区域的高度)
    */
    function getMore() {
    let theBox = document.getElementsByClassName('box');
    let len = theBox.length;
    let ele = theBox[len - 1 ];
    // 获取div的 offsetTop ,offsetHeight
    let divTop = ele.offsetTop + ele.offsetHeight/2 ;
    // 获取 scrollTop(滚动到上面看不到的距离) 可能有iframe吧
    let scrollT = document.body.scrollTop || document.documentElement.scrollTop;
    // 获取 clientHeight (现在可视区域的高度)
    let clitH = document.body.clientHeight || document.documentElement.clientHeight;
    return divTop < scrollT + clitH;
    }
    //后台请求数据
    function putDate() {
    // var xhr = ajaxContent();
    // xhr.onreadystatechange = function(){
    // if(xhr.readyState == 4 && xhr.status == 200) {
    // var res = JSON.parse(xhr.responseText);
    // addImages(res);
    // }
    // }
    // xhr.open('get','../config/data.js',true);
    // xhr.send();
    var res = [{"src":"1.jpg"},{"src":"2.jpg"},{"src":"3.jpg"},{"src":"4.jpg"},{"src":"5.jpg"},{"src":"6.jpg"},{"src":"7.jpg"},{"src":"8.jpg"},{"src":"9.jpg"},{"src":"10.jpg"},{"src":"11.jpg"},{"src":"12.jpg"},{"src":"13.jpg"},{"src":"14.jpg"},{"src":"15.jpg"},{"src":"16.jpg"},{"src":"17.jpg"},{"src":"18.jpg"},{"src":"19.jpg"},{"src":"20.jpg"},{"src":"21.jpg"},{"src":"22.jpg"},{"src":"23.jpg"},{"src":"24.jpg"},{"src":"25.jpg"},{"src":"26.jpg"},{"src":"27.jpg"},{"src":"28.jpg"},{"src":"29.jpg"},{"src":"30.jpg"}];
    addImages(res);
    function addImages(res) {
    var oparent = document.getElementById('main');
    res.map(function(item,index) {
    var url = item.src;
    var str ='<div class="pic"><img src="imgp/'+url+'" onload="waterfall(\'main\',\'box\')"></div>';
    var mdiv = document.createElement('div');
    mdiv.setAttribute('class','box')
    mdiv.innerHTML = str;
    oparent.appendChild(mdiv);
    // 请求一次做一次定位
    waterfall('main','box');
    loading = false;
    });
    }
    }
    // 封装ajax请求
    function ajaxContent(){
    var xhr = null;
    if(window.XMLHttpRequest){
    xhr = new XMLHttpRequest();
    }else{
    xhr = new ActiveXObjext('Microsoft.XMLHTTP');
    }
    return xhr;
    }
    </script>
    </body>
    </html>

算法基础

发表于 2017-08-31   |   分类于 Algorithm

堆排序

首先要知道完全二叉树的概念:叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树。

堆可以视为一棵完全的二叉树,大顶堆中每个父节点的元素值都大于等于其孩子结点的值,小顶堆中每个父节点的元素值都小于等于其孩子结点的值。

堆排序不需要大量的递归或者多维的暂存数组,这对于数据量非常巨大的序列是合适的,比如超过数百万条记录。因为快速排序,归并排序都使用递归来设计算法,在数据量非常大的时候,可能会发生堆栈溢出错误。堆排序会将所有的数据建成一个堆,最大的数据在堆顶,然后将堆顶数据和序列的最后一个数据交换。接下来再次重建堆,交换数据,依次下去,就可以排序所有的数据。如果要取大量数据中最大或最小的几个,这种时候用堆排比较好,取出要求的前几个最大元素即可停止了。

建堆思路:从最后一个非叶子节点开始,按照从下到上、从右到左的顺序进行迭代,让它(下标记为x)同其孩子节点(下标:2x、2x+1)比较,如果它小于任何一个孩子节点,则说明这个子树是符合堆规则的;否则把它同其较小的子节点交换。由于交换后,以这个节点(x)会破坏原来孩子的堆特性,因此这里有一个子迭代,让它继续上面的行为,直到找到一个合适的位置落脚。如此,直到根节点,就可以保证整颗完全树具备堆的性质。

堆排序思路:对于一个最小堆,去掉它的堆顶元素,然后以最末端元素移动到堆顶位置,然后进行调整,使之再次成为最小堆。如此迭代,直到没有剩余的元素,依次取出来的顺序就是实现排序的过程。

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
31
32
33
34
35
36
37
38
function adjustHeap(arr, len, first) {
var sortedFlag = true;
var i = first ? Math.floor(len/2-1) : 0;
while(i >= 0) {
var j = i;
i--;
var flag = true;
while(flag) {
flag = false;
var largePos;
if (j*2+1 < len) {
largePos = j*2+1;
}
if ( (j*2+2) < len && arr[j*2+2] > arr[largePos]) {
largePos = j*2+2;
}
if (largePos && arr[j] < arr[largePos] ) {
flag = true;
var temp = arr[j];
arr[j] = arr[largePos];
arr[largePos] = temp;
j = largePos;
}
}
}
}
function heapSort(arr) {
var first = true;
for(var i= arr.length; i > 0; i--) {
adjustHeap(arr, i, first);
first = false;
var temp = arr[0];
arr[0] = arr[i-1];
arr[i-1] = temp;
}
}
var arr = [16, 4, 10, 14, 7, 9, 3, 2, 8, 1];
heapSort(arr);

100万个数取最大的5个,要求尽量提升性能

思路一

用一个长度为5的数组存结果,对100万个数进行一遍扫描,先存入前5个元素从小到大插入结果数组,对于后面的每个元素,如果小于第一个元素则继续,如果大于第五个元素则删除第一个元素将当前元素插入,如果大于第一个元素小于第五个元素,找到元素所在位置插入并删除最小的元素,这样扫描一遍就可以找到最大的5个元素。

思路二

使用快速排序的思路,在递归时放弃处理值小的那部分,每次都是递归处理值大的部分,直到要处理的元素个数为5个停止。

思路三

查找1000个数中最大或最小的10个,这种时候用堆排比较好。

冒泡排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function bubbleSort(arr) {
for(var i = 0; i < arr.length; i++) {
var pos = i;
var min = arr[i];
for( var j = i+1; j < arr.length; j++) {
if (min > arr[j]) {
min = arr[j];
pos = j;
}
}
var temp = arr[pos];
arr[pos] = arr[i];
arr[i] = temp;
}
}
const arr = [19, 32, 35, 40, 69, 74, 84, 91, 96, 98];
bubbleSort(arr);

React Router-简单原理

发表于 2017-08-27   |   分类于 FJ

以下内容主要参考自
深入理解 react-router 路由系统
react-router的实现原理
前端路由实现与 react-router 源码分析

  • react-router怎么实现页面局部刷新和url变化的

    路由的原理并不复杂,即保证视图和URL的同步,只要URL一致,那么返回的UI界面总是相同的。从性能和用户体验的层面来比较的话,后端路由每次访问一个新页面的时候都要向服务器发送请求,然后服务器再响应请求,这个过程肯定会有延迟。而前端路由在访问一个新页面的时候仅仅是变换了一下路径而已,没有了网络延迟,对于用户体验来说会有相当大的提升。

    在HTML5的historyAPI出现之前,前端的路由都是通过hash来实现的,hash能兼容低版本的浏览器。Web服务并不会解析hash,也就是说#后的内容Web服务都会自动忽略,但是JavaScript是可以通过window.location.hash读取到的,读取到路径加以解析之后就可以响应不同路径的逻辑处理。

    history是HTML5才有的新API,可以用来操作浏览器的session history(会话历史)。基于history来实现的路由可以和最初的路径规则一样,即不带#。用户可能都察觉不到该访问地址是Web服务实现的路由还是前端实现的路由。

    react-router路由系统的核心是history对象,它的很多特性也与React保持了一致,比如声明式组件、组件嵌套、状态机特性等,毕竟它就是基于React构建并且为之所用的。路由进入时装载匹配的组件离开时卸载该组件的策略可以做到合理利用资源,不会一下把所有的组件都装载进来使内存占用飙升,也不会离开时没有卸载而时内存泄漏,每一个路由中声明的组件在渲染之前都会被传入一些props,主要包括history对象和location对象,这两个对象同时存在于路由组件的context中,还可以通过React的context API在组件的子级组件中获取到这两个对象。

    点击Link后路由系统发生了什么?

    Link组件最终会渲染为HTML的a标签,它的to、query、hash属性会被组合在一起并渲染为href属性。虽然Link被渲染为超链接,但在内部实现上使用脚本拦截了浏览器的默认行为,然后调用了history.pushState方法。history包中底层的pushState方法支持传入两个参数state和path,在函数体内又将这两个参数传输到createLocation方法中,返回location。系统会将这个location对象作为参数传入到TransitionTo方法中,然后调用window.location.hash或者window.history.pushState()修改了应用的URL,这取决于你创建history对象的方式。同时会触发history.listen中注册的事件监听器。

    接下来请看路由系统内部是如何修改UI的。在得到了新的location对象后,系统内部的matchRoutes方法会匹配出Route组件树中与当前location对象匹配的一个子集,并且得到了nextState。在Router组件的componentWillMount生命周期方法中调用了history.listen(listener)方法。listener会在上述matchRoutes方法执行成功后执行listener(nextState),nextState对象里面包含location、routes、params、components属性,接下来执行this.setState(nextState)就可以实现重新渲染Router组件。

    点击浏览器的前进和后退按钮发生了什么?

    前进与后退的实现,是通过监听popstate以及hashchange的事件,当前进或后退url更新时,触发这两个事件的回调函数。可以简单地把web浏览器的历史记录比做成一个仅有入栈操作的栈,当用户浏览器到某一个页面时将该文档存入到栈中,点击后退或前进按钮时移动指针到history栈中对应的某一个文档。在传统的浏览器中,文档都是从服务端请求过来的。不过现代的浏览器一般都会支持两种方式用于动态的生成并载入页面。

    第一种方式

    location.hash与hashchange事件,这也是比较简单并且兼容性也比较好的一种方式,会在浏览器的URL中添加一个#号,对应history包中的createHashHistory方法,出于兼容性的考虑(ie8+),路由系统内部将这种方式作为创建history对象的默认方法。详细请看下面几点:

    使用hashchange事件来监听window.location.hash的变化
    hash发生变化浏览器会更新URL,并且在history栈中产生一条记录
    路由系统会将所有的路由信息都保存到location.hash中
    在react-router内部注册了window.addEventListener(‘hashchange’, listener, false)事件监听器
    listener内部可以通过hash fragment获取到当前URL对应的location对象
    接下来的过程与点击<Link/>时保持一致

    当然,你会想到不仅仅在前进和后退会触发hashchange事件,应该说每次路由操作都会有hash的变化。确实如此,为了解决这个问题,路由系统内部通过判断currentLocation与nextLocation是否相等来处理该问题。不过,从它的实现原理上来看,由于路由操作hash发生变化而重复调用transitonTo(location)这一步确实无可避免。

    第二种方式

    history.pushState与popstate事件,新的HTML5规范中还提出了一个相对复杂但更加健壮的方式来解决该问题,对应history包中的createBrowserHistory方法,使用这种方式进行路由需要服务端要做一个路由的配置将所有请求重定向到入口文件位置,否则在用户刷新页面时会报404错误。详细请看下面几点:

    可以通过window.history.pushState(state, title, path)方法来改变浏览器的URL,实际上该方法同时在history栈中存入了state对象
    在浏览器前进和后退时触发popstate事件,然后注册window.addEventListener(‘popstate’, listener, false),并且可以在事件对象中取出对应的state对象
    state对象可以存储一些恢复该页面所需要的简单信息,state会作为属性存储在location对象中,这样你就可以在组件中通过location.state来获取到
    在react-router内部将该对象存储到了sessionStorage中,也就是上图中的saveState操作
    接下来的操作与第一种方式一致

    实际上,上面提到的state对象不仅仅在第二种路由方式中可以使用。react-router内部做了polyfill,统一了API。在使用第一种方式创建路由时你会发现URL中多了一个类似_key=s1gvrm的query,这个_key就是为react-router内部在sessionStorage中读取state对象所提供的。

  • react-router中的几类history

    老浏览器的history: 主要通过hash来实现,对应createHashHistory
    高版本浏览器: 通过html5里面的history,对应createBrowserHistory
    node环境下: 主要存储在memeory里面,对应createMemoryHistory

    上面针对不同的环境提供了三个API,但是三个API有一些共性的操作,将其抽象了一个公共的文件createHistory,createHashHistory、createBrowserHistory、createMemoryHistory只是覆盖其中的某些方法而已。需要注意的是,这里的location跟浏览器原生的location是不相同的,最大的区别就在于里面多了key字段,history内部通过key来进行location的操作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 内部的抽象实现
    function createHistory(options={}) {
    ...
    return {
    listenBefore, // 内部的hook机制,可以在location发生变化前执行某些行为,AOP的实现
    listen, // location发生改变时触发回调
    transitionTo, // 执行location的改变
    push, // 改变location
    replace,
    go,
    goBack,
    goForward,
    createKey, // 创建location的key,用于唯一标示该location,是随机生成的
    createPath,
    createHref,
    createLocation, // 创建location
    }
    }

    执行URL前进

    createBrowserHistory: pushState、replaceState
    createHashHistory: location.hash=*、location.replace()
    createMemoryHistory: 在内存中进行历史记录的存储

    伪代码如下:

    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
    // createBrowserHistory(HTML5)中的前进实现
    function finishTransition(location) {
    ...
    const historyState = { key };
    ...
    if (location.action === 'PUSH') ) {
    window.history.pushState(historyState, null, path);
    } else {
    window.history.replaceState(historyState, null, path)
    }
    }
    // createHashHistory的内部实现
    function finishTransition(location) {
    ...
    if (location.action === 'PUSH') ) {
    window.location.hash = path;
    } else {
    window.location.replace(
    window.location.pathname + window.location.search + '#' + path
    );
    }
    }
    // createMemoryHistory的内部实现
    entries = [];
    function finishTransition(location) {
    ...
    switch (location.action) {
    case 'PUSH':
    entries.push(location);
    break;
    case 'REPLACE':
    entries[current] = location;
    break;
    }
    }

    检测URL回退

    createBrowserHistory: popstate
    createHashHistory: hashchange
    createMemoryHistory: 因为是在内存中操作,跟浏览器没有关系,不涉及UI层面的事情,所以可以直接进行历史信息的回退

    伪代码实现如下:

    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
    // createBrowserHistory(HTML5)中的后退检测
    function startPopStateListener({ transitionTo }) {
    function popStateListener(event) {
    ...
    transitionTo( getCurrentLocation(event.state) );
    }
    addEventListener(window, 'popstate', popStateListener);
    ...
    }
    // createHashHistory的后退检测
    function startPopStateListener({ transitionTo }) {
    function hashChangeListener(event) {
    ...
    transitionTo( getCurrentLocation(event.state) );
    }
    addEventListener(window, 'hashchange', hashChangeListener);
    ...
    }
    // createMemoryHistory的内部实现
    function go(n) {
    if (n) {
    ...
    current += n;
    const currentLocation = getCurrentLocation();
    // change action to POP
    history.transitionTo({ ...currentLocation, action: POP });
    }
    }

    为了维护state的状态,将其存储在sessionStorage里面:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // createBrowserHistory/createHashHistory中state的存储
    function saveState(key, state) {
    ...
    window.sessionStorage.setItem(createKey(key), JSON.stringify(state));
    }
    function readState(key) {
    ...
    json = window.sessionStorage.getItem(createKey(key));
    return JSON.parse(json);
    }
    // createMemoryHistory仅仅在内存中,所以操作比较简单
    const storage = createStateStorage(entries);
    function saveState(key, state) {
    storage[key] = state;
    }
    function readState(key) {
    return storage[key];
    }

ES6-常用知识

发表于 2017-08-26   |   分类于 FJ

ES6相关

  • let、const与var相比

    使用未声明的变量,直接报错
    重复声明同一个变量,直接报错
    是块级作用域
    必须声明 ‘use strict’ 后才能使用let声明变量

  • 数组的解构赋值,是值的拷贝,不是深拷贝

  • 对象的解构赋值,是值的拷贝,不是深拷贝,let {a: { b }} = {a: { b: 1 }} 给b赋值,此时a是未定义的
  • 箭头函数

    (1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
    (2)不可以当作构造函数,也就是不可以使用new命令,否则会抛出错误。
    (3)不可以使用arguments对象,该对象在函数体内不存在,如果要用,可以使用rest参数代替。
    (4)不可以使用yield命令,因为箭头函数不能用作Generator函数。

    理解定义时所在的作用域。

    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
    var obj = {
    a: 1,
    fn: function () {console.log(this.a)}
    };
    obj.fn(); // 1
    var obj = {
    a: 1,
    fn: () => {console.log(this.a)}
    };
    obj.fn(); // undefined
    var obj = {
    a: 1,
    fn: function () {
    setTimeout(() => {console.log(this.a)}, 0)
    }
    };
    obj.fn(); // 1
    var obj = {
    a: 1,
    fn: function () {console.log(this.a)}
    };
    setTimeout(obj.fn, 0); // undefined
    var obj = {
    a: 1,
    fn: () => {console.log(this.a)}
    };
    setTimeout(obj.fn, 0); // undefined
  • super

    super关键字,既可以当作函数使用,也可以当作对象使用。使用super的时候,必须显式指定是作为函数、还是作为对象使用,否则会报错。由于对象总是继承其他对象的,所以可以在任意一个对象中,使用super关键字。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class A {}
    class B extends A {
    constructor() {
    super();
    /*
    Uncaught SyntaxError: 'super' keyword unexpected here
    无法看出super是作为函数使用,还是作为对象使用
    */
    console.log(super);
    /*
    super.valueOf()表明super是一个对象
    */
    console.log(super.valueOf() instanceof B); // true
    }
    }
    var obj = {
    toString() {
    return "MyObject: " + super.toString();
    }
    };
    obj.toString(); // MyObject: [object Object]

    第一种情况,当作函数使用,代表父类的构造函数,super虽然代表了父类的构造函数,但是返回的是子类的实例,即super内部的this指的是子类。super()只能用在子类的构造函数中,用在其它地方就会报错。ES6要求,子类的构造函数必须执行一次super函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class A{}
    class B extends A {
    constructor() {
    /*
    当作函数使用,代表父类的构造函数,相当于
    A.prototype.constructor.call(this)
    */
    super();
    }
    }

    第二种情况,super作为对象时,在普通方法中指向父类的原型对象,在静态方法中指向父类。ES6规定,通过super调用父类的方法时,super会绑定子类的this。由于绑定子类的this,所以如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性。

    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
    class A {
    constructor() {
    this.q = 2;
    }
    p() {
    return 2;
    }
    print() {
    /*
    通过B的super.print()虽然调用的是A.prototype.print(),但是A.prototype.print()会绑定子类B的this,相当于
    super.print.call(this)
    */
    console.log(this.q);
    }
    }
    class B extends A {
    constructor() {
    super();
    this.q = 3;
    /*
    当作对象使用,这是普通方法,super指向A.prototype,相当于
    A.prototype.p()
    q是父类A实例的属性,super.q就引用不到它
    */
    console.log(super.p()); // 2
    console.log(super.q); // undefined
    }
    subPrint() {
    super.print();
    }
    }
    let b = new B();
    b.subPrint(); // 3
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class Parent {
    static myMethod(msg) {
    console.log('static', msg);
    }
    myMethod(msg) {
    console.log('instance', msg);
    }
    }
    class Child extends Parent {
    static myMethod(msg) {
    // 静态方法,super指向父类
    super.myMethod(msg);
    }
    myMethod(msg) {
    // 普通方法,super指向父类的原型对象
    super.myMethod(msg);
    }
    }
    Child.myMethod(1); // static 1
    var child = new Child();
    child.myMethod(2); // instance 2

JavaScript-AJAX

发表于 2017-08-26   |   分类于 FJ

http://www.w3school.com.cn/jquery/ajax_ajax.asp

AJAX(Asynchronous JavaScript and XML),最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。编写常规的AJAX代码并不容易,因为不同的浏览器对 AJAX 的实现并不相同,不过jQuery为我们解决了这个难题。

jQuery AJAX 使用

$.ajax([settings]) 返回其创建的 XMLHttpRequest 对象,大多数情况下你无需直接操作该函数,除非你需要操作不常用的选项以获得更多的灵活性。参数对象settings可选,参数中的所有的选项都可以通过$.ajaxSetup() 函数来全局设置。

  • global: Boolean 默认为true,表示触发全局AJAX事件

  • options: Object 可选,AJAX请求设置

  • async: Boolean 默认为true,表示异步请求

  • cache: Boolean 默认为true,dataType为script和jsonp时默认为false,false表示不缓存此页面

  • ifModified: Boolean 默认为false,true表示仅在服务器数据改变时获取新数据

  • timeout: Number 设置请求超时时间(毫秒),此设置将覆盖全局设置

  • traditional: Boolean 如果想要用传统的方式来序列化数据,那么就设置为true

  • processData: Boolean 默认为true,true表示通过data选项传递进来的数据会被转化成一个查询字符串

  • context: Object 设置AJAX相关回调函数的上下文,也就是回调函数内的this,如果不设定那么this指向调用本次AJAX请求时传递的options参数

  • username: String 用于响应 HTTP 访问认证请求的用户名

  • password: String 用于响应 HTTP 访问认证请求的密码

  • contentType: String 默认为”application/x-www-form-urlencoded”,表示发送信息至服务器时内容编码类型

  • dataType: String 预期服务器返回的数据类型,如不指定jQuery将自动根据HTTP包MIME信息来智能判断,可用值:”xml”、”html”、”script”、”json”、”jsonp”、”text”

  • type: String 默认为GET,可选”POST”或”GET”

  • headers: Object 设置请求头

  • url: String 发送请求的地址,默认为当前页地址

  • data: String 发送到服务器的数据、请求参数,将自动转换为请求字符串格式,processData 选项可禁止此自动转换,GET请求中将附加在URL后

  • jsonp: String 在一个jsonp请求中重写回调函数的名字,来替代在”callback”

  • jsonpCallback: String 为jsonp请求指定一个回调函数名,将用来取代jQuery自动生成的随机函数名。想让浏览器缓存GET请求的时候,指定这个回调函数名

  • scriptCharset: String 只有当请求时dataType为”jsonp”或”script”,并且type是”GET”才会用于强制修改 charset。通常只在本地和远程的内容编码不同时使用。

  • xhr: Function 需要返回一个 XMLHttpRequest 对象,默认在 IE 下是 ActiveXObject 而其他情况下是 XMLHttpRequest 。用于重写或者提供一个增强的 XMLHttpRequest 对象。

  • beforeSend: Function(XMLHttpRequest) 发送请求前可修改XMLHttpRequest对象的函数,如添加自定义HTTP头。参数为XMLHttpRequest。如果返回false可以取消本次ajax请求

  • complete: Function(XMLHttpRequest, TS) 请求完成后回调函数(请求成功或失败之后均调用),参数为XMLHttpRequest 对象和一个描述请求类型的字符串

  • error: Function(XMLHttpRequest, textStatus, errorThrown) 请求失败时调用此函数,参数为XMLHttpRequest对象、错误信息、捕获的异常对象

  • success: Function(responseData, TS) 请求成功后的回调函数,参数为根据dataType参数进行处理后的数据和描述状态的字符串。

  • dataFilter: Function(data, type) 给Ajax返回的原始数据的进行预处理的函数,data是返回的原始数据,type是dataType参数

示例如下:

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
<html>
<head>
<script type="text/javascript" src="/jquery/jquery.js"></script>
<script type="text/javascript">
$(document).ready(function(){
$("#b01").click(function(){
// 同步
htmlobj=$.ajax({url:"/jquery/test1.txt",async:false});
$("#myDiv").html(htmlobj.responseText);
// 异步
$.ajax({
url: "/jquery/test1.txt",
success: function(responseData) {
$("#myDiv").html(responseData);
}
});
});
});
</script>
</head>
<body>
<div id="myDiv"><h2>通过 AJAX 改变文本</h2></div>
<button id="b01" type="button">改变内容</button>
</body>
</html>

AJAX实现

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 ajax(options) {
options = options || {};
options.type = (options.type || "GET").toUpperCase();
options.dataType = options.dataType || "json";
var params = formatParams(options.data);
var xhr;
//创建 - 第一步
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else if(window.ActiveObject) {//IE6及以下
xhr = new ActiveXObject('Microsoft.XMLHTTP');
}
//连接 和 发送 - 第二步
if (options.type == "GET") {
xhr.open("GET", options.url + "?" + params, true);
xhr.send(null);
} else if (options.type == "POST") {
xhr.open("POST", options.url, true);
//设置表单提交时的内容类型
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(params);
}
//接收 - 第三步
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
var status = xhr.status;
if (status >= 200 && status < 300 || status == 304) {
options.success && options.success(xhr.responseText, xhr.responseXML);
} else {
options.error && options.error(status);
}
}
}
}
//格式化参数
function formatParams(data) {
var arr = [];
for (var name in data) {
arr.push(encodeURIComponent(name) + "=" + encodeURIComponent(data[name]));
}
arr.push(("v=" + Math.random()).replace(".", ""));
return arr.join("&");
}

GET跨域提交表单

前台代码:

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
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>跨域</title>
<script src="./jquery.min.js"></script>
<script>
function flightHandler(data) {
console.log('2: ',data)
}
window.onload = function() {
$.ajax({
url: "http://localhost:9432",
type: "get", // jsonp必须是get方式,post不支持,注意
dataType: "jsonp",
jsonp: "callback", // 回调函数名称变量名
jsonpCallback: "flightHandler", // 回调函数名称的值
data: {param: '参数'},
beforeSend: function () {
console.log('1');
},
success: function (msg) {
console.log('3: ',msg);
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
console.log('4: ',XMLHttpRequest, textStatus, errorThrown);
}
});
};
</script>
</head>
<body>
</body>
</html>

后台代码:

1
2
3
4
5
6
7
8
9
10
11
12
var http = require('http'),
fs = require('fs'),
url=require("url");
http.createServer(function(req, res) {
var query = url.parse(req.url, true).query;
var func = query.callback;
console.log(query);
var msg = `${func}({"txt": "success"})`;
res.write(msg);
res.end();
}).listen(9432);

运行结果为:

1
2
3
1
2: {txt: "success"}
3: {txt: "success"}

POST跨域提交表单

CORS(跨域资源共享,Cross-Origin Resource Sharing),定义一种跨域访问的机制,可以让AJAX实现跨域访问。CORS允许一个域上的网络应用向另一个域提交跨域AJAX请求。实现此功能非常简单,只需由服务器发送一个响应标头即可。

header(“Access-Control-Allow-Origin: *”);
header(“Access-Control-Allow-Origin: http://www.test2.com“);

CORS提供了一种跨域请求方案,但没有为安全访问提供足够的保障机制,如果需要信息的绝对安全,不要依赖CORS当中的权限制度,应当使用更多其它的措施来保障,比如OAuth2。CORS使用场景:

  • CORS在移动终端支持的不错,可以考虑在移动端全面尝试
  • jsonp是get形式,承载的信息量有限,所以信息量较大时CORS是不二选择
  • 配合新的JSAPI(fileapi、xhr2等)一起使用,实现强大的新体验功能

前台代码:

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
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>跨域</title>
<script src="./jquery.min.js"></script>
<script>
window.onload = function() {
$.ajax({
url: "http://localhost:9432",
type: "post",
dataType: "json",
data: {param: '参数'},
success: function (msg) {
console.log('3: ',msg);
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
console.log('4: ',XMLHttpRequest, textStatus, errorThrown);
}
});
};
</script>
</head>
<body>
</body>
</html>

后台代码:

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
var http = require('http'),
fs = require('fs'),
url=require("url");
http.createServer(function(req, res) {
res.writeHead(200,{"Access-Control-Allow-Origin":"*"});
// res.setHeader("Access-Control-Allow-Origin","*");
// 设置接收数据编码格式为 UTF-8
req.setEncoding('utf-8');
//POST & GET : name=zzl&email=zzl@sina.com
var postData = "";
// 数据块接收中
req.addListener("data", function (postDataChunk) {
postData += postDataChunk;
});
// 数据接收完毕,执行回调函数
req.addListener("end", function () {
console.log('数据接收完毕');
//GET & POST ////解释表单数据部分{name="zzl",email="zzl@sina.com"}
var params = querystring.parse(postData);
console.log(params);
});
var msg = `{"txt": "success"}`;
res.write(msg);
res.end();
}).listen(9432);

1…101112…17
© 2021 小朱
由 Hexo 强力驱动
主题 - NexT.Pisces