achen的个人博客

一个能力有限的前端


  • 首页

  • 归档49

  • 公益 404

  • 搜索

Symbol的应用场景

发表于 2020-02-20 | 更新于 2020-05-25

Symbol的应用场景

应用场景1:使用Symbol来作为对象属性名(key)
1
2
3
4
5
6
7
8
9
10
11
12
13
let obj = {
[Symbol()]: 'test',
a: 1,
b: 2,
};

Object.keys(obj); // ['a', 'b']

for (let p in obj) {
console.log(p); // 分别输出: a 和 b
}

Object.getOwnPropertyNames(obj); // ['a', 'b']

由上代码可知,Symbol类型的key是不能通过Object.keys()或者for…in来枚举的,它未被包含对象自身的属性名集合(property names)之中。所以,利用该特性,我们可以把一些不需要对外操作和访问的属性使用Symbol来定义。

应用场景2:使用Symbol来替代常量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const TYPE_AUDIO = 'AUDIO'
const TYPE_VIDEO = 'VIDEO'
const TYPE_IMAGE = 'IMAGE'

function handleFileResource(resource) {
switch(resource.type) {
case TYPE_AUDIO:
playAudio(resource)
break
case TYPE_VIDEO:
playVideo(resource)
break
case TYPE_IMAGE:
previewImage(resource)
break
default:
throw new Error('Unknown type of resource')
}
}

如上面的代码中那样,我们经常定义一组常量来代表一种业务逻辑下的几个不同类型,我们通常希望这几个常量之间是唯一的关系,常量少的时候还算好,但是常量一多,你可能还得花点脑子好好为他们取个好点的名字。

现在有了Symbol,我们大可不必这么麻烦了:

1
2
3
const TYPE_AUDIO = Symbol()
const TYPE_VIDEO = Symbol()
const TYPE_IMAGE = Symbol()
应用场景3:使用Symbol定义类的私有属性/方法

使用的少

彻底搞懂浏览器Event-loop

1. 预备知识

1
JavaScript的运行机制

(1) 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2) 主线程之外,还存在”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
(3) 一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4) 主线程不断重复上面的第三步

概况就是:调用栈中的同步任务都执行完毕,栈内被清空了,就代表主线程空闲了,这个时候就会去任务队列中按照顺序读取一个任务放入到栈中执行。每次栈内被清空,都会去读取任务队列有没有任务,有就读取执行,一直循环读取-执行的操作。

1
JavaScript中有两种异步任务:
  1. 宏任务:script(整体代码),setTimeout、setInterval、setImmediate、I/O、UI rendering
  2. 微任务:process.nextTick(Nodejs)、Promise、Object.observe、 MutationObServer

2. 事件循环(event-loop)是什么?

主线程从”任务队列”中读取执行事件,这个过程是循环不断的,这个机制被称为事件循环。此机制具体如下:主线程会不断从任务队列中按顺序读取任务执行,每执行一个任务都会检查microtask队列是否为空(执行完一个任务的具体标志是函数执行栈为空),如果不为空则会一次性执行完所有microtask。然后进入下一个循环去任务队列中取下一个任务执行。

当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件, 然后再去宏任务队列中取出一个事件。同一次事件循环中, 微任务永远在宏任务之前执行。

3. 为什么会需要event-loop?

因为 JavaScript 是单线程的。单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。为了协调事件(event),用户交互(user interaction),脚本(script),渲染(rendering),网络(networking)等,用户代理(user agent)必须使用事件循环(event loops)。

node中的event-loop与浏览器之间的差异

浏览器和 Node 环境下,microtask 任务队列的执行时机不同

  • Node 端,microtask 在事件循环的各个阶段之间执行
  • 浏览器端,microtask 在事件循环的 macrotask 执行完之后执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
console.log('start')
setTimeout(() => {
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')
})
}, 0)
setTimeout(() => {
console.log('timer2')
Promise.resolve().then(function() {
console.log('promise2')
})
}, 0)
Promise.resolve().then(function() {
console.log('promise3')
})
console.log('end')

// 浏览器 start=>end=>promise3=>timer1=>promise1=>timer2=>promise2

