翻译自Stop toggling classes with JS, use behaviour driven DOM manipulation with data-states

使用一个类来操作DOM?这种方式使用类有诸多问题,其中一个主要的问题是为了改变它们的状态而添加类,这样跨越了行为和样式范例。我们应该把行为与风格分开,随着我们的网站的功能变得更加丰富,分清样式和状态之间的界限是一个挑战,有时会变得很凌乱。

当用:hover这个伪元素选择器满足不了网站样式的要话将会引入javascript,你也许会添加一个open类给你的菜单,一个toggled类给tabs等等。这是我们的hover事件的一个很好的语义名称,但是两者并没有真正解决管理代码行为的问题。

你可能会这样做:

1
elem1.onclick = toggleClass(elem2, 'open');

这是我们经常以实现DOM状态差异的简单示例。这糟透了!

它不仅凌乱而且还难以维护,我们必须继续为每个组件编写脚本,并可能会重复很多这样的代码。如果你添加了active类,那么它也会引入样式问题,因为它不但可能与其他元素中的其他元素冲突。而且它也没有告诉我关于该元素在样式表中的行为。

翻译自Ditch the [].forEach.call(NodeList) hack

在我们进一步之前,我不得不承认,我曾经使用过这种技术。 这让它看起来很酷,很酷,我像是在做ECMA5 hack,但过了一段时间,我写出了更好的JavaScript,才发现这种技术只会引起头痛且是不必要的时髦代码,接下来我将把我的想法放在为什么我现在不主张这种技术。

理解 [].forEach.call(NodeList)

让我们来确定一下这个hack的实际情况,然后才能看出为什么我认为这是一个非常糟糕的技术。让我们创建一个普通的数组,并使用ECMA5的.forEach这个神奇的方法去遍历它:

1
2
3
4
var myArray = [1,2,3,4];
myArray.forEach(function (item) {
console.log(item); // prints each number
});

上面看起来不错,但”hack”在哪里适用?试试输入NodeList:

1
2
3
4
5
6
var myNodeList = document.querySelectorAll('li'); // 获取一些 <li>
// Uncaught TypeError: Object #<NodeList> has no method 'forEach'
myNodeList.forEach(function (item) {
// :(
});

这里我们遇到了一个错误,因为NodeLists不共享其中包含forEach方法的Array的原型,。呃,但是还是有”解决方案”:

1
NodeList.prototype.forEach = Array.prototype.forEach;

如果你这样做过,那可能不是一个好主意(请不要使用它)。通过原型扩展现有的DOM功能通常被认为是不好的做法,因为这可能会导致大量的问题。

解决这个问题更好的方法是:

1
2
3
4
var myNodeList = document.querySelectorAll('li'); // grabs some <li>
[].forEach.call(myNodeList, function (item) {
// :) hooray `item` can be used here
});

通过创建(空)数组并通过NodeList间接调用数组的原型的forEach方法,现在一切都奏效了。

现在来看看这个技术的一些问题

Problems

Problem #1: 没有数组方法

这是一个大问题。 NodeLists是有一个length属性的,如果要添加一个新元素或者从该列表中删除一个元素,你不能通过使用forEach的hack来保持任何状态,并且也无法访问列表本身,这意味着它是一条单向的方法,也就是说在静态元素的情况下你只能操纵一次,你不能继续添加/删除其他元素。

使用诸如.splice()之类的方法将导致错误 - 原因是NodeLists在它们的Prototype中不包含这个方法。节点列表不能被改变,这通常是非常不切实际的。这也意味着你除了可以绑定事件处理程序或调用方法外,不能做任何其它令人兴奋的操作。

Problem #2: 限制复用

当我们缓存一个dom时,我们缓存的不是一个数组,这意味着我们无法重复使用该方法。我认为这是一个严重的可扩展性和可重用性的问题。如果我想再次调用该方法怎么办?那我必须编写相同的非描述性代码两次。

1
2
3
4
5
6
7
8
// cached, we can access this again
var myNodeList = document.querySelectorAll('li');
// this will only get called once
// and cannot be called again
[].forEach.call(myNodeList, function (item) {
// :(
});

Problem #3: 分离问题

节点列表和数组是两个不同的数据类型,那么为什么我们编写这些重叠而不会为我们带来任何好处的代码呢?如果您需要一个NodeList中的数组,那么可以执行下面这种操作。这是非跨浏览器版本的方法:

1
var myArrayFromNodeList = [].slice.call(document.querySelectorAll('li'));

但是这又是一个Array.prototype hack,我也不鼓励使用这种方式。它并不能兼容所有的浏览器,因为IE不允许NodeLists形成Array.prototype.slice调用的宿主对象。不过,可以使用下面这种方法将所有节点推送到新数组中:

1
2
3
4
5
var myNodeList = document.querySelectorAll('li');
var myArrayFromNodeList = []; // empty at first
for (var i = 0; i < myNodeList.length; i++) {
myArrayFromNodeList.push(myNodeList[i]); // ahhh, push it
}

我们将使用我们的节点填充为数组! B-E-A-uuutiful。这给我们带来了什么好处?我们完全分离了两种对象类型,并且可以在需要时引用它们:

1
2
console.log(myNodeList); // NodeList
console.log(myArrayFromNodeList); // Array of Nodes

现在我们可以遍历我们的数组,并可以调用splice和push方法来实际做一些有价值的事情

Problem #4: 创建不必要的数组

使用[] .forEach.call实际上时创建了一个新的数组,然后它会驻留在内存中,我们显然没有必要这样做。然而有一个解决方法,那就是使用Array.prototype.forEach.call,这实际上更快也更可靠(一些库将会与使用[]语法的方式产生冲突),这样只是访问forEach方法,而不是创建一个新的数组然后访问它。

Problem #5: It’s slower and works harder

我不打算进行大规模辩论关于从方法中消耗的0.00012230毫秒,但[] .forEach.call真的非常慢,特别是因为它通常是通过实例化新对象(或类似的东西)的方式来生成新的元素。首先,[]实例化一个新的Array对象,然后forEach方法链接到.call()方法,最后它会改变每次循环的执行上下文。我不知道你,但是对于这样一个简单的任务,这样做了很多不必要的工作。

Problem #6: Stupidity vulnerabilities

根据先前我们看到的例子,你应该知道这个例子还是有效的

1
2
3
4
5
var myNodeList = document.querySelectorAll('li');
[1,2,3,4,5].forEach.call(myNodeList, function (item) {
// Wah?...
// Are we looping over the NodeList or Array?!
});

我不希望我的代码容易受到这种事情的影响,这也是可能会发生的事情

Problem #7: 可扩展性

如果我想将NodeList传递到另一种方法中,那我就必须完全重写forEach的hack,然后才能将其传递到一个方法中,这样就导致需要进行更多的测试和产生更多的bug的可能性。如果第一次能正确地编写好代码,那这将能够给代码带来极好的扩展性。

Problem #8: 可读性

一个随机的forEach(通常在脚本结尾看到)是完全没有意义的。循环通常基于操纵某种类型的对象/元素,因此将其包含在您自己的方法中可能会更好

Problem #9: Confusing syntax

你是在操作NodeList还是Array?当你可以轻松地编写一个方法来描述这些事情的时候为什么还让别人要弄清楚你是在操作哪个对象

Problem #10: 非跨浏览器兼容

我通常不会使用ECMAScript 5 forEach方法,一个简单的for循环就足够了:

1
2
3
4
var myNodeList = document.querySelectorAll('li');
for (var i = 0; i < myNodeList.length; i++) {
// do something with myNodeList[i]
}

先不说这样更快,这也让我有更多的方式去控制数组元素,例如,我可以反向循环(通常比正向循环更快!):

1
2
3
4
var myNodeList = document.querySelectorAll('li');
for (var i = myNodeList.length; i--;) { // reverse
// do something with myNodeList[i]
}

当然你也可以写一个函数去封装这些代码,这样你就可以在所有的浏览器中调用这个方法,因此你也可以少敲一些代码

Problem #11: 开发人员的误解

我看到过开发人员用这种方法来遍历数组,这样做是很愚蠢的,因为这个hack是针对NodeLists而不是数组的。

使用hack会有更多的问题,但现在我只在主要的领域发表一些看法

建议

我避开了基于上述的方法,使用更好的代码对我来说更明智。编写自己的forEach方法是很容易的,它避免了对hack方法的需求,而它只需要一个NodeList或Array做为参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
// forEach method, could be shipped as part of an Object Literal/Module
var forEach = function (array, callback, scope) {
for (var i = 0; i < array.length; i++) {
callback.call(scope, i, array[i]); // passes back stuff we need
}
};
// Usage:
// optionally change the scope as final parameter too, like ECMA5
var myNodeList = document.querySelectorAll('li');
forEach(myNodeList, function (index, value) {
console.log(index, value); // passes index + value back!
});

