lennonover


  • 首页

  • 归档

  • 标签

混乱

发表于 2017-12-22

在地下墓穴里有一个死去的女人,

这个死去的女人不愿意死去。

在书架的某一层上,

有一些非常古老的书讲述着一段梦想者所不曾经历的过去。

一段无法忆起的记忆在前世中活动。

幻想、思想和回忆交织在一起。

灵魂梦想着,思想着,然后想象着。

诗人把我们带到了一个临界状态,

接近一个我们害怕超出的边界,介于疯癫与理性,生者与死者。

最轻的声响酝酿着一场灾难。

四面吹来的风酝酿着万物的混乱一片。

喃喃细语和大声喧哗互相伴随。

我们学会了预感的存在论。

我们被放入前听觉中。

我们被要求留心最细弱的迹象。

在这宇宙的边界处,一切都是现象前的迹象。

迹象越是微弱,它就越有意义,因为它预示着起源。

由于所有的迹象都被理解为起源,

它们好像不断地反复重新开始着故事。

我们从中接受了天才的基本教导。

D2-QUIC

发表于 2017-12-17

昨天在参加阿里 D2 的时候听腾讯的黄佳琳老师分享 QQ空间HTTP2加速实践 时她提到在今 QQ 空间 2017年开始使用 QUIC 协议。看到 QUIC 时就想腾讯这么冒进了敢用它!!!至于为什么会这么想,请继续。

QUIC

QUIC 维基百科
Google Develop Live

QUIC 是谷歌在 2013 年实现,2014 推出的一种实验性的传输层网络传输协议。 QUIC 全称是 Quick UDP Internet Connections 就是快速 UDP 网络连接。一个它以UDP为基础,实验性传输层协议。

TCP 和 UDP

web 平台的数据传输都基于 TCP 协议。TCP协议在创建连接之前需要进行三次握手,如果需要提高数据交互的安全性,既增加传输层安全协议(TLS),还会增加更多的握手次数。
正因为TCP协议连接建立的成本相对较高,可以通过TCP快速打开(TCP Fast Open)来减少建立连接时的握手次数。但是该技术目前应用较少。

UDP协议是无连接协议。客户端发出UDP数据包后,只能“假设”这个数据包已经被服务端接收。这样的好处是在网络传输层无需对数据包进行确认,但存在的问题就是为了确保数据传输的可靠性,应用层协议需要自己完成包传输情况的确认。

来源维基百科 : 对于 Google 来说优化 TCP 协议是一个长期目标,QUIC 旨在创建几乎等同于 TCP 的独立连接,但有着低延迟,并对类似 SPDY 的多路复用流协议有更好的支持。 如果 QUIC 协议的特性被证明是有效的,这些特性以后可能会被迁移入后续版本的 TCP 和 TLS 协议(它们都有很长的开发周期)。

TCP 协议的实现是高度管制的。TCP 协议栈通常由操作系统实现,如 Linux、Windows 内核或者其他移动设备操作系统。修改 TCP 协议是一项浩大的工程,因为每种设备、系统的实现都需要更新。

UDP 协议在操作系统层面实现相对简单,基于 UDP 协议实现新的协议以验证 Google 对于 TCP 协议改进的理论,不需要操作系统内核层面的更改,验证成本相对较低。

可以说 QUIC 是夹在 HTTP2 和 UDP 的中间部分就像汉堡。

地位

QUIC 特点

