MinimalistYing.io

HTTP响应中的Date首部应该表示原始服务器最初产生这个对象的日期

可以借助 position: sticky 来快速实现元素滚动吸顶功能

Fetch API 目前为止还不提供对请求超时的相关设置,如果有需要的话可能得自己实现

visibility 属性可继承,借助这个特性可以实现隐藏父节点,显示子节点的效果。

源IP地址、目标IP地址、源端口号、目标端口号这四个值一起唯一地定义了一个TCP连接

Vue中对进行双向绑定的数据需进行初始化(包括向组件中传递的数据),否则会导致双向绑定失效

IE8 中的伪元素只支持类似 :after 的写法,不支持 ::after 的写法

HTTP中的URL中没有具体表明端口号时,默认访问80端口,而HTTPS中的默认端口号为443

::selection可用于改变文字选中时的字体颜色和背景色,IE9及以上和现代浏览器兼容

全角空格占位符   可以完美的用作一个中文字符大小的空白
不受字体大小变动影响

可以使用 <pre> 来展示代码 / JSON 等内容
因为该标签内等空格换行会被完整保留

关于浏览器的同源策略:域名(需各级域名完全相同)、协议、端口号都相同称为同源,非同源的请求会存在跨域问题

匹配中文字符(简繁体都包含)的正则 /^[\u4e00-\u9fa5]+$/ 暂时无法确认其是否完全正确

z-index 属性只有设置在定位元素也就是 position 不是 static 的元素上才会生效

可以使用Object的 hasOwnProperty() 方法来检测一个属性是该对象独有还是由原型链继承而来

正则表达式中的 . 可以用于匹配除换行符外的所有字符,如果想匹配含换行符在内的所有字符可以使用 [\s\S]

Javascript的变量名允许使用Unicode字符集中的所有字母和数字,所以类似 var 变量 = 1 也是合法的

类似 input/img/iframe 等内部无法容纳其它内容的元素,无法利用伪元素 ::after/::before 来实现特定样式

实现文字模糊效果

{
    color: transparent;
    text-shadow: #111 0 0 5px;
}

HTTP的版本号不会被当作小数来比较,而是每个数字都单独处理, 所以假设将来有这么俩个版本HTTP/2.22和HTTP/2.3, 前者的版本更高

可以通过

filter: Alpha(opacity = ?)

来在 IE7-8 中兼容 CSS3 的 opacity 属性

通过在Response Header中配置 Strict-Transport-Security 可以实现将请求的页面强制重定向至HTTPS协议访问

<input type="file" /> 只能是 Uncontrolled Component
因为在前端文件只能通过用户交互来选择,不能在程序中控制

在开启Webpack devServer 遇到问题时可以路由至URL /webpack-dev-server 来观察打包出来的bundle文件详情来Debug

Vue-Router中路由配置的redirectalias区别在于前者会将地址栏到URL重定向到新的而后者不改变URL, 使得不同的URL也可以绘制同样的组件

在Vue中使用Scoped Style时最好采用类选择器或Id选择器,这样会使与属性选择器连用 (PostCSS实现Scoped Style的方式)时造成的性能损失最小

IE8 切换为兼容性视图模式时会将原 User-Agent 中包含的 MSIE8.0 转变为 MSIE7.0
所以在通过 UA 来判断 IE 版本时尤其要注意

在HTML中属性可以用双引号、单引号、甚至不用引号包围,浏览器都是支持的。 所以Google为了节省字节会采用不用引号的风格,大概对访问量极大的网站这也是一种省钱的方式吧

当页面的UI需要在后台数据返回后进行刷新时,一定要考虑到网络极差的情况下,请求会延迟很久后返回。 这期间UI要怎样展示,或者用户能否进行操作,会不会有遗留的表单数据等等。

由于TCP具有慢启动特性(不能在连接的开始就将所有的IP分组一次发出, 而是只有在一个分组成功确认后才有发出其它俩个分组的权限), 所以新连接的速度一般会比已建立好的连接要慢

在自测与后台有交互,会发送请求的地方时一定要注意在Chrome的Network中观察发送请求的URL、参数等是否符合预期, 同时也要注意考虑请求返回失败或返回空结果时页面UI的展示

ES7 引入了新的指数计算操作符**
可以用于替代以往使用的Math.pow()

Math.pow(2, 3) // 4
2 ** 3 //8

通过 Element.requestFullscreen() 以及 Document.exitFullscreen()
可以将页面上的内容进行全屏展示以及取消全屏展示

一种提高使用transform以及opacity来做过度效果性能的思路FLIP

使用 Fetch API 可以通过 res.ok === true 来判断请求是否成功
相当于 res.status >= 200 && res.status < 300

AntD 表格组件 columnsfilteredValue 字段只接受字符串数组
要注意把其它类型的 ID 转为字符串后再传入,否则会导致筛选项的多选框回填出现问题

使用 $(dom).html('...') 时需注意防范脚本注入攻击,如果传入的字符串中包含可由用户填写的字段需要先进行转义,更好的办法是尽量使用 $(dom).text('...')

Javascript 中一共有六种种原始类型( primitive type )
string/boolean/number/null/undefined/symbol( ES6 新增 )

在Javascript中尝试去获取对象的某个属性值时,如果该对象没有该属性
则会继续在其原型链上查找直至 Object.prototype ,如果都没有找到才会返回 undefined

在使用ES6的Default Parameter时需要注意
调用函数时如果希望传入空参数应该传 undefined 而不是 null
例如 foo(undefined, 66)

利用Function.prototype更快捷的创建一个空函数

var cb = Function.prototype; // 相当于 var cb = function(){}

通过CSS3的 vh(当前视窗高度百分比) vw(当前视窗宽度百分比) vmin vmax 这几个熟悉来实现基于浏览器视窗高度的布局,例如全屏遮罩,左侧导航100%自适应当前视窗高度等

HTML5新增了 input 事件来监听文本框的输入变化,但在IE9下存在用户删除输入或剪切文本时不会触发该事件的Bug, 并且IE8下没有该事件,需同时监听 propertychange 来实现兼容IE8

// 因为都是构造函数?
typepf Object // => function
typeof Array // => function
typeof Symbol // => function

使用 Fetch API 时 mode 设为 no-cors|same-origin 会导致请求不会发出
浏览器会报错 failed to fetch 并且在 network 中也看不到请求的相关信息

可以借助\来实现跨行书写单行字符串
ES6的Template String也支持这种写法

const str = 'a\
b\
c'

console.log(str) // => 'abc'

display: inline-block 的元素间有换行时,浏览器的渲染结果会带有间隙。 这是由于浏览器会将这个换行符当作字符,所以会占有一个的字符大小的宽度。 解决方法有很多,个人较喜欢 font-size:0

可以通过给锚点设置一个向上的负偏移量来实现调至锚点位置时不会将锚点至于页面最顶部(避免被顶部所固定的Header遮挡), 类似:

a {
    position: relative;
    top: -66px;
}

关于正则表达式量词(Regexp Quantifier)

  • ? => {0,1}
  • + => {1,}
  • * => {0,}

如果只有一个量词则为贪婪匹配,会尽可能的匹配更多结果。如果量词后附加后缀?则进行非贪婪匹配。

判断点击是否在某个DOM外部发生的思路,判断 event.srcElement(IE) || event.target(FF)
是否是这个DOM节点本身或者是其子元素,这里要注意在内部元素有特殊定位的情况下可能这个思路会有问题

IE8下可采用

filter:
progid:DXImageTransform
.Microsoft
.gradient(startColorstr=xxx,endColorstr=xxx);

来兼容 rgba()

利用Webpack的Code Splitting特性以及Vue的Async Component特性可以很容易的做到按需加载

const Com = () => import('./my-async-compnent')

webpack配置中的 optput.publicPath 也会同时影响到webpack-dev-server去何处读取静态资源, 如果配置错误会导致页面或静态资源无法加载,页面报错404或 Cannot get /index.html