通过变量缓存来跟踪数组和节点列表状态,虽然写了一些额外的几行代码,但却让你的代码改善十倍,这将会在往后写代码的过程中我们将获益更多。

Any thoughts appreciated! Happy coding!

翻译自Replacing switch statements with Object literals

在许多编程语言中,都有switch语句 - 但是它还应该存在吗?如果你是一个JavaScript程序员,那么你经常会跳出对象,创建,实例化和操作它们。对象是非常灵活的,它们几乎是JavaScript中的一切,使用对象而不是使用switch语句是我最近一直在做的。

switch语句的问题

switch语句有多个问题,从其过程控制流到其处理代码块的方式是非标准的,而JavaScript的其它流程控制语句使用的时花括号。它既不是语法上最好的,也不是设计上最好的。我们不得不在每个case语句后面添加break语句,这可能导致难以调试和产生嵌套的错误。因此,进一步讲,我们应该避免使用case语句。道格拉斯·克罗克福德(Douglas Crockford)曾经多次写过和说过,谨慎对待switch语句。

我们经常在javascript中使用对象查找数据,但我们通常不会考虑使用switch。所以为什么不使用对象字面量来替换switch呢?对象更加灵活,具有更好的可读性和可维护性,且我们不需要手动添加break和case语句;它们对于新的JavaScript开发人员来说也是很友好的,因为它们是标准的数据对象。

随着”case”数量的增加,对象(哈希表)的性能要好于switch的平均成本(case的顺序很重要)。对象方法是哈希表查找,而switch必须评估每个case语句,直到它遇到匹配的case和break。

对象字面量查找

我们一直使用对象,无论是构造函数还是字面量。通常,我们使用它们进行对象查找,从Object属性中获取值。

现在我们设置一个简单的对象字面量,它只返回一个String类型的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
function getDrink (type) {
var drinks = {
'coke': 'Coke',
'pepsi': 'Pepsi',
'lemonade': 'Lemonade',
'default': 'Default item'
};
return 'The drink I chose was ' + (drinks[type] || drinks['default']);
}
var drink = getDrink('coke');
// The drink I chose was Coke
console.log(drink);

在没有default case的情况下我们甚至可以进一步简化

1
2
3
4
5
6
7
function getDrink (type) {
return 'The drink I chose was ' + {
'coke': 'Coke',
'pepsi': 'Pepsi',
'lemonade': 'Lemonade'
}[type];
}

然而,我们可能需要比String更复杂的代码,这可放置在一个函数中来实现。为了简洁起见,我从新创建的函数返回上面的字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
var type = 'coke';
var drinks = {
'coke': function () {
return 'Coke';
},
'pepsi': function () {
return 'Pepsi';
},
'lemonade': function () {
return 'Lemonade';
}
};

不同的是我们需要调用对象字面量的函数:

1
drinks[type]();

这样更可以更好的维护和有更号的可读性。我们也不用担心break和case语句的落空 - 这只是一个简单的对象。

通常,我们将把一个switch置于一个函数中并获得一个返回值,所以让我们在这里做同样的事情,并将一个对象字面查找转换成一个可用的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function getDrink (type) {
var drinks = {
'coke': function () {
return 'Coke';
},
'pepsi': function () {
return 'Pepsi';
},
'lemonade': function () {
return 'Lemonade';
}
};
return drinks[type]();
}
// let's call it
var drink = getDrink('coke');
console.log(drink); // 'Coke'

这样就很好,也很容易,但这没有”默认值”的情况,然而我们可以轻松创建:

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
function getDrink (type) {
var fn;
var drinks = {
'coke': function () {
return 'Coke';
},
'pepsi': function () {
return 'Pepsi';
},
'lemonade': function () {
return 'Lemonade';
},
'default': function () {
return 'Default item';
}
};
// if the drinks Object contains the type
// passed in, let's use it
if (drinks[type]) {
fn = drinks[type];
} else {
// otherwise we'll assign the default
// also the same as drinks.default
// it's just a little more consistent using square
// bracket notation everywhere
fn = drinks['default'];
}
return fn();
}
// called with "dr pepper"
var drink = getDrink('dr pepper');
console.log(drink); // 'Default item'

