一般来说,我们用媒介查询(Media Queries)来构建响应式邮件设计。但是最近我发现另一种可以构建响应式设计的方案,通过使用CSS的calc()技术与其他三个宽度属性:width、min-width、max-width。

问题

创建响应式邮件应用很困难,特别是一些手机客户端应用不支持媒介查询。使用hybird、Gmail邮件应用或者不通过媒介查询来构建响应式邮件设计是三种可选方案。

我最近正在研究第三种方案。一开始我给div元素设置fixed width并且设置display属性为inline-block。一旦屏幕可以容纳两个以上的块,它们将会自然的排列下去,但是我还是遇到了问题。

一旦所有的块开始层叠了,它们就不能占满屏幕宽度。
无法占满屏宽
我花了很长一段时间去寻找解决这个问题的方案,Flexbox是一个不错的建议,但不幸的是,电子邮件对Flexbox的支持很差。

一种解决方案

记住width、min-width、max-width

紧接着calc()函数,我在这个方案中引入了三个CSS属性。为了更好的理解它是怎么工作的,我们先来回顾下width、min-width、max-width同时使用时在前台页面中是如何表现的。

  • 如果width属性值比max-width大,max-width属性生效
  • 如果min-width属性值比width或者max-width属性值大,则min-width属性生效

你先猜猜下面这个盒子的样式,宽度会是多少?

1
2
3
4
5
.box {
width:320px;
min-width:480px;
max-width:160px;
}

答案:盒子宽度将会是480px

calc()

不再啰嗦,下面演示如何用这种方案创建在宽度屏幕宽度小于480px的情况下会自动堆积为两列。

1
2
3
4
5
6
.block {
display:inline-block;
min-width:50%;
max-width:100%;
width:calc((480px — 100%) * 480);
}

我们一个个分析这几个属性。

1
min-width:50%;

min-width属性定义的列宽可以让我们在桌面端使用,我们可以改变这个值来添加更多的列,如:改变为25%来显示四列,或者用fixed pixed宽度来设置列宽属性。

1
max-width:100%;

max-width属性定义的列宽可以让我们在手机端使用,由于设置为100%,每列宽都将自适应,撑满容器的宽度。我们也可以改变属性值来在手机端显示多列。

1
width:calc((480px100%) * 480);

多亏了calc()函数,可以给width属性变魔法。值480是我们希望的转换点,值100%表示父容器的宽度,计算的结果为一个比max-width更大或者比min-width更小的值,因此这两个属性中的一个一定会生效并取代width属性。下面是两个例子:
parent width 500px
父元素宽度500px的情况下,计算出来的宽度为-9600px,比min-width要小,因此min-width生效,宽度为50%,呈现两列布局。
parent width 400px
父元素宽度400px的情况下,计算出来的宽度为38400px,远比min-width大,但max-width比它小,因此max-width生效,呈现单列布局。

支持情况

在浏览器端,IE9已经开始支持calc()函数。而且在电子邮件客户端,calc()函数也得到了良好的支持。同时,在Apple Mail(IOS/OS X)、Thunderbird、Outlook(IOS/Android apps)、Gmail(webmail/IOS/Android apps)、AOL(webmail)和老的Outlook.com也能生效。

老的Outlook.com

Outlook.com有一点小瑕疵,就是在calc()函数中会过滤每一个包含括号的属性。这就意味着,支持“calc(480px-100%)”,但不支持calc((480px-100%)*480)。因此我们需要像下面这样改变写法来支持老的Outlook.com:

1
width:calc(480px * 480100% * 480);

不支持的客户端

calc()函数在一些老的电子邮件客户端像Lotus Notes及最新Windows版本的Outlook是不被支持的。同时,在Outlook Web App和Yahoo也不被支持。这些应用中的calc()函数将会被忽略掉。

让步(兼容处理)

在这种情况下,我建议使用fixed width引入一些自定义属性来兼容那些不支持calc()函数的客户端。为了在这些客户端隐藏这几个属性,尽管可能不会生效,我还是建议用calc()函数,下面是第一个例子:

1
2
3
4
5
6
7
8
.block {
display:inline-block;
min-width:240px;
width:50%;
max-width:100%;
min-width:calc(50%);
width:calc(480px * 480 — 100% * 480);
}

Outlook Web App

然而,Outlook Web App(包括Offiec 365及新的Outlook.com)还有一个自带的小瑕疵。当calc()函数包含乘法(*),新的Outlook.com及Office 365将会删除整个对应的内联样式属性。这就意味着我们需要手动计算乘法,并使用计算后的结果。下面是在480px转折点计算乘法后的结果:

1
width:calc(230400px48000%);

WebKit前缀(WebKit Prefixes)

老版本的Android(<Android 5.0)及IOS(<IOS 7)需要加上WebKit前缀以生效。因此我们最终的结果如下:
.block {
display:inline-block;
min-width:240px;
width:50%;
max-width:100%;
min-width:-webkit-calc(50%);
min-width:calc(50%);
width:-webkit-calc(230400px — 48000%);
width:calc(230400px — 48000%);
}