QUIC 协议的主要目的,是为了整合 TCP 协议的可靠性和 UDP 协议的速度和效率。

  • 避免队头阻塞

    HTTP 1.1 协议的并发请求并非是直接解决了 HOC 的问题,而是尽可能减少 HOC 造成的影响。

    SPDY 它采用了多路复用(Multiplexing) 技术也是解决不了这个问题。TCP 协议如果发生了丢包或者错包,数据就会被重传,后面的包就得停下来等这个包重新传输,也就是发生了队头阻塞,TCP 协议的设计者们又发明了滑动窗口,滑动窗口的概念大幅度提高了 TCP 传输数据时抗干扰的能力,一般丢失一两个 ACK 根本没关系。但如果是发送的包丢失,或者出错,窗口就无法向前滑动,出现了队头阻塞的现象。

    队头阻塞不仅仅在 HTTP 层存在,在 TCP 层也存在,这也正是 QUIC 协议要解决的问题。QUIC 协议也采用了多路复用思想。

    QUIC协议直接通过底层使用 UDP 协议天然的避免了 SPDY 和 HTTP/2 协议由于 TCP 协议在处理包时是有严格顺序而遇到前序包阻塞的问题。由于UDP协议没有严格的顺序,当一个数据包遇到问题需要重传时,只会影响该数据包对应的资源,其他独立的资源不会受到影响。

  • 重传与恢复

    丢包恢复一共有两种方法:前向纠错(FEC)和重传。前向纠错可以减少重传,但需要在保重添加冗余信息,用 XOR 实现。如果前向纠错不能回复包,才启用重传,重传的不是旧包,而是重新构造的包。

    前向纠错(FEC):FEC 采用简单异或的方式(也就是相同数字异或成 0,不同数字异或成 1)。每次发送一组数据,包括若干个数据包后,并对这些数据包依次作异或运算,最后的结果作为一个 FEC 包再发送出去。接收方收到一组数据后,根据数据包和 FEC 包即可以进行校验和纠错。

    对于某些重要的数据包,如初始密钥协商时的数据包,在建立连接时非常重要,如果这类包丢失会阻塞整体数据流。QUIC 对于这一类数据包在确认发生丢失前就会尝试重传,通常是等待较短的时间(如20ms)没收到确认后就马上再次发送。这样在网络中会有若干个相同的包同时传输,只要有一个能成功抵达就完成了连接,这样降低了丢包率。接收方对于关键数据包的多次发送和普通数据包的超时重传,都采用相同的重复包处理机制

    QUIC在拥塞避免算法的基础上还加入了心跳包,用于减少丢包率

  • 减少数据包

    QUIC协议在创建连接握手时,只需要1到2个数据包对比传统TCP+TLS协议的传输方式,在创建连接时的4个数据包。

  • TCP 快速打开 0 个 RTT

    核心思想:将当前会话的上下文缓存在客户端。如果以后需要恢复对话,只需要将缓存发给服务器校验,而不必花费一个 RTT 去等待。
    QUIC 握手的过程是需要一次数据交互,0-RTT 时延即可完成握手过程中的密钥协商,比 TLS 相比效率提高了5倍,且具有更高的安全性。 QUIC 在握手过程中使用 Diffie-Hellman 算法协商初始密钥,初始密钥依赖于服务器存储的一组配置参数,该参数会周期性的更新。初始密钥协商成功后,服务器会提供一个临时随机数,双方根据这个数再生成会话密钥。

  • 安全性

    QUIC 协议内置了 TLS 栈,实现了自己的传输加密层,QUIC 对每个散装的 UDP 包都进行了加密和认证的保护,并且避免使用前向依赖的处理方法(,这样每个 UDP 包可以独立地根据 IV 进行加密或认证处理。 QUIC 采用了两级密钥机制:初始密钥和会话密钥。初次连接时不加密,并协商初始密钥。初始密钥协商完毕后会马上再协商会话密钥,这样可以保证密钥的前向安全性,之后可以在通信的过程中就实现对密钥的更新。接收方意识到有新的密钥要更新时,会尝试用新旧两种密钥对数据进行解密,直到成功才会正式更新密钥,否则会一直保留旧密钥有效。

    ​QUIC客户端源码

    QUIC服务端源码

使用

  • 启用

    Chrome 浏览器(版本50.0或更高)中打开地址: chrome://flags/ 在Experimental QUIC protocol 项目勾选 Enabled;然后重启。

    tab

  • 查看站点是否通过 QUIC 通信

    使用Chrome 选项卡 chrome://net-internals/#quic

    sea

  • Chrome 插件

    插件 HTTP/2 and SPDY indicator HTTP/2显示蓝色标志,HTTP/2 + QUIC 显示绿色,可以看到 QQ 空间没用 HTTP/2 + QUIC。

    QQ 空间

总结

  • QUIC 目前处于实验阶段国内”不存在”的协议
  • QUIC 是基于 UDP 的同为传输层
  • UDP 通信,所以服务器上的防火墙要打开。网站或项目的服务器需要有传入 443/UDP 的策略,对于客户端来说,允许 443/UDP 接入互联网
  • 想想就目前国内宽带运营商的各种 UDP 限制 。。

PS: 今天重新看了下黄佳琳老师分享并没有说支持 QUIC 只是说了 2017 在实验 。。

CSS 动画

发表于 2017-10-15

转换(transition)

页面元素从旧的属性值立即变成新的属性值的效果。Transition (转变)能让页面元素不是立即的、而是慢慢的从一种状态变成另外一种状态,从而表现出一种动画过程。

1
2
3
4
5
6
7
8
9
10
transition-property – 什么属性将用动画表现,例如, opacity。       

transition-duration – 转变过程持续时间。

transition-timing-function – 转变时使用的调速函数(比如, linear、 ease-in 或自定义的 cubic bezier 函数)。
linear – 线性函数,返回值一个输入值一样的结果。
ease – 减缓函数, 是缺省值, 等同于 cubic-bezier(0.25, 0.1, 0.25, 1.0).
ease-in – 等同于 cubic-bezier(0.42, 0, 1.0, 1.0).
ease-out – 等同于 cubic-bezier(0, 0, 0.58, 1.0).
ease-in-out – 等同于 cubic-bezier(0.42, 0, 0.58, 1.0)

变形(transform)

- 旋转 rotate(中心为原点)
    transform: rotate(10deg),其中“10deg”表示“10度”。

- 扭曲、倾斜skew(skew(x,y), skewX(x), skewY(y))
    transform: skew(20deg);

- 缩放scale(scale(x,y), scaleX(x), scaleY(y))
    transform: scale(1.5);

- 移动translate(translateX,translateY)
    transform: translate(120px,0),如下表示向右位移120像素,如果向上位移,把后面的“0”改个值就行,向左向下位移则为负“-”。

- 矩阵变形matrix。

关键帧动画

语法:

animation: name duration timing-function delay iteration-count direction;

- animation-name    规定需要绑定到选择器的 keyframe 名称。
- animation-duration    规定完成动画所花费的时间,以秒或毫秒计。
- animation-timing-function    规定动画的速度曲线。
- animation-delay    规定在动画开始之前的延迟。
- animation-iteration-count    规定动画应该播放的次数。
- animation-direction    规定是否应该轮流反向播放动画。

transition 和 animation 主要区别

transition 也可以看做animation 的缩略版,transition 是着重于属性的变化,而animation 重点是在时间轴和关键帧,是在于创建帧,让不同帧在不同的时间节点发生不同变化,基于animation和@keyframe 的动画一方面也是为了实现表现与行为的分离,另一方面也使得前端设计师可以专注得进行动画开发而不是冗余的代码中。

Post 和 Get

发表于 2017-09-24

本文来源自

常规的区别就是大家常说的这些:

  • GET 在浏览器回退时是无害的,而 POST 会再次提交请求。

  • GET 产生的 URL 地址可以被 Bookmark,而 POST 不可以。

  • GET请求会被浏览器主动 cache,而 POST 不会,除非手动设置。

  • GET 请求只能进行 url 编码,而 POST 支持多种编码方式。

  • GET 请求参数会被完整保留在浏览器历史记录里,而 POST 中的参数不会被保留。

  • GET 请求在 URL 中传送的参数是有长度限制的,而 POST 么有。

  • 对参数的数据类型,GET 只接受 ASCII 字符,而 POST 没有限制。

  • GET 比 POST 更不安全,因为参数直接暴露在 URL 上,所以不能用来传递敏感信息。

  • GET 参数通过URL传递,POST 放在 Request body 中。

隐藏了个重大区别

GET 产生一个 TCP 数据包,POST 产生两个 TCP 数据包。

对于 GET 方式的请求,浏览器会把 http header 和 data 一并发送出去,服务器响应200(返回数据);
而对于 POST,浏览器先发送 header,服务器响应 100 continue,浏览器再发送 data,服务器响应 200 ok(返回数据)。

因为 POST 需要两步,时间上消耗的要多一点,看起来 GET 比 POST 更有效。
因此 Yahoo 军规有推荐用 GET 替换 POST来优化网站性能。
但是需要考虑下,因为:

  • GET 与 POST 都有自己的语义,不能随便混用。
  • 据研究,在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。而在网络环境差的情况下,两次包的TCP在验证数据包完整性上,有非常大的优点。
  • 并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次

JS 原生事件

发表于 2017-09-20

JS原生事件

事件流,所谓的事件流就是页面接受事件的顺序。IE 的事件流叫冒泡事件,Netscape 提出了另一个事件流叫事件捕获。事件的三阶段,分别为捕获,目标,冒泡。

  • 捕获阶段:从最上层元素,一直到最下层,你点击的那个 target 元素,路过的所有节点都可以捕捉到这事件
  • 目标阶段:如果给事件成功的到达了 target 元素,它会进行事件处理
  • 冒泡阶段:事件从最下层向上传递,依次出发父元素的该事件处理函数

阻止事件冒泡,可以使用 e.stopPropagation()(Firefox,chrome)或者 e.cancelBubble=true(IE)来阻止事件的冒泡传播。

事件绑定

直接在 DOM 元素上绑定 onclick、onmouseover、onmouseout、onmousedown、onmouseup、ondblclick、onkeydown、onkeypress、onkeyup 等事件,缺点是只能实现一个绑定,也就是说我们再为 element 绑定第二个 click 事件时候,会覆盖掉之前的 click 事件

1
2
3
4
5
6
7
8
// 在 html 中直接写
<button onclick="alert('你点击了这个按钮');"> 点击这个按钮 </button>

// DOM 绑定
var node = document.getElementById('div')
node.onclick = function() {
console.log('click 事件绑定成功')
}

事件监听

事件监听实现的功能和直接绑定差不多新增了一个特点。无论监听多少次,都不会覆盖掉前面的监听事件。本质原因是监听事件每次都会生产一个全新的匿名函数,和前面的函数完全不同。
原生的事件绑定是这样的 btn.addEventListener(“click”,function(event){}, false)
IE 浏览器提供的是 btn.attachEvent(“click”,function(event){}, false)
true 表示捕获阶段调用事件处理程序
false 表示冒泡阶段调用事件处理程序
相应的移除事件绑定这是 removeEventListener 和 detachEvent

1
2
3
4
5
6
7
var div = document.getElementById('div')
div.addEventListener('click', function() {
console.log('事件绑定成功1')
})
div.addEventListener('click', function() {
console.log('事件绑定成功2')
})

事件委托

事件委托是基于上面利用了事件冒泡,只指定一个事件处理程序,优点减少监听器,监听动态内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// html
<ul class="list">
<li></li>
</ul>
// js
var ul = document.querySelector('.list')
list.addEventListener('click',function (e){
var l = e.target
while(l.tagName !== 'LI'){
l = l.parentNode
if(l === ul){
l = null
break;
}
}
if (l) {
console.log('执行回调')
}
})

DOM3 级事件

DOM0 就是直接通过 onclick 写在 html 里面的事件
DOM2 是通过 addEventListener 绑定的事件, IE 下的 DOM2 事件通过 attachEvent 绑定
DOM3 是一些新的事件

判定浏览器是否支持该事件类型

1
2
3
4
var isSupported=document.implementation.hasFeature('HTMLEvents','2.0');
alert(isSupported);//true or false
var isSupported=document.implementation.hasFeature("UIEvent",'3.0');
alert(isSupported);//true or false

第 DOM3 级事件

  • UI 事件:当用户与页面上的元素交互时除法
    load, unload,abord,error,select,resize,scroll
  • 焦点事件:元素获得或失去焦点
    blur、DOMfocusOut、focusout 是事件目标是失去焦点的元素
    focus、DOMfocusIn、focusin 是事件获取到焦点的元素
  • 鼠标事件:通过鼠标在页面上执行操作
    click,dbclick,mousedown,mouseenter、mouseleave、mousemove、mouseout、mouseover、mouseup
  • 滚轮事件:使用鼠标滚轮或类似设备
    mousewheel,即当用户通过鼠标滚轮与页面交互的时候会触发,最终会冒泡到 document 或者 window 上,它包含的 event 对象包含鼠标事件的所有标准信息外,还包含一个特殊的 wheelData 属性,wheelDelta 是 120 的倍数,向前滚动是 120x,向后滚动是 - 120x
  • 文本事件:当用户在文档中输入文本;
  • 键盘事件:通过键盘在页面上执行操作
    1、keydown:用户按下键盘上的任意键时候出发,而且如果按住不放的话就会一直触发,会重复触发这个事件,
    2、keypress:当用户按下键盘上的字符键时触发,
    3、keyup:用户释放键盘上的键时触发
    4、textInput: 是 keypress 的补充,在文本显示给用户之前更容易拦截文本,在文本插入文本框之前会触发该事件,该 event 对象包含一个 data 属性,这个属性是值就是用户输入的字符,event 对象的 inputMethod 属性会判断用户输入到文本框的方式,一共有 0~7 个值,其中常用的就是 0 和 1;

    注:0:表示浏览器不知道怎么输入的,
    1:表示用户使用键盘输入的,2 表示用户是粘贴进来的
    keyCode 的值可以用来表示键盘值,DOM3 之后就不再包含 charCode 了,新增 key 和 char,兼容:keyIdentifier

  • 合成事件:当为 IME(Input Method Editor,输入法编辑器)输入字符时除法;
  • 变动事件:底层 DOM 结构发生变化
    DOM2 级的变动事件能再 DOM 中的某一部分发生变化时触发。
    包括:DOMSubtreeModified、DomNodeInserted、DomNodeRemove、DomNodeInsertIntoDocument 等。
    1、删除节点:removeChild,replaceChild,目标事件:event.target(被删除的节点);event.relateNode(相关节点) 会触发 DOMNodeRemoved 事件,会冒泡
    2、插入节点:appendChild、repalceChild、insertBefore

HTML5 事件

  • contextmenu 事件
    冒泡,这个事件的目标是发生用户操作的元素。通常使用 contextmenu 事件来显示自定义的上下文菜单,而使用 onclick 事件处理程序来隐藏该菜单,
  • beforeunloaded 事件
    为了让开发人员有可能在页面卸载前阻止这一操作
  • DOMContent 事件
    在 window 的 load 事件会在页面中的一切都加载完毕时候触发,但是这个事件在形成 DOM 树的时候就会触发,这个事件会冒泡到 window 但它的目标实际上是 document,它不会提供任何额外的信息(其 target 属性是 document)
  • pageShow、pagehide(Firefox,Opera)
    pageShow 会在 load 事件触发后触发,当页面离开并且点击返回时候,返回到上一个页面的时候会触发,event 事件的 persisted 为 true 的时候页面会保存在 bfchache 中,
    pagehide 事件,该事件会在浏览器卸载的时候触发,会在 unload 事件之前触发,和 pageShow 一样在 document 上触发
  • haschange 事件
    当 URL 的参数列表发生变化的时候触发,(# 后面的所有字符)

Js 异常处理

发表于 2017-09-18

Js 异常

JavaScript 一般有三种错误,语法错误、运行期错误、逻辑错误。

  • 语法错误

语法错误同样也被称为解析错误,对于传统的编程语言,该错误出现先编译的时候,对于 JavaScript 该错误出现在解释时期,最常见比如打了中文分号,语法错误不会通过解析器。

  • 运行时错误

运行期错误也被称为异常,通常在编译或者解释之后运行时会出现。异常出现时会影响进程创建时的正常执行,但是允许对于其他的 JavaScript 进程则可以继续正常执行。

JavaScript 提供了特殊的语句来捕捉异常。

  • try…catch…finally 语句

JavaScript 的最新版本中添加了异常处理的功能。它实现了 try…catch…finally 结构和 throw 操作用来处理异常。 你可以捕获程序员和运行期产生的异常,但是对于用户不能捕获 JavaScript 的语法错误。

1
2
3
4
5
6
7
8
9
10
11
function A() {
throw new Error("A报错");
}
function B() {
try {
A();
} catch(error) {
console.log(error);
}
}
B()
  • throw 语句

用 throw 语句产生一个内置的异常或者你自己定制的异常。之后这些异常可以被捕获,而且捕获后你可以采取合适的操作

1
2
3
4
5
6
7
8
9
function A(a){
if ( a === 0 ){
}else{
var err = new Error("a 不等于0");
err.statusCode = 000;
throw err;
}
}
A(1);
  • onerror() 方法

onerror 事件句柄是 JavaScript 中添加的第一个为了方便错误处理的特性。无论任何时候在网页中产生了异常,窗口对象就会触发 error 事件

  • 错误消息。 浏览器显示给定错误的相关信息。
  • URL。 错误出现的文件。
  • 行数。 在指定的 URL 中造成错误的行数。
    1
    2
    3
    4
    5
    window.onerror = function (msg, url, line) {
    alert("Message : " + msg );
    alert("url : " + url );
    alert("Line number : " + line );
    }

对于异常处理

  • 预期异常:参数不合法,前提条件不符合,通常直接 throw
  • 非预期异常: js 运行时异常,来着依赖库异常
  • 可以直接在异常上面提供一下附加属性来提供上下文

异步回调异常处理

try catch 无法捕获异步队列中抛出的异常,因为执行栈执行完,异步队列才开始执行,所以执行栈无法捕获异步
函数抛出的异常
对于异步函数,我们对异常的处理原则为, 异步函数里面使用自己的try catch
自处理异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function asyncCallbackError(callback) {
setTimeout(function(){
try {
if (发送了异常) {
throw new Error("异步出现了异常");
}
else 没有异常 {
callback(null, value); 无异常的话 第一个参数设置为null 第二个自己的值
}
} catch(error) {
callback(error);
}
}, 0);
}
function callAsync() {
//callback 判断获取异步异常值
asyncCallbackError(function(error, value){
error ? console.log(error) : "执行方法";
});
}
callAsync();

ES5 bing 的实现

发表于 2017-07-12

bind函数的实现

bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的call属性)。当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

实现功能

  • 返回新函数,调用新函数的返回值与调用旧函数的返回值相同
  • 新函数的this指向bind的第一个参数
  • 其余参数将在新函数后续被调用时位于其它实参前被传入
  • 新函数也能使用new操作符创建对象,构造器为原函数

步骤

  • 返回新函数,很简单,return一个包裹着旧函数调用的新函数 .
1
2
3
4
5
6
function bind () {
var t = this
return function () {
return t()
}
}
  • 新函数的this要指向bind的第一个参数,也简单,用js的call或apply函数
1
2
3
4
5
6
function bind (that) {
var t = this
return function () {
return t.call(that)
}
}
  • 其余参数将在新函数后续被调用时在其它实参前被传入,类似实现curry化时通过闭包保存参数
1
2
3
4
5
6
7
8
function bind (that) {
var t = this
var bindArgs = Array.prototype.slice.call(arguments, 1)
return function () {
var callArgs = Array.prototype.slice.call(arguments)
return t.apply(that, bindArgs.concat(callArgs))
}
}

这里的 Array.prototype.slice.apply(arguments) 指的是这个返回函数执行的时候传递的一系列参数,所以是从第一个参数开始 [0] ,之前的 args = slice.apply(arguments,1) 指的是 bind 方法执行时候传递的参数,所以从第二个开始 [1],两则有本质区别,不能搞混,只有两者合并了之后才是返回函数的完整参数

  • 最后要达成的目标是在使用 new 操作符时,旧函数能作为构造器被调用。

函数被 new 实例化之后,需要继承原函数的原型链方法,且绑定过程中提供的 this 被忽略(继承原函数的 this 对象),但是参数还是会使用。这里就需要一个中转函数把原型链传递下去,我们需要看看 new 这个方法做了哪些操作 比如说 var a = new b()。

  • 创建一个空对象 a = {},并且this变量引用指向到这个空对象a
  • 继承被实例化函数的原型 :a.proto = b.prototype
  • 被实例化方法b的this对象的属性和方法将被加入到这个新的 this 引用的对象中: b的属性和方法被加入的 a里面
  • 新创建的对象由 this 所引用 :b.call(a)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function bind (that) {
var target = this
var bindArgs = Array.prototype.slice.call(arguments, 1)
var Empty = function () {}
function bound () {
var callArgs = Array.prototype.slice.call(arguments)
if (this instanceof Empty) {
return target.apply(this, bindArgs.concat(callArgs))
} else {
return target.apply(that, bindArgs.concat(callArgs))
}
}
// 实现继承,让bound函数生成的实例通过原型链能追溯到target函数
// 即 实例可以获取/调用target.prototype上的属性/方法

Empty.prototype = target.prototype
bound.prototype = new Empty()
// 这里如果不加入Empty,直接bound.prototype = target.prototype的话
// 改变bound.prototype则会影响到target.prototype,原型继承基本都会加入这么一个中间对象做屏障
return bound
}

测试

1
2
3
4
5
6
7
8
9
10
11
var test = function(a,b){
console.log('作用域绑定 '+ this.value)
console.log('testBind参数传递 '+ a.value2)
console.log('调用参数传递 ' + b)
}
var obj = {
value:'ok'
}
var fun_new = test.bind(obj,{value2:'also ok'})

fun_new ('hello bind')

跨域的二三事

发表于 2017-03-10

公司的桌面版运用了 iframe 在取数据方面遇到不少坑,周末空闲把知识点整理下。

什么是跨域

JavaScript对安全访问因素的考虑,是不允许js跨域调用其他页面,具体分为以下几类:

  • 不同域名

  • 相同域名不同端口号,如 https://www.baidu.com:8000 和 https://www.baidu.com:8088

  • 同一个域名不同协议,如 https://www.baidu.com 和 http://www.baidu.com

  • 域名和域名对应的的IP,如 http://www.baidu.com:8000 和 http://119.75.217.109

  • 主域和子域,如 http://www.baidu.com 和 https://test.baidu.com

  • 子域和子域,如 https://test1.baidu.com 和 https://test2.baidu.com

解决方案

  • JSONP

JSONP 和 JSONP?
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。
JSONP是JSON with Padding的略称。它是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)–百度
实际是利用了 <script> 标签的 src 属性并不被同源策略所约束,其实所有的 src 属性的标签都是不被同源策略所约束。

