Andre Huang


  • 首页

  • 归档

学习 Deferred 对象

发表于 2016-10-13

Deferred 对象

Deferred 对象,是 jQuery 的一个特别的对象,用来解决回调函数的问题。

1.5 开始,$.ajax 方法也开始使用这个对象。1.5- 时,$.ajax() 方法返回的是 XHR 对象。之后 1.5+ 开始,返回的是一个 Deferred 对象。因此,原先设置在对象中的 success 和 error 属性方法不再使用,取而代之的是一些回调方法,包括 done 和 fail。而且调用方式是通过链式调用来实现,如:

$.ajax()
.done()
.fail()

对某个操作指定多个回调函数

如上面的 ajax 的栗子,在 ajax 操作结束后有绑定了两个回调函数。但其实,可以连续绑定多个回调函数的,直接在后面继续调用即可。

$.ajax()
.done()
.fail()
.done()

为多个操作指定相同的回调函数

这是 deferred 对象的一个特点,也是其优点。它允许我们为多个事件指定共同的回调函数。
在下面这个栗子中,用到的是 $.when() 方法:

$.when( $.ajax('test1.html'), $.ajax('test2.html') )
.done(function(){
alert('success');
})
.fail(function(){
alert('fail');
})
.done(function(){
alert('success again');
})

Deferred 使用

deferred 对象使用的范围不仅仅只是 ajax(),其他普通操作也适用。不管是 ajax 操作还是本地操作,也不管是异步操作还是同步操作,都可以通过使用 deferred 对象来指定回调函数。
那么,怎么给具体操作指定回调函数呢?这就利用了 deferred 对象的状态值。
deferred 对象的状态值有:pending、resolved(已完成)、rejected(已失败)。
状态值通过使用 deferred 对象的属性方法——resolve()、reject() 来改变状态值。

var dfd = $.Deferred(); // 创建一个 deferred 对象
// 在这个函数中设置任意自定义操作,
// 并且在操作完成后,改变 deferred 对象的状态值,以此来触发回调函数
var binding = function( dfd ){
var foo = function(){
alert('action finished');
dfd.resolve(); // 改变状态值为“已完成”
}
setTimeout(foo, 3000);
return dfd;
}
// 使用 when() 方法来指定回调函数
$.when( binding )
.done(function(){
alert('success');
})
.fail(function(){
alert('fail');
})

在上面的栗子中,dfd.resolve() 是必须的,不然的话会 done() 会立即执行。
但是有个问题,假如 dfd.resolve() 是在外部执行的话,done() 方法就会在 foo 操作完成之前就执行,这样就没有了回调函数的意义。
因此,就需要对这种情况进行预防。

防止执行状态被外部改变的几种方案

deferred.promise()

promise() 方法的作用是在原来对象的基础上,返回一个另一个 deferred 对象。这个对象中,屏蔽了和改变执行状态值有关的方法(如 resolve() 和 reject() ),只开发和状态值无关的方法。这样使得状态被隐藏起来不能被改变。

var dfd = $.Deferred(); // 创建一个 deferred 对象
// 在这个函数中设置任意自定义操作,
// 并且在操作完成后,改变 deferred 对象的状态值,以此来触发回调函数
var binding = function( dfd ){
var foo = function(){
alert('action finished');
dfd.resolve(); // 改变状态值为“已完成”
}
setTimeout(foo, 3000);
return dfd.promise(); // 返回新的 deferred 对象
}
// 使用 when() 方法来指定回调函数
var d = binding(dfd);
$.when( )
.done(function(){
alert('success');
})
.fail(function(){
alert('fail');
})
d.resolve(); // 报错
dfd.reject(); // 可以执行

(但是有个问题,上面栗子中,dfd 还是一个全局变量,那么可以跳过 d 对象直接操作 dfd,这样使用 promise 不是没有意义了?)

通过改良后,得到一个更好的方案:

// 在这个函数中设置任意自定义操作,
// 并且在操作完成后,改变 deferred 对象的状态值,以此来触发回调函数
var binding = function(){
var dfd = $.Deferred(); // 在函数内部创建 deferred 对象
var foo = function(){
alert('action finished');
dfd.resolve(); // 改变状态值为“已完成”
}
setTimeout(foo, 3000);
return dfd.promise(); // 返回新的 deferred 对象
}
// 使用 when() 方法来指定回调函数
var d = binding();
$.when(d)
.done(function(){
alert('success');
})
.fail(function(){
alert('fail');
})
d.resolve(); // 报错