// node start=>end=>promise3=>timer1=>timer2=>promise1=>promise2

iframe的优缺点

发表于 2019-10-09 | 更新于 2020-03-05

iframe的优缺点

iframe的优点:

  1. iframe能够原封不动的把嵌入的网页展现出来。

  2. 如果有多个网页引用iframe,那么你只需要修改iframe的内容,就可以实现调用的每一个页面内容的更改,方便快捷。

  3. 网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe来嵌套,可以增加代码的可重用。

  4. 如果遇到加载缓慢的第三方内容如图标和广告,这些问题可以由iframe来解决。

iframe的缺点:

  1. 会产生很多页面,不容易管理。

  2. iframe框架结构有时会让人感到迷惑,如果框架个数多的话,可能会出现上下、左右滚动条,会分散访问者的注意力,用户体验度差。

  3. 代码复杂,无法被一些搜索引擎索引到,这一点很关键,现在的搜索引擎爬虫还不能很好的处理iframe中的内容,所以使用iframe会不利于搜索引擎优化。

  4. 很多的移动设备(PDA 手机)无法完全显示框架,设备兼容性差。

  5. iframe框架页面会增加服务器的http请求,对于大型网站不是可取的。

iframe迁移问题以及解决方案

  1. import React, { propTypes } from ‘react’;

error: PropTypes is undefined;

出现原因: React在新版本中废弃了集成类似propTypes这种第三方库方案。

解决方案: 新仓库使用的react版本较高,以上方式已被废弃,应使用

1
import PropTypes from 'prop-types';
  1. import ‘./style.less’;

error: 样式失效问题。

出现原因: 新项目中开启了css-module,需要通过配置:global来声明一个全局class,使其在全局起作用。

解决方案1:

1
2
3
4
5
6
7
### style.less

.content {
:global {
.items {}
}
}
1
2
3
4
5
import styles from './style.less';

<div className={styles.content}>
<div className="items"></div>
</div>

解决方案2:

1
2
3
4
5
6
7
# style.less

:global {
.content {
.items {}
}
}
1
2
3
4
import './style.less';
<div className="content">
<div className="items"></div>
</div>

推荐使用方案1,可以利用css-module的命名规则减少项目中样式的冲突问题。方案2改动小但是太暴力,非常可能产生样式冲突问题。

  1. comm和components中组件出现不可用情况。

error: 功能不可用情况。

解决方案: 建议直接使用antd替换,需要花上一点时间对应antd api做一定代码重构。(!这块需要仔细效验功能是否正常)

  1. ec_fe中使用了Component.contextTypes。

例如:

1
2
3
4
Manage.contextTypes = {
history: PropTypes.object.isRequired,
store: PropTypes.object.isRequired
};

Warning: Failed context type: The context history is marked as required in Connect(ContractReview), but its value is undefined.

解决方案:拆分出去的仓库,react-router版本使用的是4.x,已经不需要像老版本一样一层层传递history或者绑定在Context上共享的方式。

  1. ec_fe中使用了this.props.router等路由api
1
2
3
4
5
6
this.props.router.replace({
pathname: `/admin/web/packageconfig/function.html`,
query: {
id: moduleList[0].f_id,
},
});

错误:index.jsx:59 Uncaught TypeError: Cannot read property ‘replace’ of undefined

解决方案:拆分出去的仓库,react-router版本使用的是4.x,请使用react-router-dom提供的api修改。

1
2
3
4
this.props.history.replace({
pathname: `/packageconfig/funModule`,
search: `id=${moduleList[0].f_id}`,
});

参考资料

  • 浅谈iframe
  • react-router-dom

NextTick 原理分析

发表于 2019-06-13 | 更新于 2020-03-05

NextTick 原理分析

nextTick可以让我们在下次DOM更新循环结束之后执行延迟回调,用于获得更新后的DOM。

在Vue2.4之前都是使用的microtasks,但是microtasks的优先级过高,在某些情况下可能会出现比事件冒泡更快的情况,但如果都是macrotasks又可能会出现渲染的性能问题。所以在新版本中,会默认使用microtasks,但在特殊情况下会使用macrotasks,比如v-on。