1
2
3
4
5
6
7
8
9
10
11
// html
<script type="text/javascript" src="http://127.0.0.1:20002/test.js"></script>


// test.js 内容
<script type="text/javascript">
//回调函数
function callback(data) {
alert(data.message);
}
</script>

这种方案需要注意的是他支持GET这一种HTTP请求类型,还有就是其他域要有一定可靠性。

  • 跨域资源共享(CORS-Cross Origin Resource Sharing)

CORS 它是JSONP模式的现代升级版,CORS 定义一种跨域访问的机制,可以让AJAX实现跨域访问。CORS 允许一个域上的网络应用向另一个域提交跨域 AJAX 请求与JSONP不同的是,CORS除了GET要求方法以外也支持其他的 HTTP要求。浏览器CORS请求分成两种。

  1. 请求方式
    GET
    HEAD
    POST
  2. HTTP的头信息子段
    Accept
    Accept-Language
    Content-Language
    Last-Event-ID
    Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain,其中’text/plain’默认支持,其他两种则需要预检请求和服务器协商。

满足以上两大点的即为简单请求,否则为非简单请求
注:个人认为除了 cors 之外的跨域解决方案 都是耍流氓

  • document.domain + iframe(适用于跨子域的交互)

只需要在A.htm与B.htm里都加上一句document.domain = ‘xxx.com’,两个页面就有了互信的基础,而能无碍的交互。

  • window.name + iframe