这样,dfd 这原来的 deferred 对象就是个局部变量,外界不能访问,不会被改变状态值。而且,binding 返回的是 promise 后的对象,也不能改变状态值。

直接使用构造函数 $.Deferred()

自定义的操作不变,还是 binding,但是我们直接把它传入 $.Deferred()。

var binding = function(dfd){
var foo = function(){
alert('action finished');
dfd.resolve(); // 改变状态值为“已完成”
}
setTimeout(foo, 120000);
}
$.Deferred(binding)
.done(function(){
alert('success');
})
.fail(function(){
alert('fail');
})

直接在操作的函数上部署 deferred 接口

var dfd = $.Deferred();
var binding = function(){
var foo = function(){
alert('action finished');
dfd.resolve(); // 改变状态值为“已完成”
}
setTimeout(foo, 3000);
}
dfd.promise(binding);
binding
.done(function(){
alert('success');
})
.fail(function(){
alert('fail');
})
binding(dfd)
d.resolve();

其他属性方法

.state()

返回 deferred 对象的状态值

.always()

只要 deferred 对象状态变化,就会触发执行

.progress() & .notify()

progress() 可以让我们指定一个回调函数;
而 notify() 方法则是,当它被调用时,progress() 指定的回调函数就会被执行。

其用意是提供一个接口,使得在非同步操作执行过程中,可以执行某些操作,比如定期返回进度条的进度。

.then()

概述

then() 方法可以一次性设置多个回调函数。接受的参数有三个,分别是 resolved、rejected 的回调方法,以及 progress() 指定的回调函数。其中,后两者是可选。

deferred.then( doneFilter [, failFilter] [, progressFilter] )

返回值

值得注意的是,在 jQuery 1.8-,then() 和 .done().fail() 是等价的。在 1.8+,then() 返回一个新的 promise 对象。而 done() 返回的是原有的 deferred 对象。如果 then() 指定的回调函数有返回值的话,该返回值会作为参数,传入后面的回调函数

var defer = $.Deferred();
defer.done(function(a,b){
return a*b;
})
.done(function(result){
console.log('result: ' + result);
})
.then(function(a,b){
return a*b;
})
.done(function(result){
console.log('result: ' + result);
})
.then(function(a,b){
return a*b;
})
.done(function(result){
console.log('result: ' + result);
});
defer.resolve(2, 3);

1.8- 的结果:

result = 2
result = 2
result = 2

1.8+ 的结果:

result = 2
result = 6
result = NaN

利用 then() 的特点

既然 then() 指定的回调函数会修改返回值,那么,我们可以利用这个特性,在调用其他回调函数时,对之前操作返回的值进行处理。


(其余的坑等接触了 jQuery 源码的相关知识后再填)

学习jQuery源码(一)- 整体架构

发表于 2016-10-13

整体架构

无new构建实例

总结

  • 通过 new jQuery.fn.init() 构建一个新的对象,拥有 init 构造器的 prototype 原型对象的方法
  • 通过改变 prototype 指针的指向,让这个新的对象也指向 jQuery 对象的原型 prototype
  • 这样构建出来的对象就继承了 jQuery.fn 原型定义的所有方法了

原理图示:
原理图示

链式调用

待补充。。。

插件接口

jQuery 支持自己扩展属性,对外提供了一个接口,jQuery.fn.extend() 来对对象增加方法

在jQuery中,接口方法的定义是这样的:

jQuery.extend = jQuery.fn.extend = function(){}

也就是说,两者指向的是同一个函数。前者是对 jQuery 本身的属性和方法进行拓展;后者是对 jQuery.fn,也就是 jQuery 的原型的属性和方法进行了拓展。而这样的功能就是通过 this 来实现。
源码:

jQuery.extend = jQuery.fn.extend = function(){
var src, copyIsArray, copy, name, options, clone,
target = arguments[0] || {} // 常见用法 jQuery.extend( obj1, obj2 ),此时,target 为 arguments[0]
i = 1,
length = arguments.length,
deep = false;
// Handle a deep copy situation
if( typeof target === "boolean" ){ // 如果第一个参数为 true,即 jQuery.extend( true, obj1, obj2 ); 的情况
deep = target; // 此时 target 为 true
target = arguments[1] || {}; // target 改成 obj1
// skip the boolean and the target
i = 2;
}
// Handle case when target is a string or something (possible in deep copy)
if( typeof target !== "object" && !jQuery.isFunction(target) ){ // 处理奇怪的情况,例如 jQuery.extend( 'hello', { nick: 'casper' } )
target = {};
}
// extend jQuery itself if only one argument is passed
if( length === i ){ // 处理类似这种情况:jQuery.extend(obj),或jQuery.fn.extend(obj)
target = this; // jQuery.extend 时,this 指的是 jQuery;jQuery.fn.extend 时,this 指向 jQuery.fn
--i;
}
for( ; i < length; i++ ){
// Only deal with non-null/undefined values
if( (options = argument[i]) != null ){ // 比如 jQuery.extend( obj1, obj2, obj3, obj4 ), options 则为 obj2、obj3……
// Extend the base object
for( name in options ){
src = target[name];
copy = options[name];
// Prevent never-ending loop
if( target === copy ){ // 防止自引用
continue;
}
// Recurse if we're merging plain objects or arrays
// 如果是深拷贝,且被拷贝的属性值本身是个对象
if( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ){
if( copyIsArray ){ // 被拷贝的属性值是个数组
copyIsArray = false;
clone = src && jQuery.isArray(src) ? src : [];
} else { // 被拷贝的属性值是一个 plainObject,比如 { nick: 'casper' }
clone = src && jQuery.isPlainObject(src) ? src : {};
}
// Never move original objects, clone them
target[name] = jQuery.extend( deep, clone, copy ); // 递归
} else if( copy !== undefined ){ // 浅拷贝,且属性值不为 undefined
target[name] = copy;
}
}
}
}
// Return the modified object
return target;
}

Reference

Aaron - jQuery 2.0.3 源码分析core - 整体架构

iframe 相关知识的笔记

发表于 2016-09-27

平时很少接触到 iframe,所以对相关的知识也挺匮乏的。刚好昨天接触到,也了解了一些知识。先占个坑,整理一下今天所得的知识,日后再慢慢补充相关的知识。

获取 iframe 元素

关于获取 iframe 子页面的元素,在网上查找了许久,发现大多是一样的,而且不知道为什么,并不是正确的。最后终于找到了一个真正正确的答案。
$(iframeID).contents().find(‘sth’);

判断 iframe 是否加载完成

要获取 iframe 里的元素,在上面的描述中,已经得到解答。但是也出现了一个问题:直接用遍历方法的话会在页面生成之前就执行,此时 iframe 可能还没有加载完成。

那么问题来了,怎么解决这个情况呢?

一开始,我是琢磨着使用定时器,设定一定时间后再执行查找元素的操作。乍一看好像达到了效果。但是,个人觉得,这样还是不妥。因为 iframe 加载完成所需的时间和网络情况等有关,而设置的时间过短,加载所需时间可能会超过自己定好的时间。如果过长,则会导致下来的操作延时,影响交互,使用体验大打折扣。

于是,就有了另一个方案。就是 iframe 具有的 onload 事件。利用这个,可以在 iframe 加载完成时触发。

另外,知道 iframe 加载完成的事件,除了解决上述问题,还可以通过在触发事件之前,以一个“Loading”的动画提示,以达到一个较好的用户体验。

参考

学习URL

发表于 2016-09-19

组成

schema://host[:port#]/path/…/[?query-string][#anchor]

  • schema: 协议。包括 http、https、ftp 等。
  • host: HTTP 服务器的 IP 地址或者是域名。
  • port: 端口。可选。
  • path: 访问的资源的路径
  • query-string: 搜索串。是发送给服务器的数据。可选。
  • anchor: 锚。可选。

某些场景

提取搜索串 search 并转化为对象

var searchObj = {};
var searchArr = window.location.search.substring(1).split('&');
for(var i=0, len=searchArr.length; i<len; i++) {
var elem = searchArr[i];
elem = elem.split('=');
searchObj[ decodeURIComponent(elem[0]) ] = decodeURIComponent(elem[1]);
}
console.log(searchObj);

Stylus 学习笔记

发表于 2016-09-12

由于一些不可描述的原因,需要接触 stylus 这个工具。作为一个爱学习的宝宝,应该要做好笔记。

stylus 呢,是一个 CSS 预处理器,让 CSS 变得像 JavaScript 般进行动态开发,可以使用变量、简单逻辑、函数等编程语言的基本语法。