缺点及最后的想法

就像在电子邮件开发世界中的任何一件事物一样,上述方案并不完美。下面是一些我能想到的一些局限性:

  • 在Yahoo上不管用。其桌面端支持媒介查询,因此我们可以稍加改进,让手机端应用优先,并且在桌面端应用中加入媒介查询来巩固它。
  • 只能设置一个转折点。
  • 你只能从桌面版减少列数到手机版,你不能从四列布局的手机版到单列布局的桌面版,虽然这种情况很少见。
  • 最终的写法很难阅读。使用预处理器和mixin生成所有必需的属性可能会有用的多。

不过,我依然认为这个方案在很多情况下会很方便。特别是针对优化Gmail。当然我也确信有很多网站上正在使用的实例。(像小部件、广告等等…)
并且我也迫不及待的想看到这种方案会给你的设计激发怎样的灵感。

(本文译自:The Fab Four technique to create Responsive Emails without Media Queries)



money☜☜☜ wechat 『『『 reward 点击扫码打赏 ~~~ ^_^ 』』』alipay ☞☞☞ money



2016年过去一个月了,才发现。

2015

简单回忆下2015年我都做了什么吧,给2016年的自己留个念想。

  • 01月,醉生梦死。
  • 02月,离开家来到济南。
  • 03月,希望给另一半更好的未来,在夹杂着不舍、迷茫与万般犹豫的思想斗争中,离开济南,来到上海,打拼。
  • 04月,衡量再三,选择一个新领域,开始一份新工作–Front-End Web Developer,学习angular、bootstrap、sass、less
  • 05月,搬家、接手移动端项目工作,学习ioniccordova
  • 06月,飘着小雨的端午节,有不快,更多的还是心情很美妙。工作方面,由于大神离开了,很多东西需要自己摸索,开始学习gulp、restful
  • 07月,由于新项目的需要,开始学习简单android的知识。
  • 08月,由于新项目的需要,自学一段时间的object-c,玩了一段时间的x-code,期间有一阵看的厌烦,好在最后了解一二,成功给ios应用添加上notification,算是一个在ios端独立完成的完整功能吧。
  • 09月,学习nodejs,了解react相关知识。
  • 10月,学习mongodb,并尝试使用nodejs+mongodb,给自己做简单后台。
  • 11月,发现自己基础比较薄弱,开始把更多的时间用在基础知识的学习,包括JavaScript、CSS、HTML5,玩了一段时间github
  • 12月,搭建自己个人博客
  • 01月,开始第一篇英文原版post的翻译,译的好渣。

生活方面,过去一年对我来说真的很长,庆幸相安无恙的走过来了,或许这就是长大了,需要你去承担和经历的。
不过很多时候脾气不好,经常惹人生气,待人处事需要多磨练、学习。四季度明显浮躁了很多,各种任性各种耍脾气,需要坚决改正。

工作方面,新的方向算是新的挑战吧,在前人的基础上,慢慢摸索着学习了一些新的技能,从最开始的陌生到慢慢熟悉。不过到目前为止,很多新知识都没有真正涉足(eg. react/nodejs/mongodb),只是停留在了解层面,这也跟自己过于浮躁及懒惰有关。

2016

虽然2016年已经过去一个月了,而且接下来的几个月会有各种事,但是计划还是要有的,多少执行点,也是赚了旧时光。
最期望做的事包括但不仅限于:

  • G F
  • G F
  • G F
  • HOUSE
  • JOURNEY
  • JS/CSS/HTML5
  • NODE/MONGODB/REACTJS
  • READ POSTS/WRITE POSTS/TRANSLATE POSTS
  • IN THE END, THE DAY AFTER THIS YEAR, I WISH I CAN MAKE MY SELF WORTH ¥20,000+/M

老了,不再敢任性,加油,少年。



money☜☜☜ wechat 『『『 reward 点击扫码打赏 ~~~ ^_^ 』』』alipay ☞☞☞ money



下面这份指南将会探索HTML5的‘pattern’属性,这个属性可以帮助我们定制我们自己的表单验证。

验证(Validation)

表单校验在网站的安全性方面启到的作用与网站的实用性是同等重要的。表单校验工作原理是在提交表单之前,评估输入的内容格式是否正确。例如,如果我们有一个邮箱地址框,那填入的信息必须是有效的邮箱地址;它应该是以字母或数字开头,随后是‘@’字符,并以一个域名地址结尾。

HTML5自带的一些新的文本类型极其预先定义好的一些校验规则,例如email、url及tel等,让表单校验变的更加简单。当输入的值与期望输入的格式不匹配时,输入框会抛出一个错误信息来阻止提交。
email
预估每一种可能的输入是不切实际的,比如,如果我们需要一个用户名、邮编、或者其他任何格式的特殊输入,我们该怎么办,怎么去校验这些输入呢?这就到了我们今天的主题–’pattern’属性。

使用’Pattern’属性