类似在 a 页面(a.com/app.html)中创建一个iframe,把其src指向 b 页面(b.com/data.html),数据页面会把数据附加到这个iframe的window.name上。

  • HTML5中的postMessage(适用于两个iframe或两个页面之间)

postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。
eg:

1
2
3
window.onload=function(){
window.frames[0].postMessage('ok','http://baidu.com');
}

postMessage(data,origin)方法接受两个参数:

  1. data 要传递的数据,html5 规范中提到该参数可以是 JavaScript 的任意基本类型或可复制的对象,但是为了兼容性在传递参数的时候需要使用 JSON.stringify() 方法对对象参数序列化。

  2. origin 字符串参数,指明目标窗口的源,协议+主机+端口号[+URL],URL会被忽略,只会将message传递给指定窗口.

优雅的for循环

发表于 2017-03-08

来源

最近做了一个维度筛选的查询项目,复杂的交互写的一脸懵逼,最后总算功能实现,回过头代码重构,满屏的for循环。。。
此时我心里是万马奔腾的,冷静下来整理思路,操作数组的地方能不用循环的全部干掉。

Array.prototype

先来复习洗 Array.prototype 的方法

1
2
3
4
5
6
7
8
9
10
// ES5
Array.prototype.indexOf
Array.prototype.lastIndexOf
Array.prototype.every
Array.prototype.some
Array.prototype.forEach
Array.prototype.map
Array.prototype.filter
Array.prototype.reduce
Array.prototype.reduceRight
  • indexOf