对于实现macrotasks,会先判断是否能使用setImmediate,不能的话降级为MessageChannel,以上都不行的话就使用setTimeout。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
macroTimerFunc = () => {
setImmediate(flushCallbacks)
}
} else if (
typeof MessageChannel !== 'undefined' &&
(isNative(MessageChannel) ||
// PhantomJS
MessageChannel.toString() === '[object MessageChannelConstructor]')
) {
const channel = new MessageChannel()
const port = channel.port2
channel.port1.onmessage = flushCallbacks
macroTimerFunc = () => {
port.postMessage(1)
}
} else {
macroTimerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}

以上代码用来判断能不能使用相应的API。

注解

  • macrotasks(宏任务): setTimeout, setInterval, setImmediate, I/O, UI rendering
  • microtasks(微任务): process.nextTick, Promise, MutationObserver

git的基本操作

发表于 2019-06-09 | 更新于 2020-05-25

git

  • Rebase 合并
  • stash
  • reflog
  • Reset

Rebase 合并

该命令可以让和 merge 命令得到的结果基本是一致的。

通常使用 merge 操作将分支上的代码合并到 master 中,分支样子如下所示

使用 rebase 后,会将 develop 上的 commit 按顺序移到 master 的第三个 commit 后面,分支样子如下所示

Rebase 对比 merge,优势在于合并后的结果很清晰,只有一条线,劣势在于如果一旦出现冲突,解决冲突很麻烦,可能要解决多个冲突,但是 merge 出现冲突只需要解决一次。

使用 rebase 应该在需要被 rebase 的分支上操作,并且该分支是本地分支。如果 develop 分支需要 rebase 到 master 上去,那么应该如下操作

1
2
3
4
5
## branch develop
git rebase master
git checkout master
## 用于将 `master` 上的 HEAD 移动到最新的 commit
git merge develop

stash

stash 用于临时报错工作目录的改动。开发中可能会遇到代码写一半需要切分支打包的问题,如果这时候你不想 commit 的话,就可以使用该命令。

1
git stash

使用该命令可以暂存你的工作目录,后面想恢复工作目录,只需要使用

1
git stash apply

使用apply命令恢复,stash列表中的信息是会继续保留的

1
git stash pop

使用pop恢复,并且会删除暂存列表

1
git stash list

查看暂存列表

reflog

reflog 可以看到 HEAD 的移动记录,假如之前误删了一个分支,可以通过 git reflog 看到移动 HEAD 的哈希值

从图中可以看出,HEAD 的最后一次移动行为是 merge 后,接下来分支 new 就被删除了,那么我们可以通过以下命令找回 new 分支

1
2
git checkout 37d9aca
git checkout -b new

PS:reflog 记录是时效的,只会保存一段时间内的记录。

Reset

如果你想删除刚写的 commit,就可以通过以下命令实现

1
git reset --hard HEAD^

但是 reset 的本质并不是删除了 commit,而是重新设置了 HEAD 和它指向的 branch。

git 常用操作

$ git ch(checkout) -b develop 创建开发分支develop

$ git push 推送当前分支到远端仓库

$ git st(status) 查看当前分支工作区、暂存区的工作状态

$ git diff diff文件的修改

$ git ci(commit) . 提交本次修改

$ git fetch –all 拉取所有远端的最新代码

$ git merge origin/develop 如果是多人协作,merge同事的修改到当前分支(先人后己原则)

$ git merge origin/master 上线之前保证当前分支不落后于远端origin/master,一定要merge远端origin/master到当前分支

$ git push 推送当前分支到远端仓库

$ git merge –no-ff origin/develop 同事review code之后管理员合并origin/develop到远端主干origin/master


👉 HEAD:当前commit引用$ git version git版本

$ git branch 查看本地所有的分支

$ git branch -r 查看所有远程的分支

$ git branch -a 查看所有远程分支和本地分支

$ git branch -d 删除本地branchname分支

$ git branch -m brancholdname branchnewname 重命名分支

$ git branch 创建branchname分支