我们可以简化上述if和else语句,在表达式里面使用或||运算符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function getDrink (type) {
var drinks = {
'coke': function () {
return 'Coke';
},
'pepsi': function () {
return 'Pepsi';
},
'lemonade': function () {
return 'Lemonade';
},
'default': function () {
return 'Default item';
}
};
return (drinks[type] || drinks['default'])();
}

这将在括号()中的两个对象中的表达式中查找。然后调用表达式的结果。如果在查找中找不到drinks[type],它将默认为[‘default’],这样就简单!

我们也不必总是返回内部函数,我们可以改变对变量的引用,然后返回它:

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
function getDrink (type) {
var drink;
var drinks = {
'coke': function () {
drink = 'Coke';
},
'pepsi': function () {
drink = 'Pepsi';
},
'lemonade': function () {
drink = 'Lemonade';
},
'default': function () {
drink = 'Default item';
}
};
// invoke it
(drinks[type] || drinks['default'])();
// return a String with chosen drink
return 'The drink I chose was ' + drink;
}
var drink = getDrink('coke');
// The drink I chose was Coke
console.log(drink);

这些是非常基本的解决方案,对象字面量持有一个返回String的函数,在你只需要一个String的情况下,您可以使用一个String作为键的值-某些时候会从函数返回逻辑。如果使用字符串来混合函数,则可能会更容易地使用函数来保存查找类型并如果它是函数的话就调用它 - 我们可不想去调用一个字符串类型的变量。

对象字面量匹配多个条件