但不同于 sass,stylus 是基于 Node.js 的,而且比 less 强大。
具体查阅:[译]为什么使用Stylus

Stylus 默认使用 “styl“ 作为文件拓展名。

配置环境

  1. Node.js
    既然说 stylus 是基于 Node.js,那么首先就要先要准备好 Node.js 的环境。

  2. Stylus 模块
    用 npm 等安装 stylus 模块

    $ npm install stylus -g
  3. Sublime Text 3 的 stylus 插件
    使用 Package Control 安装

完成这三步之后,就可以在 ST3 里进行 styl 文件的编译了。当然,如果不想借助 ST 的话,只需完成前两步即可。


下来就是 stylus 的语法了

选择器

1. 缩进

stylus 使用缩进(Indentation)来表示花括号{}。
对于缩进可以使用空格或者 tab,只不过必须整个文件要统一(要么只用 spaces,要么只用 tab),否则会报错。

body
color: blue

P.s. 属性名后面的冒号可加可不加

另外,缩进也可以表示嵌套

.outer
.inner
color: blue
/* 表示 */
.outer .inner{
color: blue
}

2. 规则

stylus 的语法和 CSS 还是有很多相似之处。
如想要为多个不同选择器设置相同的样式,可以用逗号隔开,或者是直接换行。如:

button, a
display: inline-block
button
a
display: inline-block
/* 编译后都是如下 ==> */
button,
a {
display: inline-block
}

文档中有个地方,我还不太明白怎么使用

The only exception to this rule are selectors that look like properties. For example, the following foo bar baz might be a property or a selector

“有一个例外,选择器看起来会和属性名相似。比如 foo bar baz 就可能是一个属性或者选择器”

So for this reason (or simply if preferred), we may trail with a comma

“因此(最好平时也如此),可以在后面添上一个逗号”

父级引用

使用 & 字符引用父选择器,可以让伪类选择器指向父选择器,如:

#p1
span
button
color: blue
&:hover
color: yellow

在上面的代码中,等同于:

#p1 span,
#p1 button{
color: blue
}
#p1 span:hover,
#p1 button:hover{
color: yellow
}

文档还有个比较特别的栗子,在 IE 浏览器中,利用父级引用以及混合书写来实现 2px 的边框:

// styl
box-shadow()
-webkit-box-shadow arguments
-moz-box-shadow arguments
box-shadow arguments
html.ie8 &,
html.ie7 &,
html.ie6 &
border 2px solid arguments[length(arguments) - 1]
body
#login
box-shadow 1px 1px 3px #eee

变身(此处应有奥特曼!)

/* css */
body #login {
-webkit-box-shadow: 1px 1px 3px #eee;
-moz-box-shadow: 1px 1px 3px #eee;
box-shadow: 1px 1px 3px #eee;
}
html.ie8 body #login,
html.ie7 body #login,
html.ie6 body #login {
border: 2px solid #eee;
}

针对 styl 部分解释一下:

  • 可以把 box-shadow() 近似看成是一个函数,参数可以用 arguments 数组来获取,而参数在传入的时候,根据空格来分隔出若干个参数。在栗子中,传入的是”1px 1px 3px #eee”,因此 arguments 就有四个元素了。
  • 将 box-shadow(函数)作为属性赋给 body #login,可以将函数内的属性设置在 body #login 中。并且可以通过传参动态改变属性值。
  • 在 html.ieX 后,带有一个字符 &。而这个 & 就引用了父级,即:当前被赋予 box-shadow() 的选择器,这里也就是 body #login

消除歧义

函数中,类似 padding -n 的表达式时,要注意消除歧义,因为它即可被解释为减法运算,也可以是一元负号属性。所以,用括号来包裹表达式以避免歧义。

marg(n)
margin (- n)
body
marg(5px)

注意,- 号和变量中间要有一个空格

无法处理的属性值,用 unquote():

filter unquote('progid:DXImageTransform.Microsoft.BasicImage(rotation=1)')

变量

设置变量

变量可以这么玩:

font-size = 14px
body
font: font-size Arial, sans-serif

===>

body {
font: 14px Arial sans-serif;
}

或者这么玩:

$font-size = 14px
font = $font-size "Lucida Grande", Arial
body
font: font sans-serif

===>

body {
font: 14px "Lucida Grande", Arial sans-serif;
}

属性引用