获取浏览器当前滚动条位置可通过 window.scrollY(Chrome Safari FF)||window.pageYOffset(IE9+)
横向位置则通过 window.scrollX||window.pageXOffset

通过代码判断文件是否被压缩

// 学习自 Redux
function isCrushed() {}
if (isCrushed.name === 'isCrushed') console.log('not minified')

Gulp在文件变化时触发回调函数

gulp.watch('...', (event) => {})
// event.path 发生变化文件的路径
// event.type added|changed|deleted|renamed

可以通过

<noscript>
    <p>抱歉 禁用 Javascript 会导致应用不能正常工作</p>
</noscript>

在用户浏览器不支持 Javascript 或者用户手动禁用 Javascript 时展示相关提示信息

当一个元素被设为 display: flex 时,它会被当作一个Flex Container, 而它的所有子元素都会被当作Flex Item,并且这时候在其子元素上设置 float | clear | vertical-align 的值都是无效的

最新的 ES 提案在 Class 内可以通过 # 申明私有属性

class Foo {
    #foo = 5
    #bar = 6
    test() {
        console.log(this.#foo, this.#bar)
    }
}

判断一个变量是否为数字

// 排除 NaN +Infinity -Infinity
function isNumber(a) {
    return typeof a === 'number' && Number.isFinite(a)
}

通过 ES5shim 和 Babel 使用新特性 Class 时如果类并没有继承却在 contructor() 中调用了 super() 会导致在 IE8 下报错 Stackoverflow。谨记如果没有继承关系则不应该调用 super() 方法

Css 伪类也可以结合起来使用,例如

a:link:hover {
    color: red;
}
a:visited:hover {
    color: green;
}

可以使得鼠标悬浮未访问过的链接时字体为红色,悬浮访问过的链接字体为绿色

通过Javascript

element.scrollTop = value
$(dom).scrollTop(value)

去设置滚动条滚动位置时,注意所选取的元素就是设置了

overflow-y: scroll

的元素

小技巧,可以通过俩次取反运算来将 string 形式的整数转为(效率比 parseInt 等高) number
类似 ~~'123'// 123 ,Ps: 处理数字的上限是 Math.pow(2,31) - 1 对超出该值的数字无法正确转化

用于监听CSS3动画结束的事件

  • webkitAnimationEnd// Chrome Safari
  • mozAnimationEnd
  • MSAnimationEnd// IE10
  • oanimationend// Opera
  • animationend

想用 Unicode 动态生成字符时,如果试图通过 const str = '\u' + '0000' 会报错

Invalid Unicode escape sequence

需要通过 String.fromCodePoint() 来实现

HTTP协议其实并未对url的长度做过多的限制,但各实际中各浏览器的实现都有着不同的长度限制, RFC2616建议不应超过255 bytes也就是2040bit,实际中是IE浏览器最为严格url最长为2083个字符。 所以关于在get请求中传数组这种操作还需慎重考虑。

为了使

display: inline-block

在IE8中起作用,必须在文档开头加上

<!DOCTYPE html>
<meta http-equiv="X-UA-Compatible" content="IE=edge">

关于清除浮动的各种方法以及其适用场景
what-methods-of-clearfix-can-i-use

关于URI/URL/URN,URL和URM其实是URI(Uniform Resource Identifier)统一资源定位符的子集。 URL不止指定了资源的地址,同时还指定了获取资源的方式(协议/方法等等)。 而URN则只通过特定的命名空间标识资源,不关注操作获取资源的方式。

Web服务器也可以接受一个目录的URL请求,类似 /dir/ 通过配置服务器可以指定不同的返回形式, 可以返回一个错误,可以默认的去搜索该目录下的index.html并返回, 可以扫描目录返回包含目录内容的html页面(通常这是不安全的,因为这样会把站点的目录结构暴露出来)

关于实现背景透明但文字不透明的效果,首先考虑的是使用 opacity 但其子元素都会继承这个属性, 且无法单独为其子元素设置一个值,所以不可行。如果只是背景色透明的话, 使用 rgba() 来设置透明 background-color 是一种不错的方法,兼容至IE9。

Javascript中的原型是一种动态关系,改变原型的属性会立即对所有该原型链下的对象可见

var a = {}
// a.test => undefined
Object.prototype.test = 'Hello'
// a.test => Hello

关于 NPM 中常见的 Sematic Version Operator
~1.0.1 意味着可以接受大于或等于 1.0.1 但是小于 1.1.0 之间的所有版本
^1.0.1 意味着可以接受大于或等于 1.0.1 但是小于 2.0.0 之间的所有版本

在服务器端将图片(或其它静态资源)请求的 Response Header 中增加 Content-Disposition: attachment 可以实现通过浏览器地址栏输入图片 URL 时自动下载图片,而不是默认的在浏览器中进行内容展示(也就是其默认值 inline 的行为)。

关于函数参数同时采用解构以及默认参数时的细微不同

function test( { x = 1 } = {}, { y } = { y: 1 }) {
    console.log(x,y)
}
test() // 1,1
test({}, {}) // 1,undefined

要注意 Array.prototype.reverse 会将原数组的元素的排序反转,而不是像大多的数据方法那样不改变原数组返回新数组。

const arr = [1, 2, 3]
arr.reverse()
console.log(arr) // => [3, 2, 1]
<meta http-equiv="Content-Type"
    content="text/html; charset=utf-8">
<meta charset="utf-8">

在HTML中俩者都可用来表明页面所采用的字符集,各浏览器的兼容性良好,但后者更短建议采用后者

关于 location.href = 'xx' || location.assign('xx')location.replace('xx') 俩者的区别在于采用前者当前的地址会被计入History中而后者不会,所以通过后者跳转到新页面后无法通过后退返回, 这点在实现某些中间页面跳转页面是会很有用

返回一个只能执行一次的函数

function once(fn) {
    let isCalled = false
    return () => {
        if (!isCalled) {
            isCalled = true
            fn.apply(this, arguments)
        }
    }
}

当一个 position: absolute 的绝对定位元素的父元素的 overflow 值被设为非 visible 时, 会出现该定位元素超出父元素的部分会被遮盖掉无法显示的情况, 暂时对这种问题的解决方式只知道将父元素改为 overflow: visible 或者尽量保证定位元素不会超出父元素的边界

关于ES6 Module

  • 基于文件,每个文件为一个Module,不可能一个文件中包含多个Module
  • 静态,不能动态的去修改一个Module对外export的API
  • 单例,所有的import都是指向同一实例
  • import和export只能出现在一个Module的最顶层,也就是说不能出现在任何块中或函数中

npm安装node-sass报错 %1 is not a valid Win32 application 看了看报错信息大概是说什么东西下载失败导致的, 切换成淘宝镜像用cnmp安装就好了 npm install -g cnpm --registry=https://registry.npm.taobao.org

关于webpack devServer 的 historyApiFallback 在使用 类似 vue-routerreact-router 来开发SPA时, 如果将模式设为history模式需要将此项设为 true 为了将404的页面请求重定向至index.html以显示相应的404错误提示页面

Andriod 部分机型的 WebView 不支持通过

window.location.replace()

来实现无法返回的页面中转操作, 因此建议优先考虑采用 History API 来实现相应功能

window.history.replaceState({}, title, url)

使用React-Router(3.x版本 其它版本估计也一样),如果在 <Router history={xxx}> 上不配置 history 会报错 Uncaught TypeError: Cannot read property 'getCurrentLocation' of undefined 所以这个属性是SPA必配?

对比let o1 = {}以及let o2 = Object.create(null)可以发现
在o2并没有从Object.prototype上继承任何属性o2.__proto__ === undefined,是一个干净的空对象
通过{}创建对象等同于Object.create(Object.prototype)

关于HTML中的相对路径 ./ 是文档相对路径,也就是当前访问页面的路径 / 是基于站点根目录的相对路径, 举例说明访问网址http://0.0.0.0/1/2/son.html

由于node有许多底层依赖包需要依靠c++,所以需要额外安装 node-gyp 提供跨平台的编译支持, 安装之前需要先装好相应的python/c++等环境, 根据官方文档 npm install --global --production windows-build-tools 即可 (很多情况下的npm安装失败可能都是因为这个没装好)

ES6的 import 除了通常的 import xx from 'lib' 外,还可以采用 import 'lib'
将依赖全部引入但不将其赋值给任何变量。在使用webpack引入样式文件时有一些作用
我们可以 import 'xx.less' 而不需要繁琐的 import Style from 'xx.less'

部分 Andriod 4.4.4 版本的机型 WebView 不支持 CSS3 的 transform 加了前缀 -webkit-transform 也不行
但是手机自带的浏览器应该是支持带前缀的形式的,只是 WebView 中不支持
询问 Andriod 的同学得知 WebView 采用的浏览器内核和手机自带浏览器的内核还是有差别的

Webpack 中 resolve.extensions 的默认值为 ['.wasm', '.mjs', '.js', '.json']
当我们自定义这个值时,会把默认规则覆盖
为了确保默认值仍可使用,可以把默认值也加入新定义的规则中
例如 ['.vue', '.wasm', '.mjs', '.js', '.json']

页面上引用静态资源时的相对路径与绝对路径的区别,