$ git checkout 切换分支到branchname

$ git checkout -b 等同于执行上两步,即创建新的分支并切换到该分支

$ git checkout – xx/xx 撤销本文件的更改

$ git pull origin master:master 将远程origin主机的master分支合并到当前master分支,冒号后面的部分表示当前本地所在的分支

$ git pull origin master –allow-unrelated-histories 允许合并两个不同项目的历史记录

$ git push origin -d 删除远程branchname分支

$ git fetch –p 更新分支

$ git status 查看本地工作区、暂存区文件的修改状态

$ git add xx 把xx文件添加到暂存区去

$ git commit -m ‘ ‘ 提交文件 -m 后面的是注释

$ git commit -am(-a -m) 提交所有的修改,等同于上两步

$ git commit ./xx 等同于git add ./xx + git commit

从零配置webpack(基于webpack 4 和 babel 7版本)

发表于 2019-05-25 | 更新于 2020-03-05

从零配置webpack(基于webpack 4 和 babel 7版本)

webpack 核心概念

  • Entry: 入口
  • Module: 模块,webpack中一切皆是模块
  • Chunk: 代码库,一个chunk由十多个模块组合而成,用于代码合并与分割
  • Loader: 模块转换器,用于把模块原内容按照需求转换成新内容
  • Plugin: 扩展插件,在webpack构建流程中的特定时机注入扩展逻辑来改变构建结果或做你想要的事情
  • Output: 输出结果

webpack 流程

webpack启动后会从Entry里配置的Module开始递归解析Entry依赖的所有Module。每找到一个Module,就会根据配置的Loader去找出对应的转换规则,对
Module进行转换后,再解析出当前的Module依赖的Module。这些模块会以Entry为单位进行分组,一个Entry和其所有依赖的Module被分到一个组也就是一个
Chunk。最好Webpack会把所有Chunk转换成文件输出。在整个流程中Webpack会在恰当的时机执行Plugin里定义的逻辑。

最简webpack配置

首先初始化npm和安装webpack的依赖:

1
2
npm init -y
yarn add webpack webpack-cli --dev

配置webpack.config.js文件:

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

module.export = {
entry: path.resolve(__dirname, 'src/index.js'),
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js', // 此处不设置,默认为 main.js
publicPath: '/'
}
}

说明: publicPath上线时配置的是cdn的地址。

使用命令进行打包:

1
webpack --mode production

也可以将其配置到package.json中的 scripts 字段。
入口文件为src/index.js,打包输出到dist/bundle.js。

使用模板html

html-webpack-plugin 可以指定 template 模板文件,将会在 output 目录下,生成 html 文件,并引入打包后的js。
安装依赖:

1
yarn add html-webpack-plugin --dev

在 webpack.config.js 增加 plugins 配置:

1
2
3
4
5
6
7
8
9
10
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
// ... other code
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'src/index.html')
})
]
}

HtmlWebpackPlugin 还有一些其他的参数,如title(html的title),minify(是否要压缩),filename(dist中生成的html文件名)等。

配置 webpack-dev-server

webpack-dev-server 提供了一个简单的Web服务器和实时热更新的能力。
安装依赖:

1
yarn add webpack-dev-server --dev

在 webpck.config.js 增加 devServer 配置:

1
2
3
4
5
6
7
8
9
10
const WebpackDevServer = require('webpack-dev-server');

module.exports = {
// ... other code
devServer: {
contentBase: './dist',
port: '8080',
host: 'localhost'
}
}

在 package.json 的 scripts 字段中增加:

1
dev: 'webpack-dev-server --mode development'

之后,我们就可以通过 npm run dev,来启动服务。
更多关于 webpack-dev-server

支持加载 css 文件

通过使用不同的 style-loader 和 css-loader,可以将 css 文件转换成js文件类型。
安装依赖:

1
yarn add style-loader css-loader --dev

在 webpack.config.js 中增加 loader 的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = {
// ...other code
module: {
rules: [
{
test: /\.css/,
use: ['style-loader', 'css-loader'],
exclude: /node_module/,
include: path.resolve(__dirname, 'src')
}
]
}
}