‘pattern’属性只对’input’元素有效。我们可以使用正则表达式定义我们自己的校验规则。同样的,如果输入值不满足条件,输入框会抛出一个异常。

例如,加入我们的表格中有一个用户名输入框,因为我们没有针对用户名的校验标准,因此,我们需要使用正则表达式来校验我们的输入内容。

1
2
3
<form action="somefile.php">
<input type="text" name="username" placeholder="Username">
</form>

下面我们使用’pattern’属性来定义一个规则。假使,我们指定用户名只能由小写字母组成,不允许输入大写字母、数组或者其他特殊字符。另外,用户名的长度不该超过15个字符。用正则来表示的话,规则是’[a-z]{1,15}’。

在我们的用户名输入框中的pattern属性中添加上述值:

1
2
3
<form action="somefile.php">
<input type="text" name="username" placeholder="Username" pattern="[a-z]{1,15}">
</form>

好了,现在它只支持输入小写字母,提交任何其他值都会抛出一个错误信息。
username
就像上面你看到的那样,错误信息是“请按规则输入(Please match the requested format)”。这表示我们的校验生效了,但是,这样的错误信息不利于我们理解我们实际上希望输入怎样的格式。这样的用户体验很失败。

定制校验信息(Customizing the Validation Message)

幸运的是,我们有很多种方式去可以定制更加有用的信息。最直观的方式就是在input元素的’title’属性上添加。

1
2
3
4
5
6
7
8
<form action="somefile.php">
<input
type="text"
name="username"
placeholder="Username"
pattern="[a-z]{1,15}"
title="Username should only contain lowercase letters. e.g. john">
</form>

现在,标题中包含了默认的提示信息:
username1
然而,这个信息提示框还是前后不一致。如果我们拿它与一个邮箱格式的输入框相比,实际的说明应该更突出一点。

第二种方案可以帮助我们解决这个问题。

替换默认的校验信息(Replacing the Default Validation Message)

让我们用一个完全自定义的信息来替换默认的“请按规则输入(Please match the requested format)”。我们需要用到一点JavaScript。

开始之前,为了更好的选中input元素,我们先给它加上id。

1
2
3
4
5
6
7
8
<form action="somefile.php">
<input
type="text"
name="username"
placeholder="Username"
pattern="[a-z]{1,15}"
id="username">
</form>

现在,我们可以用JavaScript选中元素,并赋值给一个变量。(可以直接使用”script”标签或者引入一个额外的JavaScript文件)

1
var input = document.getElementById('username');

然后,当输入框触发校验状态的时候,我们指定特定的信息。

1
2
3
input.oninvalid = function(event) {
event.target.setCustomValidity('Username should only contain lowercase letters. e.g. john');
}

“oninvalid”事件继承自”event”对象,其包涵有一系列属性,包括”target”属性(校验的元素)及带有错误信息的”validationMessage”属性。
在上面的例子中,我们使用”setCustomValidity()”方法替换了提示信息。

现在我们应该可以发现默认的提示信息被无缝替换了。
username3

样式(Styling)

为了补充新的输入类型及设置一个自定义的验证消息,CSS3规范带来了几个有用的伪类,’:valid’及’:invalid’。这就能够让我们根据不同的输入校验状态应用相应的样式,例如:

1
2
3
4
5
6
7
input:invalid {
border-color: red;
}
input,
input:valid {
border-color: #ccc;
}

当使用这些伪类时,有几点需要记住的是:

  • 首先,”:valid”是默认样式,即使输入值为空。因此,正如上面你看到那样,我们设置”border-color”为”#ccc”,则输入框元素默认的颜色也是这个。空值永远被认为是有效输入,除非我们已经加了“必填”(required)属性。那样的话,输入校验无效,边框将变成给定的红色。
  • 有效和无效样式将在用户输入时立马生效,即使值为空。即时样式改变可能会造成用户的恐慌。

弹出框样式一致性(A Word on Styling the Popup Message)

表单校验作为一个HTM5的特性已经变成了一种规范,然而,错误信息提示框如何呈现却完全取决于浏览器厂商。
如果在不同的浏览器上呈现不一样的效果,这对你界面的一致性是没有帮助的。
html5-validation-different-browsers

谷歌浏览器在几年前阻止用户去自定义默认的弹框。如果你想做啥改变的话,唯一可供选择的方式是使用JavaScript覆盖弹框信息,下面我们来看看应该怎么做。

进阶(Going More Advanced)

我们先创建一个当我们的输入值有效的时候才呈现的自定义弹框。在一开始,我们需要选中一些必要的元素,包括”input”及”form”元素:

1
2
var input = document.getElementById('username');
var form = document.getElementById('form');

然后,新建一个包含我们信息的新元素:

1
2
3
4
var elem = document.createElement('div');
elem.id = 'notify';
elem.style.display = 'none';
form.appendChild(elem);

上面我们创建了一个新的div元素,设置其id为”notify”,设置其”display”样式为”none”。最后,将其插入到”form”元素中。

使用事件(Working with Events)