返回在该数组中第一个找到的元素位置,如果它不存在则返回-1

  • lastindexOf

返回在该数组中最后一个找到的元素位置,和 indexof 相反。

  • forEach()

forEach 有一个小缺点:不能使用 break 语句来跳出循环,也不能使用 return 语句来从闭包函数中返回。

1
2
3
4
5
var arr = [1,2,3,4,5,6,7,8];

arr.forEach(function(item,index){
console.log(item);
});
  • map()

对数组的每个元素进行一定操作(映射)后,会返回一个新的数组, map()是处理服务器返回数据时是一个非常实用的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
var oldArr = [
{first_name:"Colin",last_name:"Toh"},
{first_name:"Addy",last_name:"Osmani"},
{first_name:"Yehuda",last_name:"Katz"}];

function getNewArr(){
return oldArr.map(function(item,index){
item.full_name = [item.first_name,item.last_name].join(" ");
return item;
});

}
console.log(getNewArr());

说道map,前两天看到过一个面试题

不用循环,创建一个包含从0到99(n)的连续整数的数组

一般这样写

1
var arr = Array(100).join(' ').split('').map(function(item,index){return index});

Array(100) 创建了一个包含100个 undefined 的数组,但是这样的数组是没法迭代的(参考 forEach 方法的 定义 ),所以要通过字符串转换,覆盖 undefined ,最后调用 map 修改元素值。