使用switch,我们可以让它们匹配多个条件(这意味着一个以上的情况可以应用于特定的代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var type = 'coke';
var snack;
switch(type) {
case 'coke':
case 'pepsi':
snack = 'Drink';
break;
case 'cookies':
case 'crisps':
snack = 'Food';
break;
default:
drink = 'Unknown type!';
}
console.log(snack); // 'Drink'

我们让coke和pepsi通过不添加break来匹配。对象字面量进行此操作也是很简单的,也更具声明性,同时也不容易出错。我们的代码突然变得更加结构化,更有可读性和可重复使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function getSnack (type) {
var snack;
function isDrink () {
return snack = 'Drink';
}
function isFood () {
return snack = 'Food';
}
var snacks = {
'coke': isDrink,
'pepsi': isDrink,
'cookies': isFood,
'crisps': isFood,
};
return snacks[type]();
}
var snack = getSnack('coke');
console.log(snack); // 'Drink'

总结

对象字面量是JavaScript中更自然的流量控制,switch有点老且笨重,容易出现难以调试的错误。Objects更可以更好地扩展,更好地维护,我们也可以对它们进行更好的测试。它们也是设计模式的一部分,并且在其他编程任务中日常使用。对象字面量可以包含函数以及任何其他对象类型,这使得它们变得很灵活!字面量中的每个函数也有函数​​作用域,所以我们可以从我们调用的父函数返回闭包(在这种情况下getDrink返回闭包)。

翻译自Perspective Boxes

相信与否,这个盒子动画是用纯CSS3变换实现的。立方体使用不同的颜色和类很容易渲染出来。

使用循环关键帧动画来给这些盒子在3D空间中产生弹起的幻觉。通过单击右上角的任何透视按钮,你甚至可以从不同的角度查看此3D模型。很酷!

html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<input type="radio" id="left" name="rotate">
<label for="left">Left</label>
<input type="radio" id="reset" name="rotate" checked>
<label for="reset">Reset</label>
<input type="radio" id="right" name="rotate">
<label for="right">Right</label>
<input type="radio" id="up" name="rotate">
<label for="up">Up</label>
<input type="radio" id="down" name="rotate">
<label for="down">Down</label>
<div class="perspective">
<div class="cube"></div>
<div class="cube"></div>
<div class="cube"></div>
<div class="cube"></div>
<div class="cube"></div>
<div class="cube"></div>
<div class="cube"></div>
<div class="cube"></div>
<div class="cube"></div>
</div>

css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
body {
font-size: 100%;
}
.perspective {
background-image:
linear-gradient(
/*从上到下画渐变*/
/*起始点透明度为0.1,画到2.5%的高度*/
hsla(0,0%,0%,.1) 2.5%,
/*从2.5%到97.5%的高度颜色为透明*/
transparent 2.5%,
transparent 97.5%,
/*从97.5%的高度以透明度为0.1到100%的高度透明度渐变*/
hsla(0,0%,0%,.1) 97.5%),
/*从左往右的透明度渐变*/
linear-gradient(90deg, hsla(0,0%,0%,.1) 2.5%, transparent 2.5%, transparent 97.5%, hsla(0,0%,0%,.1) 97.5%);
/*以3*3的大小重复图片*/
background-size: 3em 3em;
/*box-shadow: h-shadow v-shadow blur spread color inset;*/
/*h-shadow 必需。水平阴影的位置。允许负值
v-shadow 必需。垂直阴影的位置。允许负值
blur 可选。模糊距离
spread 可选。阴影的尺寸
color 可选。阴影的颜色
inset 可选。将外部阴影 (outset) 改为内部阴影。*/
box-shadow: 0 0 0 .25em hsla(0,0%,0%,.2);
height: 9em;
width: 9em;
/*水平垂直居中*/
position: absolute;
left: 50%;
top: 50%;
margin: -7.5em;
/*外围一层3*3的大小*/
padding: 3em;
transform: perspective(500px)/*为 3D 转换元素定义透视视图*/ rotateX(45deg) rotateZ(45deg);
transform-style: preserve-3d;/*子元素将保留其 3D 位置*/
transition: 1s;
}
.cube,/*顶面*/
.cube:before, /*右面*/
.cube:after /*左面*/
{
width: 3em;
height: 3em;
float: left;
position: absolute;
/*四边形成边框阴影*/
box-shadow: inset 0 0 0 .25em hsla(0,0%,0%,.1);
content: '';
}
.cube /*顶面*/
{
background: #f66;
/*覆盖原有的position:absolute,平铺成9个方块*/
position: relative;
transform: rotateZ(0deg)
/*距离底面3em高度*/
translateZ(3em);
transform-style: preserve-3d;
transition: .25s;
}
.cube:after /*左面*/
{
background-color: #e55;
transform: rotateX(-90deg) translateY(3em);
/*设置旋转元素的基点位置
语法:transform-origin: x-axis y-axis z-axis;
值 描述
x-axis 定义视图被置于 X 轴的何处。可能的值:
left
center
right
length
%
y-axis 定义视图被置于 Y 轴的何处。可能的值:
top
center
bottom
length
%
z-axis 定义视图被置于 Z 轴的何处。可能的值:
length
*/
transform-origin: 100% 100%;
}
/*右面*/
.cube:before {
background-color: #d44;
transform: rotateY(90deg) translateX(3em);
transform-origin: 100% 0;
}
/*至此已形成3个面*/
/* 改变背景颜色 */
.cube:nth-child(even) {
background-color: #fc6;
}
.cube:nth-child(even):after {
background-color: #eb5;
}
.cube:nth-child(even):before {
background-color: #da4;
}
/*动画*/
@keyframes wave {
50% {
transform: translateZ(4.5em);
}
}
.cube:nth-child(1) {
/*
语法:animation: name duration timing-function delay iteration-count direction;
值 描述
animation-name 规定需要绑定到选择器的 keyframe 名称。。
animation-duration 规定完成动画所花费的时间,以秒或毫秒计。
animation-timing-function 规定动画的速度曲线。
animation-delay 规定在动画开始之前的延迟。
animation-iteration-count 规定动画应该播放的次数。
animation-direction 规定是否应该轮流反向播放动画。
*/
animation: wave 2s .1s ease-in-out infinite;
}
.cube:nth-child(2) {
animation: wave 2s .2s ease-in-out infinite;
}
.cube:nth-child(3) {
animation: wave 2s .3s ease-in-out infinite;
}
.cube:nth-child(4) {
animation: wave 2s .4s ease-in-out infinite;
}
.cube:nth-child(5) {
animation: wave 2s .5s ease-in-out infinite;
}
.cube:nth-child(6) {
animation: wave 2s .6s ease-in-out infinite;
}
.cube:nth-child(7) {
animation: wave 2s .7s ease-in-out infinite;
}
.cube:nth-child(8) {
animation: wave 2s .8s ease-in-out infinite;
}
.cube:nth-child(9) {
animation: wave 2s .9s ease-in-out infinite;
}
/*上下左右按钮*/
input {
display: none;
}
label {
background: #ddd;
cursor: pointer;
display: block;
font-family: sans-serif;
width: 4.5em;
line-height: 3em;
text-align: center;
position: absolute;
right: .5em;
top: 4em;
transition: .25s;
}
label[for="left"] {
right: 10.5em;
}
label[for="reset"] {
right: 5.5em;
}
label[for="up"] {
right: 5.5em;
top: .5em;
}
label[for="down"] {
right: 5.5em;
top: 7.5em;
}
label:hover {
background-color: #bbb;
}
input:checked + label {
background-color: #666;
color: #fff;
}
#left:checked ~ .perspective {
transform: perspective(500px) rotateX(45deg) rotateZ(75deg);
}
#right:checked ~ .perspective {
transform: perspective(500px) rotateX(45deg) rotateZ(15deg);
}
#up:checked ~ .perspective {
transform: perspective(500px) rotateX(75deg) rotateZ(45deg);
}
#down:checked ~ .perspective {
transform: perspective(500px) rotateX(15deg) rotateZ(45deg);
}​