我们需要处理两个事件。首先,当输入的值不合法的时候,需要触发”invalid”事件。下面我们添加”invalid”事件:

1
2
3
4
5
6
7
8
9
10
input.addEventListener('invalid', function(event){
event.preventDefault();
if ( ! event.target.validity.valid ) {
elem.textContent = 'Username should only contain lowercase letters e.g. john';
elem.className = 'error';
elem.style.display = 'block';
input.className = 'invalid animated shake';
}
});

在这里,利用”event.preventDefault();”,我们可以阻止默认的行为,这样浏览器就不会显示默认的弹框。
相反,我们显示新建的div元素。我们在内容区域里添加文本信息,添加新的样式”error”,并且通过设置”display”属性为”block”来显示信息。

同样的,我们给输入框添加一个”invalid”样式,让它边框为红色。我们也需要在样式表中设置其样式:

1
2
3
input.invalid {
border-color: #DD2C00;
}

另外,你也可以加一些抖动样式通过”Animate.css”,这可以加上一些抖动的动画。

第二个事件是”input”事件,它将在输入值改变的时候触发。我们可以使用这个事件将输入值恢复到合法输入的状态,也可以隐藏弹出框,如下:

1
2
3
4
5
6
input.addEventListener('input', function(event){
if ( 'block' === elem.style.display ) {
input.className = '';
elem.style.display = 'none';
}
});

如上文,我们可以通过移除input元素的样式来隐藏弹出框。
现在我们可以完全定制自己的弹出校验框了,试试输入任何一些非法的值。

最后几点(Final Thoughts)

在标准的输入类型中加上pattern属性可以给表单带来一些额外的校验形式,但是请注意,你也应该执行一些服务器端的校验。

神奇的是,即使当用户禁止了浏览器端的JavaScript,最新版的浏览器也会显示校验提示框并阻止表单提交。
然而,对于Safari浏览器来说,在用户输入时,不支持pattern属性。不过使用”Webshim Lib”也可以启到类似的效果。

最后,希望你能喜欢本教程,并且将其作为一个针对HTML5表单校验的简单参考指南。
(本文译自:HTML5 Form Validation With the “pattern” Attribute)



money☜☜☜ wechat 『『『 reward 点击扫码打赏 ~~~ ^_^ 』』』alipay ☞☞☞ money



当我第一次接触到关于JavaScript对象模型的知识,我的反应是震惊的,充满了不可思议.那是我第一次接触基于原型的语言,我被它的原型特征完全搞糊涂了.我不理解把原型加在JavaScript方法的构造函数组成上有啥不同,我敢打赌,你们中的大多数人也曾有过类似的经历.

但是当我经常使用JavaScript之后,我不仅理解了它的对象模型,而且也爱上了它这部分特性.多亏了JavaScript,我才能发现基于原型语言的典雅与灵活.正因为他们比基于类的语言更简单且更灵活,我现在更倾向于原型语言.

JavaScript的原型

许多指南介绍JavaScript对象时都以方法的构造函数开始,我认为这是个误区,因为他们在一开始就介绍复杂的内容会让JavaScript看起来很难,一开始就理解的很困惑.让我们后续介绍这个,我们先从原型基础说起.

原型链

在JavaScript中,每一个对象都有原型(prototype)属性,当有一个消息指向对象,JavaScript会先尝试在其寻找对应的属性,
如果没有找到,这条消息会被传递到对象的原型上,并依次传递下去.这种工作机制有点像基于类的语言的单亲继承.
原型链可以指向任何你想指向的对象,但是一般来说,太长的原型链会让你的代码理解起来很困难并且很难维护.

proto‘对象

理解JavaScript原型链最简单的就是先理解proto属性,不幸的是,proto属性并不是JavaScript标准的一部分,
至少在ES6之前如此.因此你不能在项目代码中应用它,但不管怎么说,它让解释原型变得更简单.

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
// let's create an alien object
var alien = {
kind: 'alien'
}
// and a person object
var person = {
kind: 'person'
}
// and an object called 'zack'
var zack = {};
// assign alien as the prototype of zack
zack.__proto__ = alien
// zack is now linked to alien
// it 'inherits' the properties of alien
console.log(zack.kind); //=> ‘alien’
// assign person as the prototype of zack
zack.__proto__ = person
// and now zack is linked to person
console.log(zack.kind); //=> ‘person’

如你所见, proto属性通俗易懂,也很好用.即使我们不能在项目代码中使用它,我也认为这些例子对理解JavaScript对象模型起到很大的帮助.

你可以像如下这样检查一个对象是否是另一个对象的原型:

1
2
console.log(alien.isPrototypeOf(zack))
//=> true

原型查找是动态的

你可以在任何时刻给一个对象的原型添加属性,原型链可以如预期那样找到这个新属性.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var person = {}
var zack = {}
zack.__proto__ = person
// zack doesn't respond to kind at this point
console.log(zack.kind); //=> undefined
// let's add kind to person
person.kind = 'person'
// now zack responds to kind
// because it finds 'kind' in person
console.log(zack.kind); //=> 'person'