有了 es6 ,用 Array.from 的写法是这样的

1
var arr = Array.from({length:100}).map(function(item,index){return index});

Array.from({length:100}) 也是创建了一个包含100个 undefined 的数组,但是这个数组可以迭代( [].slice.call({length:100})创建的不可迭代 ),可以直接调用 map 方法。

不过在性能上却有些尴尬。直接 for 循环性能最好,第二种次之,Array.from 最差。

  • every()

检测数组中的每一项是否符合条件

1
2
3
4
5
var ary = [12,23,24,42,1];
var result = ary.every(function(item, index){
return item > 0
})
console.log(result)
  • some()

检测数组中是否有某一项符合条件

  • for-in
    for-in 设计的目的是用于遍历包含键值对的对象,对数组并不是那么友好。
    for-in 中 index 变量是 “0”、”1”、”3” 等这样的字符串,而并不是数值类型,在某些情况下,上面代码将会以任意顺序去遍历数组元素,太可怕。

  • filter()

filter() 方法创建一个新的匹配过滤条件的数组。

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
// 比如找 "apple"
var heroes = [
{"name":"apple", "count": 2},
{"name":"orange", "count": 5},
{"name":"pear", "count": 3},
{"name":"orange", "count": 16},
];
function isBlackWidow(hero) {
return (hero.name === 'apple');
}