翻译自codrops

剪辑属性用于剪除元素的区域,只保留元素的一部分可见

在元素被裁剪之后可见的元素的部分被称为剪切区域。剪辑应用于元素的border-box区域,该区域最初是完全可见的。剪切一个元素类似于裁剪它。

使用clip,您可以指定向内指定要从中剪除元素边缘的区域的偏移量。

clip属性只接受一个形状函数,即rect()函数作为一个值。 rect()函数需要四个参数,这些参数是元素的顶部和左侧边界的偏移量。

1
clip: rect(<top>, <right>, <bottom>, <left>);

顶部和底部的值定义了从顶部边框的偏移量,左侧和右边的值均定义了左边界的偏移量。

剪辑区域或元素被裁剪后保持可见的元素部分由rect()函数的偏移定义,如上图所示。

元素的裁剪区域剪裁位于裁剪区域之外的元素的任何方面(例如,content, children, background, borders, text decoration, outline和可见滚动机制(如果有的话))。被剪辑的内容不会导致溢出。

官方语法

  • 语法
1
clip: auto | rect() | inherit;

where

1
rect() = rect(<top>, <right>, <bottom>, <left>), and <top>, <right>, <bottom>, <left> are all <length> values
  • 初始值: auto

  • 适用于: 绝对定位的元素

  • 动画:rect()函数的偏移量接受值,因此可以作为一个长度来动画化

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function bubbleSort(arr) {
var i = arr.length, j;
var tempExchangVal;
while (i > 0) {
for (j = 0; j < i - 1; j++) {
if (arr[j] > arr[j + 1]) {
tempExchangVal = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tempExchangVal;
}
}
i--;
}
return arr;
}
var arr = [3, 2, 4, 9, 1, 5, 7, 6, 8];
var arrSorted = bubbleSort(arr);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function merge(left, right){
var result=[];
while(left.length>0 && right.length>0){
if(left[0]<right[0]){
result.push(left.shift());
}else{
result.push(right.shift());
}
}
return result.concat(left).concat(right);
}
function mergeSort(items){
if(items.length == 1){
return items;
}
var middle = Math.floor(items.length/2),
left = items.slice(0, middle),
right = items.slice(middle);
return merge(mergeSort(left), mergeSort(right));
}

The classical and prototypal paradigms for creating new objects are very different from each other, and the objects that each one produces behave differently. Each paradigm has its own pros and cons, which should help you determine which one to use in a given situation.

Classical inheritance is well understood, both in JavaScript and the programmer community in general. Almost all object-oriented code written in JavaScript uses this paradigm. If you are creating an API for widespread use, or if there is the possibility that other programmers not familiar with prototypal inheritance will be working on your code, it is best to go with classical. JavaScript is the only popular, widely used language that uses prototypal inheritance, so odds are most people will never have used it before. It can also be confusing to have an object with links back to its prototype object. Programmers who don’t fully understand prototypal inheritance will think of this as some sort of reverse inheritance, where the parent inherits from its children. Even though this isn’t the case, it can still be a very confusing topic. But since this form of classical inheritance is only imitating true class-based inheritance, advanced JavaScript programmers need to understand how prototypal inheritance truly works at some point anyway. Some would argue that hiding this fact does more harm than good.

Prototypal inheritance is very memory-efficient. Because of the way prototype chain reads members, all cloned objects share a single copy of each attribute and method, until those attributes and methods are written to directly on the cloned object. Contrast this with the objects created using classical inheritance, where each object has a copy of every attribute (and private
method) in memory. The savings here are enormous. It also seems to be a much more
elegant approach, needing only a single clone function, rather than several lines of incomprehensible syntax such as SuperClass.call(this, arg) and SubClass.prototype = new SuperClass for each class you want to extend (it is true, however, that some of these lines can, in turn, be condensed into the extend function). Don’t think that just because prototypal inheritance is simple that it isn’t also complex. Its power lies in its simplicity.