给对象新增/修改的属性,将不会再指向原型

如果你给对象添加一个在原型上已经存在的属性,会发生什么呢?我们来看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var person = {
kind: 'person'
}
var zack = {}
zack.__proto__ = person
zack.kind = 'zack'
console.log(zack.kind); //=> 'zack'
// zack now has a 'kind' property
console.log(person.kind); //=> 'person'
// person has not being modified

注意,属性’kind’现在同时存在于person与zack.

Object.create

正如我说的,由于分配给对象原型的proto属性并不被良好支持,因此下一个最简单的方式是使用Object.create().
这个在ES5中已经支持,但是在一些老的浏览器或者引擎中需要使用es5-shim来转换.

1
2
3
4
5
6
7
8
var person = {
kind: 'person'
}
// creates a new object which prototype is person
var zack = Object.create(person);
console.log(zack.kind); // => ‘person

你可以使用Object.create方法去创建一个新对象,并将另一个对象及一些特殊属性当参数传递进去.

1
2
var zack = Object.create(person, {age: {value: 13} });
console.log(zack.age); // => ‘13’

是的,你需要传递对象有一点绕,但这就是它的用法.如果你想了解更多,点击这里

Object.getPrototype

你可以使用Object.getPrototypeOf方法去获取对象的原型

1
2
var zack = Object.create(person);
Object.getPrototypeOf(zack); //=> person

注意没有所谓的Object.setPrototype.

构造器函数(Constructor Functions)

在JavaScript中,构造器函数用的最多的地方就是用在构造原型链上.
构造器函数的流行来自于这样一个事实:它是唯一的一种针对构造类型最原始的方式.
当然还有其他一个重要的事实是其他很多引擎已经高度支持构造器函数.

不幸的是,这让人很困惑.在我看来,这是我觉得JavaScript让菜鸟很困惑的一个主要原因.
因此这也是这门语言中很大的一个需要我们去很好理解并掌握的模块.

函数作为构造器

JavaScript中,你可以像下面这样创建一个函数的实例:

1
2
3
4
5
6
function Foo(){}
var foo = new Foo();
//foo is now an instance of Foo
console.log(foo instanceof Foo ) //=> true

像这样使用’new’关键字,看似用工厂模式新建一个函数,实际上表示创建一个新对象.这个新对象的原型会指向这个函数,
这里以后再详述.因此在JavaScript中,我们把’this’称作函数实例.

‘this’是隐式分配的

当我们使用’new’关键字时,JavaScript以’this’关键字的形式注入一个指向新对象的隐式引用.而且,在函数最后,将这个隐式引用返回.
当我们像下面这样时:

1
2
3
4
5
6
function Foo() {
this.kind = ‘foo’
}
var foo = new Foo();
foo.kind //=> ‘foo’

在这个场景后面,又像是在做下面这件事情:

1
2
3
4
5
6
7
8
function Foo() {
var this = {}; // this is not valid, just for illustration
this.__proto__ = Foo.prototype;
this.kind = ‘foo’
return this;
}

但是请记住,当只有使用’new’关键字时,才将隐式的’this’分配到新对象上.如果你忘记使用’new’关键字,’this’将指向全局对象(global object).
忘记使用’new’关键字会引起一系列bug,因此不要忘记使用’new’关键字.

有一个我比较喜欢的约定是当这个函数要被当成函数构造器使用的时候,有效利用函数的第一个字母(译者注:一般我习惯首字母大写),
这样可以很直观的发现你忘记了’new’关键字.

函数的原型属性(The ‘function prototype’)

在JavaScript中,每一个函数都有一个特殊的属性:’prototype’.

1
2
3
4
function Foo(){
}
Foo.prototype

尽管听起来可能很费解,这个’prototype’属性并不是函数的原型.

1
Foo.__proto__ === Foo.prototype //=> false

当然,使用’prototype’表示不一样的东西会引起人们很多疑问.
我认为当表示函数的’prototype’属性的时候,称之为’函数的原型属性’(‘the function prototype’)而不仅仅是原型
可以做到更好的区分.

当使用’new’关键字创建对象, 新对象的原型会指向函数的原型属性(‘prototype’ property).
困惑吧?下面的例子可以更好的解释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Person(name) {
this.name = name;
}
// the function person has a prototype property
// we can add properties to this function prototype
Person.prototype.kind = ‘person’
// when we create a new object using new
var zack = new Person(‘Zack’);
// the prototype of the new object points to person.prototype
zack.__proto__ == Person.prototype //=> true
// in the new object we have access to properties defined in Person.prototype
zack.kind //=> person

上面就是关于JavaScript对象模型你最需要了解的一些知识,理解proto及function.prototype的关系会给你无尽的乐趣及满足,
当然也许并不会.

最后,如果有不对的地方,或者你有什么不解,请留言联系.
(本文译自:A Plain English Guide to JavaScript Prototypes)



money☜☜☜ wechat 『『『 reward 点击扫码打赏 ~~~ ^_^ 』』』alipay ☞☞☞ money