var blackWidow = heroes.filter(isBlackWidow)[0];

// 这段代码的问题是效率不够高,找到之后不会停止
// 我们可以写一个 find 函数来返回第一次匹配上的元素
// for of 是 ES6 引入新的循环遍历语法接下来会说道
function find(predicate, arr) {
for (let item of arr) {
if (predicate(item)) {
return item;
}
}
}

var blackWidow = find(isBlackWidow, heroes);

// JavaScript 已经提供了这样的方法:
var blackWidow = heroes.find(isBlackWidow);
  • for-of

它是遍历数组最简单直接的方法,也可以用于遍历字符串,会正确识别32位 UTF-16字符等等特性与 forEach() 不同的是,它支持 break、continue 和 return 语句。

它还适用于 Map 和 Set 对象(Map 和 Set 是ES6中的新对象)。

1
2
3
4
5
6
7
// 例如,可以用一个 Set 对象来对数组元素去重
var uniqueWords = new Set(words);

// 当得到一个 Set 对象后去遍历该对象
for (var word of uniqueWords) {
console.log(word);
}

for-of 不能直接用来遍历对象的属性,如果你想遍历对象的属性,你可以使用 for-in

  • Iterator

Iterator(遍历器)就是一种机制;任何数据结构只要是部署了 iterator 接口,就可以完成遍历操作