  • src='xx.js' 相对于当前页面的路径,
  • src='./xx.js' 相对于当前页面的路径,
  • src='../xx.js' 相对与当前页面的上级路径,
  • src='/xx.js' 相对于根目录路径,
  • src='http//:xx.com/xx.js' 绝对路径

当使用 Uncontrolled Component 时,如果想要指定一个输入框当默认值需要采用 defaultValue

<input defaultValue="默认值" />

因为直接设置 value 会导致输入框的值无法修改,因为 React 当每次 Render 都会根据 value 重新设置输入框的值

在Less 1.x和2.x的版本中会默认的对calc中的数值进行计算,从而导致一些意外的结果,例如
height: calc(100vh - 20px)经less编译后的结果是80vh,很明显与我们想要的不符
为了避免这个问题需要采用height: calc(~"100vh - 20px")这样的写法(Ps: Less 3.x版本已修复这个问题)

Andriod 中调用 WebView 来访问 H5 页面时 HTML5 的 DOMStorage 也就是
localStorage|sessionStorage 默认是关闭的
需要通过 settings.setDomStorageEnabled(true) 来开启
未开启的话会碰到在 H5 中读取 localStorage 为 null 的问题

关于

<meta http-equiv="X-UA-Compatible" content="IE=edge">

详情参看这个回答

Javascript中的整数在超过9007199254740992也就是 Math.pow(2, 53) 时精度无法精确至个位
会出现 Math.pow(2, 53) + 1 === Math.pow(2, 53) 的情况
关于其它数字过大时存在的问题可见这篇Blog

在使用Javascript的 parseInt() 时,最好显示的指明进制,因为 parseInt('0x16') === 22
而你可能期望的结果是 parseInt('0x16') === 0 所以显示的指定进制才能做到真正的结果可控

parseInt('0x16', 16) === 22
parseInt('0x16', 10) === 0

Gulp确保任务按一定顺序执行

gulp.task('second', ['first'], () => {})

Gulp在匹配的文件列表中剔除指定文件

gulp.src(['asset/*.js', '!asset/exclude.js'], () => {})

上述代码会匹配asset目录下除去exclude.js的所有以.js结尾的文件

Webpack中的 url-loaderfile-loader 都是用于打包一些图片字体之类的静态资源文件, 区别在于 url-loader 会对一定大小限制内的图片进行Base64编码并采用DataUrl的形式嵌入页面或css, 这些编码后的图片不会占用HTTP请求。但在图片过大的情况下会增加文件的大小,得不偿失, 更适用于处理一些项目中多处用到的小图片(1kb以下)

考虑如下一种比较情景

if (value === 'a' || value === 'b' || value === 'c')

如果都是字符串的话,可以运用正则使得比较更加优雅

if (/^a|b|c$/.test(value))

如果是变量,可以运用数组来比较

if ([foo, bar, zoo].includes(value))

当一个

display: inline-block

元素的overflow被设为visible以外的值时,它的baseline位置会被从默认的字符x的底线位置修改为下外边沿, 与此同时同一包含块的其它

display : inline-block

元素会被迫向下偏移来和这个元素对齐,遵循IFC(Inline Formatting Contexts)原则

关于React中的Event Handlers传参数有一下俩种方式

<button onClick={(e) => this.func(id, e)}>click</button>

或者

<button onClick={this.func.bind(this, id)}>click</button>

第二种方式下的 e 会默认作为最后一个参数传递

避免浏览器缓存HTML页面可以在head中加上标签

<meta http-equiv='Cache-Control' content='no-store'>

通常会采用 no-cache 的策略,只有在服务器的资源发生变化时才去再重新拉取,否则返回304采用缓存的资源 Ps: 这种方式好像不可靠,不能保证浏览器一定会按照这个规则来执行,最好还是去配置提供静态资源的服务端容器

可以通过 attr() 来在样式中获取到 Dom 上的属性,例如

/*
<p data-foo="hello">world</p>
含有 data-foo 属性的 dom 元素会在之前显示一个与其属性值相同的伪元素
*/
[data-foo]::before {
  content: attr(data-foo) " ";
}

Ps: 这个函数的兼容性好像欠佳,建议谨慎使用

HTML 中的类名 / ID等都建议以字母开头
虽然有少部分浏览器兼容数字或下划线开头
但还是有不少浏览器不支持以数字或下划线开头的CSS选择器
并且通过 document.querySelector() 查找节点时
部分浏览器会报错 Failed to execute 'querySelector' on 'Document': xxx is not a valid selector.

现在 CSS 提供了原生的全局变量支持(IE 不兼容)

:root {
    --somecolor: #666;
    --someshadow: #ddd 0 0 6px;
}

div {
    background: var(--somecolor);
    box-shadow: var(--someshadow);
    color: var(--none, red); /* 支持设置默认值 */
}

关于

String.prototype.split([separator[, limit]])
// 'abc'.split() => ['abc']
// 'abc'.split('') => ['a','b','c']
// 'a,b'.split(/(,)/) => ['a', ',', 'b']
'abc'.split('').reverse().join('') // 字符串倒序

可以通过mask属性来实现对一块区域的遮罩效果 兼容性不佳,目前只有webkit内核支持 Ps:知乎在内容收起时的渐变透明文字遮罩的实现方式

-webkit-mask-image: linear-gradient(#1a1a1a calc(100% - 8rem),transparent calc(100% - 2.8rem));
-webkit-mask-size: 100% 100%;

实现类似改变一个DOM元素的滚动条位置但不触发绑定在上面的onscroll函数
或者改变一个input元素的值不触发绑定在上面的onchange函数的一种思路:在改变值之前先将其绑定的事件函数解绑
改变完成后再将原有函数绑定回元素上注意如果值的改变如果是连续的,也就是这个过程会短时间内重复多次执行时
需要将解绑和绑定操作放在延时函数中执行,避免反复多次的绑定事件和解绑事件消耗过多资源,导致浏览器卡顿

假设有如下俩个锚

<a>不带 href</a>
<a href="www.xxx.com">带有 href</a>

设置如下样式

a:link {
    color: red;
}

只有第二个真正代表链接第锚标签会变为红色
如果通过

a {
    color: red;
}

来设置未访问链接的字体颜色会发现页面上所有的 <a> 都字体都变成了红色

当页面有大量图片需要展示时可以考虑采用Google提出的webp来进行优化
由于兼容性还欠佳所以仍需要做一些降级工作,在浏览器不支持时降级为其它图片格式 相关细节可以看这篇Blog

Edge / IE11 / Safari 好像会试图去识别页面上的数字是否像电话号码
如果像的话会在这些数字下加一个下划线,并使其可点击打开Skype之类的应用拨号(有些邮箱以及地址也同理)
想禁用这一特性可在 <head> 中加上

<meta
    name="format-detection"
    content="telephone=no,email=no,address=no"
>

在 IE8 下如果在 table-layout: auto 的表格中为单元格设置

white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;

想实现单元格内文字过长时出现...并截断多余内容会发现无效,反而表格会被内容撑宽,破坏原有布局。 要想实现这种效果,只能将表格设为

table-layout: fixed

阅读垠神博客有感

  • 会写程序不是一件很了不起的事情,不要自负
  • 语言、框架都只是工具,会用即可,不必过于推崇,重要的是我们脑海里的思想
  • 开发软件也是一种工程,一定要极力避免Bug,这才是一个工程师该做的
  • 面对同事、新人的提问请耐心解答,不要动不动就让提问者自行Google
  • 不要觉得向他人提问是什么难为情的事,世界上总有东西是你不清楚的,哪怕是你日常工作所用的东西
  • 复杂的代码不是显示能力的途径,简单易懂的才是

React-Router采用动态路由的形式时页面报错 The root route must render a single element 可能是因为React组件是采用ES6的 export default 导出, 而React-Router是采用CommonJS来 require 所以需要在导出的组件后加上 .default 类似 require('components/Comp')).default

在用 Vue 开发 IOS WebView 内嵌 H5 SPA 页面时碰到点击APP返回上一页时出现页面白屏的问题
需要滑动一下页面,内容才会显示
具体问题以及解决方式可以参考issue
导致这个问题的主要原因应该还是在返回时仍去异步加载数据,最佳解决方式应该是缓存相应的异步请求数据

moment的国际化资源文件很大,所以在生产环境打包时要留意不要将不必要的国际化文件也包含进来 可以通过在webpack生产环境的配置文件中新增如下插件来解决这个问题

plugins: [
    // 以下的配置会使打包出来的文件只包含简体以及繁体中文的国际化
    new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn|zh-tw/)
]