loader 可以配置以下参数:

  • test: 匹配处理文件的扩展名的正则表达式
  • use: loader的名称
  • include/exclude: 手动指定必须处理的文件夹或屏蔽不需要处理的文件夹
  • query: 为loader提供额外的设置选项

如果需要给loader传参,那么可以使用use + loading的方式,如:

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

支持加载图片

  • file-loader: 解决css等文件中的引入图片路径问题
  • url-loader: 当图片小于limit的时候会把图片base64编码,大于limit参数的时候还是使用file-loader进行拷贝

如果希望图片存放在单独的目录下,那么需要制定outputPath

安装依赖:

1
yarn add url-loader file-loader --dev

在 webpack.config.js 中增加loader的配置(增加在 module.rules的数组中)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module.exports = {
// ...other code
module: {
rules: [
{
test: /\.(gif|jpg|png|bmp|eot|woff|woff2|ttf|svg)/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
outputPath: 'images'
}
}
]
}
]
}
}

支持编译less和sass

现在大家都习惯使用less或者sass编写css,那么也需要在webpack中进行配置。
安装对应的依赖:

1
2
yarn add less less-loader --dev
yarn add sass sass-loader --dev

在 webpack.config.js 中增加 loader 的配置(module.rules数组中)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module.exports = {
// .. other code
module: {
rules: [
{
test: /\.less/,
use: ['style-loader', 'css-loader', 'less-loader'],
exclude: /node_modules/,
include: path.resolve(__dirname, 'src')
},
{
test: /\.scss/,
use: ['style-loader', 'css-loader', 'sass-loader'],
exclude: /nodu_modules/,
include: path.resolve(__dirname, 'src')
}
]
}
}

支持转义 ES6/ES7/JSX

ES6/ES7/JSX 转义需要 Babel 的依赖,支持装饰器

1
yarn add @babel/core babel-loader @babel/preset-env @babel/preset-react @babel/plugin-proposal-decorators @babel/plugin-proposal-object-rest-spread --dev

在 webpack.config.js 中增加 loader 的配置(module.rules 数组中)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module.exports = {
// ...other code
module: {
rules: [
test: /\.jsx?$/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['@babel/react'],
plugins: [
['@babel/plugin-proposal-decorators', {'legacy': true}]
]
}
}
],
exclude: /nodu_modules/,
include: path.resolve(__dirname, 'src')
]
}
}

压缩js文件

安装依赖:

1
yarn add uglifyjs-webpack-plugin --dev

在 webpack.config.js 中增加 optimization 的配置

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

module.exports = {
// ... other code
optimization: {
minimizer: [
new UglifyWebpackPlugin({
parallel: 4
})
]
}
}

以上最新版webpack已经默认开启uglifyjs,不需要单独安装plugins
但是它是单线程压缩的,我们还可以利用webpack-parallel-uglify-plugin,解决多个js打包并行压缩的需要,优化打包效率

1
yarn add webpack-parallel-uglify-plugin --dev

在 webpack.config.js 中增加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const WebpackParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');

module.exports = {
// other code
plugins: [
new WebpackParallelUglifyPlugin({
ugfilyJS: {
output: {
beautify: false, // 是否保留空格和制表符
comments: false, // 是否保留注释
},
compress: {
warnings: false, // 删除没有用到的代码时的警告信息,
drop_console: true, // 是否删除代码中的console语句
}
}
})
]
}

分离css(如果css文件较大的话)

因为CSS的下载和JS可以并行,当一个html文件很大的时候,可以把css单独提取出来加载

1
yarn add mini-css-extract-plugin --dev

在 webpack.config.js 中增加 plugins 的配置,并且将 ‘style-loader’ 修改为 {
loader: MiniCssExtractPlugin.loader
}。
css打包在单独的目录,那么配置filename。

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
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
module: {
rules: [
{
test: /\.css/,
use: [
{
loader: MiniCssExtractPlugin.loader,
}, 'css-loader'
],
exclude: /node_modules/,
include: path.resolve(__dirname, 'src')
},
{
test: /\.less/,
use: [
{
loader: MiniCssExtractPlugin.loader,
}, 'css-loader', 'less-loader'
],
exclude: /node_modules/,
include: path.resolve(__dirname, 'src')
},
{
test: /\.sass/,
use: [
{
loader: MiniCssExtractPlugin.loader
}, 'css-loader', 'sass-loader'
],
exclude: /node_modules/,
include: path.resolve(__dirname, 'src')
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].css'
})
]
}