由于controllers之间不共享scope,如果希望在controllers之间传递参数,可能需要通过其他的方式实现,
以下是当前我用到的两种在controllers之间传递参数的方法.
注:参考文章Sharing Data Between Angular Controllers

service

可以写一个包含get/set的service,取参数/赋参数

1
2
3
4
5
6
7
8
9
10
11
.factory('paramService',function(){
return {
result:[],
getResult:function(){
return this.result;
},
setResult:function(res){
this.result = res;
}
};
})

然后可以在controllerOne中赋值,在controllerTwo中取值

1
2
3
4
5
6
7
8
9
// 赋值
.controller('one',function(paramService){
paramService.setResult('one');
})
// 取值
.controller('two',function(paramService){
var param = paramService.getResult();
})

$stateParams

第二种方法用于路由间传递参数,用途也比较广泛,使用场景比较多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 传参
.state('one',{
url:'one',
controller:'one',
template:'one.html',
params:{
name:'john'
}
})
// 取参
.controller('one',function($stateParams){
var name = $stateParams.name;
})
others/localStorage

其他方法可以使用一些h5的小技巧,比如使用localStorage来存参/取参,其他的方法,暂时没想到也没用到,有待后续补充.



money☜☜☜ wechat 『『『 reward 点击扫码打赏 ~~~ ^_^ 』』』alipay ☞☞☞ money



在html中读取图片资源,一般要使用img标签的src属性,或者用于填充div的背景,使图片显示出来,此时要设置background样式的url属性。
一般情况下,src属性值或者url属性值,都为一个路径可访问的资源链接,如:http:xxx.com/xxx.jpg,或者当前项目里的图片资源,如:../xxx.jpg。
而,当在android应用中,拍完的照片存储在某一个路径,如com.xxx/下时,使用后者是访问不到的,这就需要我们借助其他方式去读取当前照片。
一般的思路是先找到路径com.xxx,获取到当前图片,然后把其路径赋值为src属性或者url属性,但是在android系统中,得到的路径在html中是找不到的。
下面,换个思路,先找到图片,然后把其资源以base64读出来,然后拿到这串base64字符串,就可以直接赋值给src属性或者url属性,以下借助cordova的文件系统插件来实现。

首先,了解cordova框架及如何新建hybird工程、包括如何打包发布;

假使你已经知道如何使用cordova,接下来的步骤如下:

添加插件
1
2
3
4
5
6
7
使用以下命令添加有关文件系统插件
cordova plugin add cordova-plugin-file
cordova plugin add cordova-plugin-file-transfer
插件具体用途,请自行查阅,主要是结合h5文件系统相关的封装
可以使用cordova plugin命令查看安装的插件,及版本信息
使用插件
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
注意:由于js是单线程执行,因此需要考虑异步读取照片.
// 以下代码,readDir表示成功请求文件系统回调函数,会传入文件系统对象作为参数,error表示请求失败回调函数,需要在调用之前定义,否则找不到指定的方法;
if (window.plugins) {
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, readDir, error);
}
// 以下代码,用于获取指定的Directory对象,create:false表示如果不存在,不新建,设置为true表示如果不存在,新建当前目录.readFile表示成功回调,会传入当前找到的Directory对象作为参数,error表示失败回调
// readFile及error同样需要提前定义,否则找不到指定的方法;
var readDir = function (fileSystem) {
fileSystem.root.getDirectory('com.xxxx', {
create : false,
exclusive: false
}, readFile, error);
};
// 以下代码用于读取目录下的jpg文件,读取到的jpg文件先添加到事先定义的imgEntry数组中,此时,imgEntry里面存放着多个entry,通过事先定义好的readPic方法,进行读取资源
var imgEntry = [];
var readFile = function (entry) {
var dirReader = entry.createReader();
// Call the reader.readEntries() until no more results are returned.
var readEntries = function () {
dirReader.readEntries(function (results) {
for (var i = 0; i < results.length; i++) {
if (results[i].name.indexOf('jpg') > 0) {
imgEntry.push(results[i]);
}
}
// 依次读取照片entry
readPic();
}, error);
};
readEntries(); // Start reading dirs.
};
// 以下代码,用imgList数组来存放h5中需要读取的图片数组,其对象中的src属性存放,src属性值或者url值
// 具体思路是,如果imgEntry里面有值,取出第一个,然后用entry.file方法,去解析fileEntry,解析成功,拿到file对象,再通过FileReader去读取当前file对象
// onload方法表示读取完成,此时结果里存放的是base64的图片资源字符串,将其保存下来
// 注意:之所以每次只读取一个entry,并且在解析完成后,再读取下一张,是因为entry.file和fr.onload方法都是异步处理,必须成功读取完一张照片,再读取下一张,而不是直接用for去遍历imgEntry读取
imgList = [];
var readPic = function () {
if (imgEntry && imgEntry.length) {
var entry = imgEntry.shift();
entry.file(function (file) {
var fr = new FileReader();
fr.onload = function () {
imgList.push({name: file.name, src: this.result});
if (imgEntry.length) {
readPic();
}
};
fr.readAsDataURL(file);
}, error);
}
};
整个过程执行完,可以用imgList[i].src让某个div或者img显示照片