关于Object.keys()以及Object.getOwnPropertyNames()的区别
相同的是俩者都不会列出从原型上继承的属性key值
区别在于前者只会列出所有可枚举属性的key值,而后者会列出所有属性的key值,包括不可枚举的
所谓不可枚举的属性,即是通过类似
Object.defineProperty(o, 'a', { enumerable: false, value: 0 })定义的属性

Javascript中的Array其实是一种类数组的对象,效率比真正的数组要低,所以会有如下一些奇怪的行为

var arr = [1,2,3]
// arr[0] => 1
// arr['0'] => 1
arr.name = 'Hello'
// arr.name => 'Hello'
arr[10] = 10
// arr[6] => undefined
arr.length = 1
// arr => [1]

读完 Dan Abramov 的 Blog 的感想

  • 即使是一名经验再丰厚的开发者也不是什么都会
    不应该期望一个开发者能熟练的掌握所有技能
  • 但是开发者应该有着各自擅长的专业方向
    并且具备快速学习新技术的能力
  • 开发者需要更深入的去学习理解自己的专精技能
    并且对其充满自信,总有一些方面那些更有经验的开发者也没有你理解的透彻

算法题中经常出现要求输出modulo 10^9+7后的结果
是因为当数字过大时,程序需要特定的算法才能精确的计算
而通过模计算,可以使得不需要实现大数计算的算法便可比较结果的正确性 Stackoverflow

JSX 其实仅仅是一种方便我们写 React App 的语法糖,经过 Babel 编译最后的产出仍是 Vanilla Javascript

const App = <div className="app">Hello World</div>

如让 JSX 编译后其实就是

const App = React.creatElement('div', {
    className: 'app'
}, 'Hello World')

Cache-Control : no-store 禁止代理缓存 Cache-Control : no-cache Pragma : no-cache// 兼容HTTP/1.0 允许缓存,但必须先与服务器进行新鲜度验证,之后才能将内容返回给客户端 Cache-Control : max-age:66,must-revalidate 允许缓存, 并且只有在内容过期后才必须进行新鲜度验证(在缓存过期时即使服务器错误也不会将这个陈旧的缓存返回给客户端)

UslifyJs 不支持压缩 ES6 的代码,所以当我们想不经过 Babel 编译直接压缩混淆 ES6 代码时
需要使用 TerserWebpackPlugin
而不是通常用的 UglifyjsWebpackPlugin

使用Javascript时如果选择在行尾不加上 ; 是比较危险的行为,例如

var arr = [1,2,3]
var b = arr
[2].toString()
console.info(b)

的结果可能会出人意料,自动加分号的结果是

var arr = [1,2,3];
var b = arr[2].toString();
console.info(b);