压缩css文件

安装依赖:

1
yarn add optimiza-css-assets-webpack-plugin --dev

在 webpack.config.js 中的 optimization 中增加配置

1
2
3
4
5
6
7
8
9
10
const OptimizaCssAssetsWebpackPlugin = require('optimiza-css-assets-webpack-plugin');

module.exports = {
// ... other code
optimization: {
minimizer: [
new OptimizaCssAssetsWebpackPlugin()
]
}
}

打包前先清空输出目录

1
yarn add clean-webpack-plugin --dev

在 webpack.config.js 中增加 plugins 的配置

1
2
3
4
5
6
7
8
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
// ...other code
plugins: [
new CleanWebpackPlugin()
]
}

This

发表于 2019-05-17 | 更新于 2020-05-25

this

  1. 默认绑定
  2. 隐式绑定
  3. 硬绑定
  4. new绑定

默认绑定

默认绑定,在不能应用其他绑定规则时使用的默认规则,通常是独立函数调用。

1
2
3
4
5
function sayHi() {
console.log('Hello', this.name);
}
var name = 'achen';
sayHi();

在调用sayHi()时,应用了默认绑定,this指向全局对象(非严格模式下),严格模式下,this指向undefined,undefined上没有this对象,会抛出错误。
上面的代码,如果在浏览器环境中运行,那么结果是 Hello, achen
但是如果在node环境中运行,结果就是Hello, undefined,这是因为node中name并不是挂在全局对象上的。

隐式绑定

函数的调用是在某个对象上触发的,即调用位置上存在上下文对象。典型的形式为 XXX.fun()。

1
2
3
4
5
6
7
8
9
function sayHi() {
console.log('Hello', this.name);
}
var person = {
name: 'achen',
sayHi: sayHi
};
var name = 'achenjs';
persin.sayHi();

硬绑定(显示绑定)

通过call,apply,bind的方式,显示的指定this所指向的对象。
call,apply,bind的第一个参数,就是对应函数的this所指向的对象。call和apply的作用一样,只是传参方式不同。call和apply都会执行对应的函数,而bind方法不会。

1
2
3
4
5
6
7
8
9
10
function sayHi() {
console.log('Hello', this.name);
}
var person = {
name: 'YvetteLau',
sayHi: sayHi
}
var name = 'Wiliam';
var Hi = person.sayHi;
Hi.call(person); // Hi.apply(person)

new绑定

使用new来调用函数,会自动执行下面的操作:

  1. 创建一个新对象
  2. 将构造函数的作用域赋值给新对象,即this指向这个新对象
  3. 执行构造函数中的代码
  4. 返回新对象
1
2
3
4
5
function SayHi(name) {
this.name = name;
}
var Hi = new SayHi('achen');
console.log(Hi.name); // achen

输出结果为achen,原因是因为在var Hi = new SayHi(‘achen’); 这一步,会将SayHi中的this绑定到Hi对象上。

绑定优先级

new绑定 > 显示绑定 > 隐式绑定 > 默认绑定

绑定另外

如果我们将null或者undefined作为this的绑定对象传入call、apply或者是bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。

1
2
3
4
5
6
7
8
var foo = {
name: 'Selina',
}
var name = 'Chirs';
function bar() {
console.log(this.name);
}
bar.call(null); // Chirs
箭头函数

箭头函数是ES6中新增的,它和普通函数有一些区别,箭头函数没有自己的this,它的this继承于外层代码库中的this。箭头函数在使用中,需要注意以下几点:

  1. 函数体内的this对象,继承的是外层代码块的this。
  2. 不可以当做构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
  3. 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用rest参数代替。
  4. 不可以使用yield命令,因此箭头函数不能用作Generator函数。
  5. 箭头函数没有自己的this,所以不能用call、apply、bind这些方法改变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