Iterator的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是ES6创造了一种新的遍历命令for…of循环,Iterator接口主要供for…of消费。(阮一峰);

只要是一个对象部署了 Symbol.interator 接口,就可以用 for…of 遍历该对象,同时也可以调用该接口的 Symbol.interator 方法调用 next() 方法对对象进行遍历,不同的是 for..of 是对该对象的值的输出,next() 返回的是对象。

1
2
3
4
5
6
7
8
var arr = ['a','b'];
for (variable of arr) {
console.log(variable) // a b
}

vat it = arr[Symbol.iterator]();
console.log(it.next()); // {value:'a';done:false}
console.log(it.next()); // {value:'b';done:false}

JS中值传递

发表于 2017-02-21

按值传递 VS 按引用传递

  • 按值传递(call by value)

    函数的形参是被调用时所传实参的副本。修改形参的值并不会影响实参。

  • 按引用传递(call by reference)

    函数的形参接收实参的隐式引用,而不再是副本。这意味着函数形参的值如果被修改,实参也会被修改。同时两者指向相同的值。

探究JS值的传递方式

JS的基本类型,是按值传递的。

1
2
3
4
5
6
var c = 3;
function foo(x) {
x = 9;
}
foo(c);
console.log(c); // 仍为3, 未受x = 9赋值所影响

对象

1
2
3
4
5
6
var obj = {x : 1};
function foo(o) {
o.x = 3;
}
foo(obj);
console.log(obj.x); // 3, 被修改了!

说明o和obj是同一个对象,o不是obj的副本。所以不是按值传递。 但这样是否说明JS的对象是按引用传递的呢?我们再看

下面的例子:

1
2
3
4
5
6
var obj = {x : 1};
function foo(o) {
o = 100;
}
foo(obj);
console.log(obj.x); // 仍然是1, obj并未被修改为100.

如果是按引用传递,修改形参o的值,应该影响到实参才对。但这里修改o的值并未影响obj。 因此JS中的对象并不是按引用传递。那么究竟对象的值在JS中如何传递的呢?

按对象传递

JS中的基本类型按值传递,对象类型按按对象传递.
数组、对象是把变量地址复制进去的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var a = []
var b = {};
var c = {};
function foo(a, b, c)
{
a = [1];
b = [2];
c = {d:3}
}

foo(a, b, c);
alert (a); // 空白
alert (b); // [object Object]
alert (c.d); // undefined

上面我们让 v1、v2、v3 作为参数进入函数后,就有了地址副本,这些地址副本的指向和外面的 v1、v2、v3 的地址指向是相同的。但我们为 v1、v2、v3 赋了值,也就是说我们把地址副本的指向改变了,指向了新的数组和对象。这样内部的 v1、v2、v3 和外部的 v1、v2、v3 就完全断了。
如果我们不赋新值,而是直接操作它,那么,它操作到的,仍然是和外面的 v1、v2、v3 指向的同一块数组或对象。

所以传值的比较比较的是数值 而传址的比较比较的是引用,引用不同即使数值相同也不等

1
2
3
4
5
6
1 == 1;    //true
1 === 1; //true
[0] == [0]; //false
[0][0] == [0][0]; //true
[0][0] === [0][0]; //true
[0].toString() == [0].toString(); //true
1…4567
lennonover

lennonover

一丿口石砳磊

70 日志
28 标签
© 2022 lennonover
由 Hexo 强力驱动
主题 - NexT.Muse