money☜☜☜ wechat 『『『 reward 点击扫码打赏 ~~~ ^_^ 』』』alipay ☞☞☞ money



默认情况下,指令应该访问父作用域.如果我们对指令暴露了父控制器的scope,那么指令就可以自由的修改scope属性.在一些情况下,你的指令可能想要添加一些只有内部可以使用的属性和函数,如果我们都在父作用域中完成,可能会污染了父作用域,因此,我们有以下两种选择:

  • 使用父作用域-如果不需要操作父作用域属性,不需要一个新的作用域,可以直接使用父作用域
    scope:false

  • 一个子作用域-这个作用域会原型继承父作用域
    scope:true

  • 一个隔离的作用域-一个全新的、不继承、独立存在的作用域
    scope:{}

作用域可以由指令定义对象中的scope属性定义,下面是关于scope属性的一些说明:

指令中的scope常见的类型

=
1
2
3
4
5
'=',用于子作用域与父作用域双向绑定.使用这种方法可以将一个实际的作用域模型赋值给一个属性,而不是一个普通的字符串.效果是你可以传递复杂的数据模型,例如数组/对象等到隔离作用域.父作用域或者子作用域属性发生了改变,会相应影响对方.
'=?',这种情况可以避免父作用域属性中不存在当前属性情况,避免抛出异常.--'If the parent scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional.'
'=*',If you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional).
&
1
'&',用于执行父作用域中的函数.
@
1
'@',进行单项文本绑定.使用这种方法可以将字符串传递到属性,当父作用域属性发生变化时,隔离作用域模型也发生变化.然而,反之则不成立!你不能通过操纵隔离作用域来改变父作用域.


money☜☜☜ wechat 『『『 reward 点击扫码打赏 ~~~ ^_^ 』』』alipay ☞☞☞ money



68 Specific Ways to Harness the Power of JavaScript
编写高质量JavaScript代码的68个有效方法

注:本文内容来自同名图书读书笔记,只涉及部分条目,序号会保持原书一致

三.使用函数

18.理解函数调用/方法调用及构造函数调用之间的不同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
函数调用示例:
function test(x){
alert(x)
}
test('1');
方法调用示例:
var obj = {
test:function(){
alert(this.name);
},
name:'john'
}
obj.test(); // 'john'
var obj2 = {
test:obj.test,
name:'mike'
}
obj2.test(); // 'mike'
需要注意的是,通过方法调用,绑定到this变量的对象被称为调用接收者,也就是说被绑定到了调用方法的对象上.
构造函数调用示例:
function Test(name,age){
this.name = name;
this.age = age;
}
var t = new Test('john',20);
构造函数的主要职责是初始化对象.
19.熟练掌握高阶函数

高阶函数无非是那些将函数作为参数或者返回值的函数.

1
2
3
4
5
常见的sort/map等函数都属于高阶函数,加强学习,掌握.例如:
var names = ['john','lily','tom'];
var upper = names.map(function(name){
return name.toUpperCase();
});



money☜☜☜ wechat 『『『 reward 点击扫码打赏 ~~~ ^_^ 』』』alipay ☞☞☞ money



68 Specific Ways to Harness the Power of JavaScript
编写高质量JavaScript代码的68个有效方法

注:本文内容来自同名图书读书笔记,只涉及部分条目,序号会保持原书一致

一.让自己习惯JavaScript

1.了解你使用的JavaScript版本
1
2
3
4
5
6
7
8
9
10
1999. ES3 (ECMAScript)
2009. ES5,ES5加入了严格模式,对于新旧代码的链接,可以选择以下两种方案:
a.不要将进行严格模式检查的文件与不进行严格模式检查的文件连接起来;
b.将其自身包裹在立即调用的函数表达式(Immediately Invoked Function Expression, IIFE)中,来连接多个文件
(function(){
function f(){};
})()
next. ES6
2.理解JavaScript的浮点数
1
2
3
4
5
在js中,数值类型只有一种:number,双精度浮点数
注意浮点数计算的陷阱
0.1+0.2; // 0.30000000000000004
浮点数计算,比较简单的解决办法是先换算成整数计算
3.当心隐式的强制转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
在js中, 3+true; // 4
一个未定义的变量会被转换为NaN,标准的库函数isNaN,会将其参数强制转换为数字,
因此对于一个不确定是否为数值类型的变量来说,用其测试不可靠.
isNaN('foo') // true
isNaN(undefined) // true
isNaN({}) // true
isNaN({valueOf:'foo'}) // true
有一个既简单又可靠的检测NaN的习惯用法,由于NaN是js中唯一一个不等于其自身的值,
因此可以通过检测是否等于自身来测试是否为NaN.
var a = NaN;
a !== a; // true
可以将其抽象为工具函数
function isRealNaN(x){
return x !== x;
}
对象通过隐式调用其自身的toString方法转换为字符串
对象也可以通过其自身的valueOf方法转换为数字
js中有7个假值: false/0/-0/""/NaN/null/undefined
4.原始类型优于封装对象
1
2
3
4
5
6
7
8
9
10
11
12
js除了对象类型外,还有5个原始值类型:布尔/数字/字符串/null/undefined.
标准库为布尔/数字/字符串提供了构造函数来封装成对象,例如:
var s = new String('hello');
不同于原始的字符串,String对象是一个真正的对象
typeof 'hello' // 'string'
typeof s // 'object'
var s2 = new String('hello')
s === s2 // false
s == s2 // false
原始类型的封装对象与其原始值,行为可能不一样,原始值类型要优于封装对象
5.避免对混合类型使用 == 运算符
1
2
3
当参数类型不同时, == 运算符应用了隐式强制转换规则
当使用了 === 运算符,可以明确比较两边的值与类型都相等
当比较不同类型的值时,使用自己定义的显示转换可以让程序的行为更明确