var obj = {
hi: function() {
console.log(this);
return () => {
console.log(this);
}
},
sayHi: function() {
return function() {
console.log(this);
return () => {
console.log(this);
}
}
},
say: () => {
console.log(this);
}
};
let hi = obj.hi(); // obj
hi(); // obj
let sayHi = obj.sayHi();
let fun1 = sayHi(); // window
fun1(); // window
obj.say(); // window

实现一个promise

发表于 2019-05-15 | 更新于 2020-03-18
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
const PENDING = 'PENDING';
const RESOLVED = 'RESOLVED';
const REJECTED = 'REJECTED';

function MyPromise(fn) {
const that = this;

that.value = null;
that.state = PENDING;

that.resolvedCallbacks = [];
that.rejectedCallbacks = [];

function resolve(value) {
if (value instanceof MyPromise) {
return value.then(resolve, reject);
}

if (that.state === PENDING) {
that.state = RESOLVED;
that.value = value;
that.resolvedCallbacks.map(cb => cb(that.value));
}
}

function reject(value) {
if (that.state === PENDING) {
that.state = REJECTED;
that.value = value;
that.rejectedCallbacks.map(cb => cb(that.value));
}
}

try {
fn(resolve, reject);
} catch(e) {
reject(e);
}
}

MyPromise.prototype.then = function(onFulfilled, onRejected) {
const that = this;
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : r => {
throw r
};

if (that.state === PENDING) {
that.resolvedCallbacks.push(onFulfilled);
that.rejectedCallbacks.push(onRejected);
}
if (that.state === RESOLVED) {
onFulfilled(that.value);
}
if (that.state === REJECTED) {
onRejected(that.value);
}

return that;
}

new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(111);
}, 0);
}).then(value => {
console.log(value);
}).then(value => {
console.log(value);
})

如何写一个jQuery插件

发表于 2019-05-13 | 更新于 2020-05-25

如何写一个jQuery插件

jQuery 插件开发模式

jQuery的插件开发模式主要有三种:

  • 通过$.extend()来扩展jQuery
  • 通过$.fn向jQuery添加新的方法
  • 通过$.widget()应用jQuery UI的部件工厂方式创建

$.extend()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$.fn.extend({
check: function() {
return this.each(function() {
this.checked = true;
}),
}
uncheck: function() {
return this.each(function() {
this.checked = false;
})
}
})

$("input[type='checkbox']").check();

$.fn

1
2
3
4
5
6
7
8
9
10
function myPlugin($ele, options) {};

myPlugin.prototype = {
method1: function() {},
method2: function() {},
};

$.fn.myplugin = function(options) {
new myPlugin(this, options);
}

JS继承的实现方式

发表于 2019-05-11 | 更新于 2020-05-25

JS继承的实现方式

1
2
3
4
5
6
7
8
9
10
11
class Animal {
this.name = 'Animal';

this.sleep = function() {
console.log(this.name + '~~~');
}
}

Animal.prototype.eat = function(food) {
console.log(this.name + ' eat: ' + food);
}

1. 原型链继承

核心: 将父类的实例作为子类的原型

1
2
3
4
5
6
7
8
9
10
function Cat() {}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';

var cat = new cat();
console.log(cat.name); // cat
console.log(cat.eat('fish')); // cat eat: fish
console.log(cat.sleep()); // cat~~~
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true

特点:

  1. 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
  2. 父类新增原型方法/原型属性,子类都能访问到
  3. 简单,易于实现

缺点:

  1. 要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中
  2. 无法实现多继承
  3. 来自原型对象的所有属性被所有实例共享
  4. 创建子类实例时,无法向父类构造函数传参

2. 构造继承

核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

1
2
3
4
5
6
7
8
9
10
function Cat() {
Animal.apply(this); // Animal.call(this);
this.name = 'cat';
}

var cat = new Cat();
console.log(cat.name); // cat
console.log(cat.sleep()); // cat~~~
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true