再第二行以 ( [ + - 开头时都需要注意避免以上情况

关于ES6的Object super关键字

const o1 = {
    foo() { console.log(1) }
}
const o2 = {
    foo() {
        // 只能在Object concise methods 中使用
        // 且只能以super.XXX这种形式调用
        super.foo()
        console.log(2)
    }
}
Object.setPrototypeOf(o2, o1)
o2.foo() // 1 2

如果希望在React组件内部进行路由、页面跳转,可以借助React-Router提供的 withRouter(comp) 之后便可在组件内部通过 this.props.router 来进行跳转。但有时候我们希望在组件外部来跳转, 这就需要借助history来实现

import {browserHistory} from 'react-router'
browserHistory.goBack()
browserHistory.push()

关于 React 中的组件名称为何需要以大写字母开头
因为如下 JSX

<Button />

经 Babel 编译后生成

// 所以要求 Button 必须在作用域中可见
React.creatElement(Button, null)

<button />

编译后生成的是

// 直接生成 <button>  标签
React.creatElement('button', null)

一种将全部元素reset为 box-sizing: border-box 的方法

html {
    box-sizing: border-box;
}
*, *:before, *:after {
    box-sizing: inherit;
}

可能会有更好的方法?详情可见这篇文章

jQuery部分版本(1.10.X 1.8.X 可能还有其它)存在一个很奇怪的Bug, 在HTML标签中使用nodeName作为ID(或者input的name)会导致页面报错 a.nodeName.toLowerCase is not a function 使用nodeType作为ID会导致$(window)发生变化并且绑定在上面的resize事件会失效。 综上所述,以后谨记不要使用nodeName/nodeType/nodeValue作为HEML标签的ID或者name。

通常Web服务器的文件系统会有一个根目录(Document Root)来专门用于存放Web内容, 当服务器收到一个对静态资源的请求时会获取其URI并附加在根目录后去寻找相应的文件, 例如服务器的根目录为 /usr/home 请求为 /test/haha.gif 服务器会在文件系统的 /usr/home/test/haha.gif 目录下去寻找被请求的资源文件。 要注意的是,一个服务器也可能同时提供多个Web站点,它可以通过不同的请求Host或者IP来访问不同的根目录

HTTP常见状态码

  • 200 => 请求成功
  • 301 => 资源永久迁移
  • 302 => 资源临时迁移
  • 303 =>需要去另一个地址获取资源
  • 304 => 资源未发生变化
  • 400 => 请求异常
  • 401 => 未授权
  • 403 => 服务器拒绝请求
  • 404 => 未找到
  • 405 => 不支持的请求方法
  • 408 => 请求超时
  • 414 => 请求URL过长
  • 500 => 服务器错误
  • 502 => 网关故障
  • 504 => 网关超时

CSS 中如下几个伪类选择器中 n 的值不止支持数字类型还支持关键字 (odd / even) 以及公式 (an + b)

  • nth-child(n)
  • nth-last-child(n)
  • nth-of-type(n)
  • nth-last-of-type(n)

例如 p:nth-child(even) 可以选择所有父元素中下标为偶数的 <p> 元素
又例如 p:nth-child(3n+1) 可以选择所有父元素中下标为 3 的倍数加 1 的 <p> 元素

手机端的 H5 页面长按会弹出复制分享的菜单,如果想要禁用 IOS 可以通过

// 这个属性会导致IOS上的input能唤起浏览器键盘 但无法聚焦input框
// 最终结果就是无法正常输入!!!
// 感觉这种禁用需求应该直接予以否决
user-select: none; 
-webkit-touch-callout: none;

Andriod 通过

window.oncontextmenu = e => e.preventDefault()

如何判断一个函数是正常被调用还是通过 new 当作构造函数调用

function Foo() {
    // 严格模式下 this 为 undefined
    if (this === window || typeof this === undefined) {
        console.log('普通调用')
    }

    // 构造函数中的 this 指向新创建的实例
    if (this instanceof Foo) {
        console.log('构造函数调用')
    }    
}

利用好jQuery的事件命名空间可以减少很多需要在一个Dom元素的同一事件上绑定多个不同回调函数时会碰到的问题,例如

$(dom).on('click.a', () => {})
$(dom).on('click.b', () => {})

当出于某种原因需要解绑第一个函数时,只需要$(dom).off('.a') 即可实现, 同时也不会对b命名空间下的绑定事件有任何影响。如果想触发某个特定空间下的事件, 可以通过 $(dom).trigger('click.b') 来实现

关于 $.trim() IE9+应该已经实现了原生的 String.prototype.trim() 低版本浏览器可以使用jQuery的方式来实现Polyfill

function( text ) {
    return text == null ?
        ''
        :
        ( text + '' ).replace( /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '' )
}

其正则中的 \xA0 代表全角空格 \uFEFF 代表BOM头

在使用ES6的Concise Methods时要注意

const o = {
    f() {
        // ....
        f() // Error: f is not a function
    }
}

其实等同于

const o = {
    f: function() {
        // ...
        f() // Error: f is not a function
    }
}

所以如果想要在函数f()通过f()来递归调用函数会导致报错,因为f()其实是一个匿名函数

利用原生的JS即可输出格式化后的JSON字符串

JSON.stringify(value[, replacer[, space]])
// space即是缩进数,默认无缩进,最大为10
// replacer可以是一个过滤函数,用来筛选或替换最后的输出结果

具体参数意义以及接口可见这篇文档

通过webpack引入相关polyfill时要留意,polyfill需要确保在所有bundle之前引入, 而webpack并不会确保主entry中的

import xx from xx

会按顺序引入,所以需要采取在entry中进行类似

app: ['babel-polyfill', './app.js']

这样的形式确保依赖顺序。 详情可参见 ReactIssue

关于 box-sizing: content-box 以及 box-sizing: border-box
前者其实是W3C提出,后者是早期IE6、7quirk mode下的盒模型实现。但后来人们发现其实后者更符合人的逻辑,所以加了这个属性。
对于 content-box 盒子宽度等于 width + padding + border
对于 border-box 盒子宽度就等于所设的 width 减去 padding 以及 border 才是真正展示内容的宽度

关于encodeURI|decodeURI以及encodeURIComponent|decodeURIComponent,俩者都是用于对URI进行编解码操作
区别在于前者默认接受的是一个完整的URL所以不会对所有的字符进行编解码
而后者会对所有需要被编解码的字符进行编解码,例如对http://www.a.com?a=1+1进行encodeURI
不会发生任何变化而进行encodeURIComponent的结果是http%3A%2F%2Fwww.a.com%3Fa%3D1%2B1

在 ES6 的对象方法中使用 super

// 注意只能在采用简写的函数中使用且只能用super,xx()的形式不能用super()的形式
var parent = {
    foo() {
      console.log('parent')
  }
}

var son = {
    foo() {
      super.foo()
    console.log('son')
  }
}

Object.setPrototypeOf(son, parent)
son.foo() // parent son

利用解构实现交换俩个变量的值,优点是无需中间变量

let x = 1, y = 2
;[ y, x ] = [ x, y ];
console.log(x, y) // 2 1

交换数组中不同下标的俩个值

let arr = [1, 2, 3]
// 使用这种方法时最好在前面加上分号 如果你的代码风格是末尾不加分号的话
// 不然有可能会碰到被上一行接着执行的问题
;[arr[2], arr[1]] = [arr[1], arr[2]]
console.log(arr) // [1, 3, 2]

在联调 Andriod WebView 内嵌 H5 页面时发现一个问题,页面的 font-size|line-height 会随着系统字体大小的调整而缩放导致布局错位
比如设置一个div的font-size: 14px当手机字体设为超小时
通过 Chrome inspect WebView 可能会发现 Computed Style 中显示的实际 font-size14*0.86=12.04px
在Andriod端通过webview.getSettings().setTextZoom(100)可完美解决问题

Iframe 内嵌的子页面与父页面间可以通过 postMessage 来相互通信

// 子页面发送
window.parent.postMessage('你好 爸爸', '*')
// 父页面发送
document.getElementsByTagName("iframe")[0].contentWindow.postMessage('你好 儿子', '*')
// 接受页面
window.addEventListener('message', e => console.log(e))

有安全方面顾虑的话最好把 * 改为特定的域名

Sublime 安装问题以及解决

There are no packages available for install

修改 Package Control 设置,增加

"channels":
    [
        "http://cst.stu.126.net/u/json/cms/channel_v3.json"
    ]

无法安装 Package Control

下载 Package Control.sublime-package 放入 Sublime Installed Package 目录下

为了实现隐藏一个元素的滚动条但仍可以在鼠标移入其中时进行滚动操作, 可以通过将其用父元素包裹,并将父元素设为

overflow: hidden;

同时将其向右偏移或者增宽,这样可以使得其滚动条隐藏在父元素之下,类似

<div calss="wrapper" style="overflow: hidden; width: 200px">
    <div class="inner" style="max-height: 10px; overflow: scroll; width: 220px"></div>
</div>

的写法

关于React-Router中 browserHisoryhashHistory 的区别, 前者的URL类似 xx/xx 后者是 /#/xx 由于HTTP协议的约定,URL中 # 后作为片段(frag)不会随请求发送至后台, 所以不需要服务器进行特殊配置,而前者是借助浏览器下的 history API实现, 在IE8/9下会导致跳页时Full Load,并且需要服务器配置接受所有请求都返回 index.html 。 优点时使得站点有清晰干净的URL,并且服务器端渲染只能通过这种方式实现,固推荐使用 browserHistory

Javascript中字符串替换API

const replacement = (match, $1, $2, offset, string) =>{}
// 其中的replacement可以是一个回调函数
String.replace(reg, replacement)

通过种方法可以实现将被匹配的文本做特殊的转化后再替换的功能
具体参数意义以及接口可见这篇文档

利用 React Hooks 模拟 Vue 的计算属性

function App () {
    const [fistName, setFirstName] = useState('F')
    const [lastName, setLastName] = useState('L')

    const fullName = useMemo(() => {
        return `${firstName} ${lastName}`
    }, [firstName, lastName])

    return (
        <div>
            {fullName}
        </div>
    )
}

随意使用通配符来设定样式可能会引来问题,例如有如下样式

* {
    color: red;
}
p {
    color: black;
}

我们希望页面上的段落中字体颜色都为黑色
由于样式继承的存在,假设我们的文档结构如下

<p>...<em>...</em>...</p>

我们会认为 <em> 会继承 <p> 的样式,字体颜色同样为黑色
但结果可能会出乎意料 <em> 的字体颜色会显示为红色
这是因为继承而来的样式没有特殊性,而通配符设置的样式特殊性为 0
哪怕是 0 特殊性仍然会覆盖掉没有特殊性的样式
所以不建议使用通配符来设定页面的样式

jQuery可以通过 $(':visible')/$(':hidden') 来查找可见/不可见的Dom元素 (通过判断元素的height和width是否大于0,所以

opacity:0;
visibility: hidden;

的元素会被认为是可见的)。 这俩种选择器会带来性能上的问题,尽量避免使用, 一定要使用的话也应该先通过纯CSS选择器将目标选出再通过 $(dom).is(':visible')$(dom).is(':hidden') 判断, 或者通过 $(dom).filter(':visible')$(dom).filter(':hidden') 过滤

在 React children 属性有着特殊的含义,所以如下代码

function MyComponent (props) {
    return (
        <p>{props.children}</p>
    )
}

<MyComponent children='舒客舒克'>
    <button>按钮</button>
</MyComponent>

最终显示出来的是按钮而不是舒客舒克,但是如果是按照如下方式调用则会显示舒客舒克

<MyComponent children='舒客舒克' />

由此可见在 React 中 props.children 会优先被传入的子元素覆盖

jQuery核心函数的几种重载形式:

$(($) => {  //文档加载完毕  //相当于$( document ).ready()  })
$('selector') // 选择器
$('selector', parentDom) // 选择器(在父元素的范围下)
$(dom) // 将Dom对象包装成jQuery对象
$(domArray) // 将Dom对象的数组包装成jQuery对象
$() // 1.4以后返回空的jQuery对象
$('<div></div>') // 将Html字符串包装成jQuery对象
$('<div>', {'class': 'a'}) // 生成一个标签并包装成jQuery对象

关于 Webpack output 配置中的 [hash]/[chunkhash]/[contenthash]
[hash] 会在每一构建时都重新生成一个唯一的哈希值,会导致所有的静态资源文件都不会被浏览器缓存
[chunkhash] 会根据不同的 entry 来计算hash值,如果一个 entry 中的文件被修改过则会产生不同的哈希值
缺点在于假设有一个 Css 以及 JS 文件都来自同一 entry 会导致输出中的这俩个文件名包含的哈希值相同
也就是说,如果只变动了 Css 文件也会同时影响到 JS 文件的缓存
[contenthash] 会根据每个输出文件的内容来计算哈希值,只要有过改动则会产生不同的值,推荐使用这个

想实现鼠标悬浮在一个父元素上能触发其子元素在 :hover 下的样式, 之前的思路是通过借助jQuery $(parent).hover(() => $(son).hover())来实现, 今天突然发现原来的方法太复杂,其实只需要几行CSS即可实现想要的效果,类似

.parent:hover .son {
    // 这里是鼠标悬浮在父元素上时子元素的样式
}

先前的思路在使用原生的Javascript时更难实现,因为原生的规范中并没有hover事件, 与之相关的是鼠标的 mouseenter/mouseleave/mousemove 事件, 而即使是在代码中触发了这些事件也是无法触发CSS的 :hover 状态的

在使用

gulp.src(...).pipe(...).pipe(...)

的过程中会发现当出错时控制台中报出的错误信息很难看懂,这是由于在Node.js中stream出错时会抛出error事件, 而上述代码里没有写有关错误事件的处理函数,所以Node会默认的报出堆栈跟踪信息作为错误信息。 如果采取如下捕获异常事件的方式来处理错误

gulp.src().pipe().on('err', () => {})

会使代码变得很复杂,推荐使用Pump的方式来进行处理

var pump = require('pump')
pump([gulp.src(), uglify(), concat()], cb)

在条件语句中申明函数会出现的情况

// 不建议使用这种形式
// 估计许多语法校验工具会视这种写法为错误写法

// 按照ES6的  Block-Scoped Function
// 理论上调用a()和b()时应该报错
if (true) {
    function a(){
        console.log('1')
    }
} else {
    function a(){
        console.log('2')
    }
} 
a() // 1

if (false) {
    function b(){
        console.log('1')
    }
} else {
    function b(){
        console.log('2')
    }
} 
b() // 2

在使用webpack提供的alias特性时,如果配置了eslint的import/no-unresolved规则
会发现eslint并不会识别alias,然后当你使用alias时会报错
这时需要借助eslint-import-resolver-webpack
然后再.eslintrc文件中增加配置项

{
    settings: {
        'import/resolver': {
            webpack: {
                // 配置alias的文件路径
                config: './webpack.base.js'
            }
        }
    }
}

在WebView中动态设置title

setTimeout(() => {
    // 利用iframe的onload事件刷新页面
    document.title = 'xxxxxxxx'
    const iframe = document.createElement('iframe')
    iframe.style.visibility = 'hidden'
    iframe.style.width = '1px'
    iframe.style.height = '1px'
    iframe.onload = () => {
        setTimeout(() => {
            document.body.removeChild(iframe)
        }, 0)
    }
    document.body.appendChild(iframe)
}, 0)

在 HTML 中一个带有初始值的输入框可以简单写作 <input value="hello" />
但在 React 中由于推崇使用 Controlled Component

ReactDOM.render(<input value="hello" />, document.getElementById('root'))

会发现上述代码生成的输入框虽然正确设置了初始值但是用户无法对其进行修改
要修复这个问题需要加上

setTimeout(() => {
    ReactDOM.render(<input value={null} />, document.getElementById('root'))
}, 1000)

也就是在一段时延后将其 value 修改为 null
当然最好的方式还是直接使用 Controlled Component

在试图通过数组的 forEach map 等方法对数组内部存储对值进行修改时需要注意

const a = {val: 1}
const b = {val: 2}
const c = {val: 3}
const arr = [a, b, c]
arr.forEach(o => o.val = 0)
console.log(arr) // [{val: 0}, {val: 0}, {val: 0}]

以上这种修改方式是正确的,因为 o 是作为一个临时变量指向的是每次循环过程中的对象
但是下面这种修改方式就是错误的,因为我们只不过是把临时变量 num 重新赋值了一次而已,并不会对数组本来的数据造成影响

const arr = [1, 2, 3]
arr.forEach(num => num = 0)
console.log(arr) // [0, 0 ,0]

给定一组数 1 2 3 4 5 6 7 8 9 在其间隔处任意加上 + - * / 空白 五种操作符
列出其所有计算结果为 100 的组合

const num = [2, 3, 4, 5, 6, 7, 8, 9]
const operators = ['', '+', '-' , '*', '/']

function recursive(t, i) {
    let str
    for (let operator of operators) {
        str = t + operator + num[i]
        if (i >= 7) {
            if (eval(str) === 100) console.log(str, eval(str))
        } else {
            recursive(str, i+1)
        }
    }
}

// 以 1 为起始进行递归
recursive('1', 0)
const listeners = []
listeners.push(function() {
  console.log(this)
})
listeners.push(function() {
  console.log(this)
})

for (let i = 0; i < listeners.length; i++) {
    const listener = listeners[i]
    listener() // 指向 window
}

for (let i = 0; i < listeners.length; i++) {
    listeners[i]() // 指向 listeners 数组
}

for (let listener of listeners) {
    listener() // 指向 window
}

理解这个问题关键在于认识到数组在 Javascript 中其实只是一种特殊的对象

关于采集站点的PV、UV数据,传统的做法是当页面load完成后像后台发送数据, 当作一次PV但在SPA(Single Page App)以及PWA(Progressive Web App)的情景下,这样的断定方式显得不那么合理, 用户有可能一天中只Load一次页面然后在一天的任意时间段在这个应用中活动 而不需要再一次Load页面考虑用更新的方式进行统计可能比较合理, 例如借助Page Visibility API 在用户初次打开应用浏览以及过去合理的一段时间后(可以用Session持续的最长时间来判断)后 从其它再次切换到这个Tab页都作为一次PV 详情可见这篇Blog

关于 NPM 的 dependencies devDependencies peerDependencies
项目实际依赖的包通常是 dependencies 通过 npm i package --save-prod 或者默认的 npm i package 安装的包会被列在该目录下
仅在开发过程中被使用的依赖是 devDependencies 通过 npm i package --save-dev 会被列在该目录下,类似 babel sass webpack 等通常属于这一类
最后 peerDependencies 通常会在开发一些插件包的时候被使用。例如开发一个 React 的 UI 组件,如果其他开发者想要使用的话必须确保在他的本地环境中已经安装好了使用该 UI 组件的 peerDependencies 例如特定版本的 react 。这样做的好处是可以尽可能的减少不必要的依赖被安装。

关于在 Promise 中使用 return Promise.reject() 以及 return new Error() 的不同

Promise.resolve('a')
.then(res => {
  if (Math.random() > 0.5) {
    return res
  } else {
    return Promise.reject('error')
    // return new Error('error')
  }
}, err => {
  console.error(err + '1 reject')
}).then(res => {
    // 使用 return new Error() 会执行
  console.log(res + '2 fulfill')
}, err => {
    // 使用 return Promise.reject() 会执行
  console.error(err + '2 reject')
})

关于 ES6 新引入的 Regexp Sticky Mode (适用于匹配一串以一定规则重复的字符串)

var reg = /foo/
var regSticky = /foo/y
var str = '***foo***'

reg.test(str) // true
reg.lastIndex = 4
reg.test(str) // true

regSticky.test(str) // false
regSticky.lastIndex = 3 // 只有在lastIndex处完全匹配 才算做匹配成功
regSticky.test(str) // true
console.log(regSticky.lastIndex) // 6 匹配成功会将lastIndex移动至匹配结果后紧接着的index
regSticky.test(str) // false
console.log(regSticky.lastIndex) // 0 匹配失败会将lastIndex重置为0

项目中开发接入支付宝跳转流程时碰到了一个问题
需要通过 Ajax 向后台请求跳转 URL 并通过 window.open() 在新窗口中打开
由于浏览器限制只允许在 Dom 事件处理函数中通过 window.open() 来打开新页面
所以如果直接在请求成功的回调函数中进行操作会发现打开新窗口的操作被浏览器拦截
需要用户确认允许该页面弹窗才能正常跳转
该问题的最终解决方案如下

function onClick() {
    // 先在点击事件中打开原项目的中转页
    const newWindow = window.open('redirect.html', '_blank')
    axios.post('xxx')
    .then(url => newWindow.location.href = url) // 请求成功 将新页面的地址修改为后台返回的 URL
    .catch(err => newWindow.close()) // 请求失败 关闭新开的窗口
}

如果确定一个Component再初始化后不需要重新render,可以在组件中声明

shouldComponentUpdate (nextProps, nextState) {return false;}

这会使React跳过对该组件是否需要重绘的检查,并且跳过调用

  • componentWillUpdate()
  • render()
  • componentDidUpdate() 获得性能上的提升。 还有一种情况下,如果你希望只有在组件的部分属性发生变化时才检查,可以通过在上述方法中比较 nextPropsnextState 中的指定值是否发生变化来实现。 还可以通过继承React提供的 React.PureComponent 来方便的实现上述需求, PureComponent只会对属性进行浅比较,当属性的数据结构复杂,层级较深时比较可能会失败 从而一直返回false导致组件不会发生更新 就最近的经验来看,把一些展示型的组件设为PureComponent可以获得较为明显的性能提升

为什么移动端的点击事件会存在 300ms 左右的延时?

移动端的浏览器支持快速双击缩放页面,如果没有这个延时当用户点击时就无法判断用户是想要双击缩放还是仅仅单击。

mobile browsers will wait approximately 300ms from the time that you tap the button to fire the click event. The reason for this is that the browser is waiting to see if you are actually performing a double tap.

解决方案:

  • 禁用缩放: <meta name="viewport" content="user-scalable=no">
  • 不禁用缩放,更改默认的视口宽度: <meta name="viewport" content="width=device-width, initial-scale=1">
  • 直接使用 FastClick

在各类 Dom 事件中可以通过

// e.path Chrome采用 非标准属性
// e.composedPath() 标准属性 最新的 FF Chrome Safari都兼容
const path = e.path || (e.composedPath && e.composedPath())

// 如果需要兼容更低版本的浏览器 可以自己去遍历
function getPath(e) {
    const path = []
    let dom = e.target || e.srcElement
    while (dom) {
        path.push(dom)

        // 为了和composedPath()行为一致
        if (dom.tagName === 'HTML') {
            path.push(document)
            path.push(window)

            return path
       }

        dom = dom.parentNode
    }
}

去获取这个事件从触发事件的 Dom 节点开始到 Window 的 Dom 路径

JavaScript 实现大数相加

/**
*    在JS中超出Math.pow(2,53) 也就是 9007199254740992 的整数会失去精度
*     包括通过parseInt()无法正确转化 在console中无法直接输出等 只能通过字符串的形式进行操作或传输
**/
// 入参 字符串形式的大数a和b
function sum(a, b) {
    a = a.split('')
  b = b.split('')
  let c = 0
  let result = ''
  while (a.length || b.length || c > 0) {
      c += ~~a.pop() + ~~b.pop() //各位对应相加 结果可能是0~18
    result = c%10 + result
    c = c>9 ? ~~(c/10) : 0 // 处理可能的进位
  }

  return result.replace(/^0+/,'') // 处理以0开头的数字
}

console.log(sum('9007199254740992', '1007199254740992'))

众所周知,页面的加载顺序是从上至下的,并且浏览器中的 JavaScript 为单线程执行
所以当碰到 <script src="xxx"></script> 时,浏览器会等待脚本下载并执行完才继续渲染页面
如果脚本文件很大或者网络较慢就会导致浏览器长时间处于白屏状态,不那么耐心的用户可能会选择直接关闭网页
起初开发者想到的优化办法是尽可能的把 <script> 放到页面底部(也就是 </body> 前)
这样脚本加载便不会影响到页面最初的渲染效率了
HTML5 新引入了 defer 以及 async 来优化这个过程

  • <script defer src="xxx"></script> 会使脚本的下载与页面渲染并行进行
    当页面渲染完毕后才去依次执行下载完的脚本文件
  • <script async src="xxx"></script> 也会使脚本的下载与页面渲染并行进行
    defer 的区别在于一等到脚本下载完成浏览器便会暂停渲染并执行相应的脚本

点击看图

HTTPS SSL/TLS 过程

对称加密和非对称加密

(1) 浏览器发送一个连接请求给服务器;服务器将自己的证书(包含服务器公钥S_PuKey)、对称加密算法种类及其他相关信息返回客户端;

(2) 客户端浏览器检查服务器传送到CA证书是否由自己信赖的CA中心签发。若是,执行4步;否则,给客户一个警告信息:询问是否继续访问。

(3) 客户端浏览器比较证书里的信息,如证书有效期、服务器域名和公钥S_PK,与服务器传回的信息是否一致,如果一致,则浏览器完成对服务器的身份认证。

(4) 服务器要求客户端发送客户端证书(包含客户端公钥C_PuKey)、支持的对称加密方案及其他相关信息。收到后,服务器进行相同的身份认证,若没有通过验证,则拒绝连接;

(5) 服务器根据客户端浏览器发送到密码种类,选择一种加密程度最高的方案,用客户端公钥C_PuKey加密后通知到浏览器;

(6) 客户端通过私钥C_PrKey解密后,得知服务器选择的加密方案,并选择一个通话密钥key,接着用服务器公钥S_PuKey加密后发送给服务器;

(7) 服务器接收到的浏览器传送到消息,用私钥S_PrKey解密,获得通话密钥key。

(8) 接下来的数据传输都使用该对称密钥key进行加密。

IE10+ 以及各现代浏览器提供了原生的方法 btoa 以及 atob 支持对字符串进行 Base64 编解码

// Binary to ASCII 编码
window.btoa('a') // "YQ=="
// ASCII to Binary 解码
window.atob('YQ==') // "a"

需要注意的是这俩个方法只支持 ASCII 编码,所以在处理 UTF-8 编码的字符串时会出现乱码
例如 btoa('我') 会报错

Uncaught DOMException: Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.

解决方法如下(Ps: GitHub 提供部分 API 就是通过这种形式对内容编解码的)

// 编码
window.btoa(unescape(encodeURIComponent(str)))
// 解码
decodeURIComponent(escape(window.atob(str)))

由于 React Component 只允许有一个根结点
所以当碰到当我们当组件由一个列表组成时需要在最外层加上一个额外的 <div>

function Columns() {
    // 如果该组件嵌套在 <tr> 中 => <tr><Columns /></tr>
    // 会导致最终生成的 HTML 不符合标准 => <tr><div><td></td><td></td></div></tr>
    return (
        <div>
            <td></td>
            <td></td>
        </div>
    )
}

现在我们可以借助 React.Fragment 来解决这个问题

function Columns() {
    return (
        <React.Fragment>
            <td></td>
            <td></td>
        </React.Fragment>
    )
}

如果不需要像 Fragment 传递 Props 或者 key, 可以采用简

function Columns() {
    return (
        <>
            <td></td>
            <td></td>
        </>
    )
}

为什么说使用无单位的数字来设置 line-height 最好

<div style="font-size: 12px; line-height: 1.5em; width: 60px;">
    <p style="font-size: 40px;">段落内容</p>
<div>

<div style="font-size: 12px; line-height: 1.5; width: 60px;">
    <p style="font-size: 40px;">段落内容</p>
<div>

上述代码中的俩个 div 样式差别仅在于 line-height
但是可以发现第一个段落中的文字会重叠在一起,因为当使用带单位的值时子元素继承的 line-height 是计算值而不是比例。所以第一个段落的实际计算行高为 18px(12px * 1.5em) 而字体大小为 40px,第二个段落的行高为 60px(40px * 1.5) 字体大小与第一段相同。 所以第一段中的文字会发生重叠,为了避免这种问题通常建议使用无单位的数字来设置 line-height
Ps: line-height 的值最好不要设置小于 1.5 ,这样才能保持文本良好的可读性

JavaScript 实现数组乱序

const arr = [1,2,3,4,5,6,7,8,9,10]

// 错误的方法 以下代码并不能做到真正乱序
// 由于Array.sort()内部的实现方式导致
// Array.prototype.sort(comparefn)
// Calling comparefn(a,b) always returns the same value v when given a specific pair of values a and b as its two arguments.
arr.sort(() => Math.random() - 0.5)

// 进阶班 保证对于相同的a,b arr.sort()比较产生的结果相同
const random = arr.map(Math.random);
arr.sort((a, b) => random[a] - random[b]);

// Fisher–Yates shuffle
let i = arr.length
while(i) {
    const random = Math.floor(Math.random()*i);
  i--;
  [arr[i], arr[random]] = [arr[random], arr[i]]
}

在Javascript中 Objecttruthy value,所以哪怕是 new Boolean(false) 也会在类型转化时被判断为true

false && console.log(1) // false
new Boolean(false) && console.log(1) // 1

这里的关键其实不在于布尔值的判断,而是通过构造函数和直接使用字面量来初始化基本类型的区别。

例如比较如下三种生成字符串的方式:

const str1 = new String('a')
const str2 = 'a'
const str3 = String('a')

// 所以 'a' 和 String('a') 是一样的
str1 === str2 // false
str1 === str3 // false
str2 === str3 // true

// 可以看到这就是最主要的区别,以及后续差异的根本原因
typeof str1 // object
typeof str2 // string

str1.foo = 'foo'
str2.foo = 'foo'
console.log(str1.foo) // foo
console.log(str2.foo) // undefined

关于 JavaScript 中的 Timer setTimeout 以及 setInterval

  • 每次调用会返回一个自增的 ID 用于传入 clearTimeout 以及clearInterval 来清除计时器
  • 由于 JavacScript 是单线程的,所以这俩个函数并不能确保一定会在指定时间到达后立即执行
// 超出 100ms 一段时间后才会输出
// 因为线程被循环阻塞
console.time('执行间隔')
setTimeout(() => console.timeEnd('执行间隔'), 100)

for (let i=0; i<1000000000; i++){}
  • 不传入延时参数时默认为 0ms,哪怕延时 0ms 也是异步,只有主线程空闲时才执行
// 输出顺序为 2 1
// 并不会按正常执行顺序输出
setTimeout(() => console.log(1))

console.log(2)
  • setInterval 所指的间隔并不是指多长时间执行一次,而是多长时间将该函数放到执行队列中一次
    所以当传入其中的函数执行时间超过所设的间隔时间时,函数真实的执行间隔可能为 0ms
let i = 0;
const start = Date.now();
const timer = setInterval(() => {
    i++;
    i === 5 && clearInterval(timer);
    console.log(`第${i}次开始`, Date.now() - start);
    for(let i = 0; i < 100000000; i++) {}
    console.log(`第${i}次结束`, Date.now() - start);
}, 100);

Ps: 还有一个 IE 专属的 setImmediate 可以理解为 setTimeout(0) 的替代,在此不做展开

一种特殊的数组去重方法,不考虑兼容性的话最好直接使用 Array.from(new Set(originArr))

// 该方法有个缺陷
// 不能兼容一些特殊情况 因为JSON.stringify()方法有一些特例
function unique(arr) {
    let obj = {}

  arr.map(item => {
      let key = JSON.stringify(item) + typeof item // 避免基本类型 类似 1与'1' stringify后作为key相同

    obj[key] = item// 利用JS对象的key不能重复的特性
  })

  console.log(Object.values(obj))// 打印结果数组
}
// 注意以下特例
unique([undefined,'undefined',null,'null',NaN,'NaN',Infinity,'Infinity',-Infinity,'-Infinity'])
// 无法进行深度比较 也就无法区分 [1,2,3] 和 [1,2,3] 类似这样的引用类型
function uniqueBySet(arr) {
    console.log(Array.from(new Set(arr)))
}

let test1 = [1,'1',1,true,true,'true']
unique(test1)
uniqueBySet(test1)
let test2 = [[1,2,3], [1,2,3], {a : 1}, {a : '1'}, {b : 1}, {b : 1}]
unique(test2)
uniqueBySet(test2)
let test3 = [undefined, 'undefined', undefined, null, 'null']
unique(test3)
uniqueBySet(test3)
let a = {a : 1}
let test4 = [a , a,  {a:1}]
unique(test4)
uniqueBySet(test4)

初学者在 React 中处理事件时会碰到 this 指向为 undefined 的问题

class Button extends React.Component {
    handleClick() {
        console.log(this)
    }

    render () {
        return <button onClick="this.handleClick"></button>
    }
}

如果在页面上点击上述组件会发现打印出来的是 undefined
当我们想要在函数中通过 this.setState() 去改变组件状态时会报错
解决这个问题可以通过下面三种方式

class Button extends React.Component {
    constructor() {
        // 借助 Function.prototype.bind
        this.handleClick = this.handleClick.bind(this)
    }
    handleClick() {
        console.log(this)
    }

    render() {
        return <button onClick={this.handleClick}></button>
    }
}
class Button extends React.Component {
    // 使用 public class fields syntax 来声明函数
    handleClick = () => {
        console.log(this)
    }

    render() {
        return <button onClick={this.handleClick}></button>
    }
}
class Button extends React.Component {
    handleClick() {
        console.log(this)
    }

    // 借助箭头函数
    // 这种方式的缺点是每次 Button 组件的重绘都需要生成一个新函数
    // 当这个函数被当作 props 传入子组件时 可能会导致不必要的重绘
    // 所以更推荐采用先前的俩种方式
    // 但是当需要传递多余参数当时候可能不得不采用这种这种形式
    // 下面会给出当碰到渲染长列表的性能问题时如何进行优化
    render() {
        return <button onClick={e => this.handleClick(e)}></button>
    }
}

class Alphabet extends React.Component {
    state = {
        num: null
    }
    // 通过将数据存放到 HTML 的 data-num 属性中来避免使用箭头函数引起的性能问题
    handleClick = e => {
        this.setState({
            num: e.target.dataset.num
        })
    }

    render() {
        return (
            <ul>
                {[1,2,3].map(num =>
                    <li key={num} data-num={num} onClick={this.handleClick}>
                        {num}
                    </li>
                )}
            </ul>
        )
    }
}