Core —— 使用字符 @ 来实现:

#logo
position: absolute
top: 50%
left: 50%
width: 150px
height: 80px
margin-left: -(@width / 2)
margin-top: -(@height / 2)

还可以给属性设置默认值:

position()
position: arguments
z-index: 1 unless @z-index

此外,这个引用功能,还会向上冒泡,查找堆栈直到发现,或者最后返回 null。栗子:

body
color: red
ul
li
color: blue
a
background-color: @color

最后 background-color 为 blue。

插值

插值

stylus 中,{} 有了新的用法。用它包裹表达式来插入一些值或者内容(有点类似 Vue 中的插值绑定 )
e.g. -webkit-{‘border’ + ‘-radius’} 等同于 -webkit-border-radius

一个比较好的栗子就是用来进行私有前缀的拓展。

vendor(prop, args)
-webkit-{prop}: args
-moz-{prop}: args
{prop}: args
border-radius()
vendor('border-radius', arguments)
button
border-radius(50%)

===>

button {
-webkit-border-radius: 50%;
-moz-border-radius: 50%;
border-radius: 50%;
}

选择器插值

直接举栗吧:

table
for row in 1 2 3 4 5
tr:nth-child( {row} )
height: 10px * row

运算符

优先级(最高到最低)

[] // 下标运算符(跟数组的用法一致)
! ~ + -
is defined
** * / %
+ -
... .. // 范围。分别是 范围操作符 和 界线操作符
<= >= < >
in
== is != is not isnt
&& and || or
?:
= := ?= += -= *= /= %=
not
if unless

范围操作符(…)和界线操作符(..)

1..5
// => 1 2 3 4 5
1...5
// => 1 2 3 4

乘除运算符

如果使用 “/“ 运算符的话,必须要给它加一个括号,否则会直接根据其字面意思来处理,如 CSS 的 line-height:

font: 14px/1.5 // 这里表示文字大小和行高
font: (14px/1.5) // 这里表示文字大小为 14÷1.5

指数操作符(**)

2 ** 8
// => 256

条件赋值( ?= := )

条件赋值,可以在不改变旧值的情况下,定义一个变量。
(其实也就是在定义一个变量时,检查它是否有值,如果没有,就给它赋予一个指定的新值。)
e.g.

color = black
color := white
borderColor ?: white
backgroundColor = backgroundColor is defined ? backgroundColor : white
// color => black
// borderColor => white
// backgroundColor => white

混入和函数(Mixin & Function)

混入和函数,是 stylus 里两种特性。两者都是通过相同的方式来定义,只不过它们的使用方式不同。

混入

混入,按我自己的理解,就是我们先针对某个属性及其前缀,自行定义一个方法(方法内是针对某个属性的模版),在选择器中调用,该方法会将内部设置的属性模版直接套用在当前选择器的属性中。
如:

// 定义
border-radius(n)
-webkit-border-radius n
-moz-border-radius n
border-radius n
// 调用
form input[type=button]
border-radius(5px);

===>

form input[type=button]{
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}

当然,使用混入的话,调用时的括号也可以省去。
另外,可以利用 arguments 变量来获取参数内容。

border-radius()
-webkit-border-radius arguments
-moz-border-radius arguments
border-radius arguments

父级引用

其实和之前选择器的父级引用用法一样。可以用来设置伪类的属性。

// 设置一个条纹表格样式
// 这里可以给其赋予一个默认值
stripe( even=#fff, odd=#eee )
tr
background-color odd
&.even
&:nth-child(even)
background-color even
table
stripe()
table
stripe pink, blue

混入的嵌套和组合

inline-list()
li
display inline
comma-list()
inline-list()
li
&:after
content ', '
&:last-child:after
content ''

函数

函数的定义和混入一致,但不同的是,函数可以返回值。
(我觉得基本就是用来返回值的。就是说,stylus 的函数的用途就用来返回处理后的数据,当然啦,不用手动返回啦)

返回值

// 定义
add(a, b)
a + b
// 调用
body
padding add(10px, 5)
// 渲染结果
body{
padding: 15px;
}

默认值

和混入一样,可以给参数设置默认值。

add(a=5, b=a)
a + b
add(10, 5)
// => 15
add(10)
// => 20
add()
// => 10

1234…8
Andre Huang

Andre Huang

40 日志
19 标签
GitHub
© 2018 Andre Huang
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.3