特点:

  1. 解决了1中,子类实例共享父类引用属性的问题
  2. 创建子类实例时,可以向父类传递参数
  3. 可以实现多继承(call多个父类对象)

缺点:

  1. 实例并不是父类的实例,只是子类的实例
  2. 只能继承父类的实例属性和方法,不能继承原型属性/方法
  3. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

3. 实例继承

核心:为父类实例添加新特性,做为子类实例返回

1
2
3
4
5
6
7
8
9
10
11
function Cat(name) {
var instance = new Animal();
instance.name = name || 'Tom';
return instance;
}

var cat = new Cat();
console.log(cat.name); // Tom
console.log(cat.sleep()); // Tom~~~
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // false

特点:

  1. 不限制调用方式,不管事new 子类()还是子类(),返回的对象具有相同的效果

缺点:

  1. 实例是父类的实例,不是子类的实例
  2. 不支持多继承

4. 拷贝继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Cat() {
var animal = new Animal();
for (var i in animal) {
Cat.prototype[i] = animal[i];
}

Cat.prototype.name = name || 'Tom';
}

var cat = new Cat();
console.log(cat.name); // Tom
console.log(cat.sleep()); // Tom~~~
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true

特点:

  1. 支持多继承

缺点:

  1. 效率较低,内存占用高(因为要拷贝父类的属性)
  2. 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)

5. 组合继承

核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

1
2
3
4
5
6
7
8
9
10
11
12
function Cat(name) {
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat; // 修复Cat的构造函数

var cat = new Cat();
console.log(cat.name); // Tom
console.log(cat.sleep()); // Tom~~~
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true

特点:

  1. 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
  2. 既是子类的实例,也是父类的实例
  3. 不存在引用属性共享问题
  4. 可传参
  5. 函数可复用

缺点:

  1. 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

6. 寄生组合继承

核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Cat(name) {
Animal.call(this);
this.name = name || 'Tom';
}
(function() {
// 创建一个没有实例方法的类
var Super = function() {};
Super.prototype = Animal.prototype;
// 将实例作为子类的原型
Cat.prototype = new Super();
Cat.prototype.constructor = Cat; // 修复Cat的构造函数

})();

var cat = new Cat();
console.log(cat.name); // Tom
console.log(cat.sleep()); // Tom~~~
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true

特点:

  1. 堪称完美

缺点:

  1. 实现较为复杂

修饰器的使用

发表于 2019-05-09 | 更新于 2020-05-25

由于使用了decorator, node不用正常识别,需要使用babel转换

1
$ babel-node test.js

1. 类修饰器(只有一个参数)

target: 指向类,如果是类型是function,则指向MyFunction.prototype

1
2
3
4
5
6
function testable(target) {
target.isTestable = false;
}

@testabel
class MyTestableClass {}

以上代码,@testable就是一个装饰器,它为MyTestableClass这个类添加了一个静态属性isTestable

1
2
3
4
5
6
7
8
9
function testable(isTestable) {
return function(target) {
target.isTestable = isTestable;
target.prototype.name = 'achen';
}
}

@testabel(false)
class MyTestabelClass {}

以上代码,告诉我们@testable何如传递参数,并且如何为类添加原型属性或者方法。

2. 方法修饰器(有三个参数)

target: 方法所在的类
name: 方法名称
descriptor: 描述对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Math {
@log
add(a, b) {
return a + b;
}
}

function log(target, name, descriptor) {
var oldValue = descriptor.value;

descriptor.value = function() {
console.log(`Calling ${name} with`, arguments);

return oldValue.apply(this, arguments);
}

return descriptor;
}

const math = new Math();

math.add(2, 4);

为什么修饰器不能用于函数?

修饰器只能用于类和类的方法,不能用于函数,因为存在函数提升

1
2
3
4
5
6
7
8
9
var conuter = 0;

var add = function() {
counter++;
};

@add
function foo() {
}
1…345
achen

achen

日常复制粘贴,问啥啥不会

49 日志
12 标签
© 2021 achen
由 Hexo 强力驱动 v3.7.1
|
主题 – NexT.Pisces v6.4.0