二.变量作用域

8.尽量少用全局对象/变量

定义全局变量会污染共享的公共命名空间,可能导致意外的命名冲突,而且不利于模块化,因为它会导致程序中独立组件间的不必要耦合.
由于全局命名空间是js程序中独立组件进行交互的唯一途径,因此,利用全局命名空间的情况是不可避免的.
尽量只在组件或者程序库不得不定义全局变量的时候,使用全局变量.

1
2
3
4
5
6
避免声明全局变量
避免对全局对象添加属性
可以使用全局对象来做平台特性检测,如,要测试当前环境是否支持JSON对象,可以如下实现:
if(!this.JSON){
...

11.熟练使用闭包
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
三个关于闭包的基本事实:
a.js允许你引用在当前函数以外定义的变量 (这个很显然,不解释)
b.即使外部函数已经返回,当前函数仍然可以引用在外部函数所定义的变量,例如:
function doubleAdd(){
var other = 1;
function add(x){
return other + x;
}
return add;
}
var f = doubleAdd();
f(2) // 3
f(4) // 5
以上函数可以修改为匿名函数,如下:
function doubleAdd(){
var other = 1;
return function(x){
return other + x;
}
}
c.闭包可以更新外部变量的值,例如:
function box(){
var val = undefined;
return{
set:function(newVal){val = newVal;}
get:function(newVal){return val;}
};
}
var b = box();
b.set(11);
b.get(); // 11
13.使用立即调用的函数表达式创建局部作用域
1
闭包通过引用而不是值捕获它们的外部变量
16.避免使用eval创建局部变量
1
2
3
4
5
6
7
8
9
10
11
12
调用eval函数会将其参数作为js程序进行解释,但是该程序运行于调用者的局部作用域中,嵌入到程序中的全局变量会被创建为调用程序的局部变量.
保证eval函数不影响外部作用域的一个简单方法是在一个明确的嵌套作用域中运行它,例如:
var y = 'global';
function test(s){
(function(){
eval(s);
})();
return y;
}
test("var y = 'local'"); // global
test("var z = 'local'"); // global
17.间接调用eval函数优于直接调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
通过函数调用方式使用eval,认为是"直接"调用eval函数的方式,例如:
var x = 'global';
function test(){
var x = 'local';
return eval("x");
}
test(); // local
绑定eval到另一个变量名,通过该变量名调用函数会使代码失去对所有局部作用域的访问能力,例如:
var x = 'global';
function test(){
var x = 'local';
var f = eval;
return f("x");
}
test(); // global
尽可能间接调用eval函数,而不要直接调用eval函数.


money☜☜☜ wechat 『『『 reward 点击扫码打赏 ~~~ ^_^ 』』』alipay ☞☞☞ money



针对下面这个ul,为每一个li添加一个点击事件,弹出对应的index
<ul id="text">
    <li>这是第一个li</li>
    <li>这是第二个li</li>
    <li>这是第三个li</li>
</ul>
解答一:bind,将当前匿名函数指向this,将i当参数传入
var init = function(){
var obj = document.getElementById('text');
for(var i=0;i<obj.children.length;i++){
  obj.children[i].addEventListener('click',function(i){
    alert(i)
  }.bind(this,i))
}
}
init();
解答二:闭包
var init = function(){
var lis=document.querySelectorAll("#text li");
  for(var i=0;i<lis.length;i++){
        lis[i].onclick=(function(i){
              return function(){
                    alert(i);
              };
        })(i)
  }
}
init();
解答三:最笨的方法,匹配
var init = function(){
  var obj = document.getElementById('text');
  for(var i=0;i<obj.children.length;i++){
    obj.children[i].addEventListener('click',function(item){
      var self = item.target;
      for(var j=0;j<obj.children.length;j++){
        if(self == obj.children[j]){
          alert(j);
        }
      }
    })
  }
}
init();


money☜☜☜ wechat 『『『 reward 点击扫码打赏 ~~~ ^_^ 』』』alipay ☞☞☞ money