The decision to use classical or prototypal inheritance probably depends most on how well you like each paradigm. Some people seem naturally drawn to the simplicity of prototypal inheritance, while others are much more comfortable in the more familiar classical.

Asymmetrical Reading and Writing of Inherited Members

In classical inheritance, each instance of Author has its own copy of the books array. You could add to it by writing author[1].books.push(‘New Book Title’). That is not initially possible with the object you created using prototypal inheritance because of the way prototype chaining works. A clone is not a fully independent copy of its prototype object; it is a new empty object with its prototype attribute set to the prototype object. When it is just created, author[1].name is actually a link back to the primitive Person.name. This is because of the asymmetry inherent in reading and writing objects linked from the prototype. When you read the value of author[1].name, you are getting the value linked from the prototype, provided you haven’t defined the name attribute directly on the author[1] instance yet. When you write to author[1].name, you are defining a new attribute directly on the author[1] object.
This example illustrates that asymmetry:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var authorClone = clone(Author);
alert(authorClone.name); // Linked to the primative Person.name, which is the
// string 'default name'.
authorClone.name = 'new name'; // A new primative is created and added to the
// authorClone object itself.
alert(authorClone.name); // Now linked to the primative authorClone.name, which
// is the string 'new name'.
authorClone.books.push('new book'); // authorClone.books is linked to the array
// Author.books. We just modified the
// prototype object's default value, and all
// other objects that link to it will now
// have a new default value there.
authorClone.books = []; // A new array is created and added to the authorClone
// object itself.
authorClone.books.push('new book'); // We are now modifying that new array.

This also illustrates why you must create new copies of data types that are passed by reference. In the previous example, pushing a new value onto the authorClone.books array is actually pushing it to Author.books. This is bad because you just modified the value not only for Author but for any object inheriting from Author that has not yet overwritten the default. You must create
new copies of all arrays and objects before you start changing their members. It is very easy to forget this and modify the value of the prototype object. This should be avoided at all costs; debugging these types of errors can be very time-consuming. In these situations, you can use the hasOwnProperty method to distinguish between inherited members and the object’s actual members.

Sometimes prototype objects will have child objects within them. If you want to override a single value within that child object, you have to recreate the entire thing. This can be done by setting the child object to be an empty object literal and then recreating it, but that would mean that the cloned object would have to know the exact structure and defaults for each child object. In order to keep all objects as loosely coupled as possible, any complex child objects should be created using methods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var CompoundObject = {
string1: 'default value',
childObject: {
bool: true,
num: 10
}
}
var compoundObjectClone = clone(CompoundObject);
// Bad! Changes the value of CompoundObject.childObject.num.
compoundObjectClone.childObject.num = 5;
// Better. Creates a new object, but compoundObject must know the structure
// of that object, and the defaults. This makes CompoundObject and
// compoundObjectClone tightly coupled.
compoundObjectClone.childObject = {
bool: true,
num: 5
};

In this example, childObject is recreated and ompoundObjectClone.childObject.num is modified. The problem is that compoundObjectClone must know that childObject has two attributes, with values true and 10. A better approach is to have a factory method that creates the childObject:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Best approach. Uses a method to create a new object, with the same structure and
// defaults as the original.
var CompoundObject = {};
CompoundObject.string1 = 'default value',
CompoundObject.createChildObject = function() {
return {
bool: true,
num: 10
}
};
CompoundObject.childObject = CompoundObject.createChildObject();
var compoundObjectClone = clone(CompoundObject);
compoundObjectClone.childObject = CompoundObject.createChildObject();
compoundObjectClone.childObject.num = 5;

The clone Function

1
2
3
4
5
6
/* Clone function. */
function clone(object) {
function F() {}
F.prototype = object;
return new F;
}

First the clone function creates a new and empty function, F. It then sets the prototype attribute of F to the prototype object. You can see here the intent of the original JavaScript creators. The prototype attribute is meant to point to the prototype object, and through prototype chaining it provides links to all the inherited members. Lastly, the function creates a new object by calling the new operator on F. The cloned object that is returned is completely empty, except
for the prototype attribute, which is (indirectly) pointing to the prototype oject, by way of the F object.