sidebarDepth |
---|
0 |
经历了许久的职场生活和感情生活后,平静的生活有如湖面一般沉寂,于是便思考,是不是应该在短暂有限的人生路途中做点什么事情,这样子当老的时候,再回顾自己的生平做了什么事情,才不至于没有波澜,也没有涟漪。于是乎就在这个博客记录一些自己的想法、读书心得与收获、技术上的积累、产品上的思考以及对于为人处世的方与圆。
一兵的博客
Home Page: https://debingfeng.github.io/blog
本文图片、部分翻译来自原文 How Flexbox works — explained with big, colorful, animated gifs
display: flex
示例如下
上面4个 div
默认为 display: block
我们给其父级容器添加
#container {
display: flex;
}
其实把每个 div
添加了一个 flex context (弹性上下文)
一个 Flexbox 的容器有两个轴:主轴 交叉轴。如下:
默认情况下容器中的每个元素都会沿着主轴自左向右一次排列。所以容器设置为 display: flex
后所有的 div
会排列在一行上
但是 Flex-direction 属性可以让主轴旋转,如下:
注意: flex-direction: column
并不是把 div
从主轴移动到交叉轴上,而是让主轴从水平变为垂直
flex-direction 还有两者值: row-reverse
和 column-reverse
(顾名思义 就是反转)
justify-content 用来控制元素在主轴上的对齐方式
先深入理解下主轴和交叉轴的区别。首先设置为 flex-direction: row
#container {
display: flex;
flex-direction: row;
justify-content: flex-start;
}
justify-content
有5个可选值:
space-between
会使每个 div
之间产生相同的小的间隔,但在 div
和容器之间没有间隔
space-around
会在每个 div
两侧各产生一个相同的间隔,即容器和最外层的 div
之间的间隔刚好是两个 div
间隔的一半
注意:justify-content
是沿着主轴工作的。 flex-direction
是改变的主轴方向的。
justify-content
是沿着主轴工作的,而 align-items
则是沿着交叉轴工作。
首先重置 flex-direction: row
两个轴展示如下:
align-items
有5个可选值:
看看后两个,其中每个 div
中的数字都包含在一个 p
标签中
align-items: stretch
时每个 div
都会充满交叉轴
align-items: baseline
时按照 p
标签的底部对齐
注意:align-items: stretch
时每个 div
的 height 必须为 auto 否则 height 属性会覆盖 stretch 的效果
align-items: baseline
时如果 div
内没有 p
标签或者 div
内没有文字或者子标签内没有文字将按照每个 div
的底部对齐。如下:
进一步理解主轴和交叉轴的区别,把 justify-content 和 align-items 合在一起,看看在 flex-direction 两种值下的效果
flex-direction: row
时每个 div
按照水平主轴排列
flex-direction: column
时每个 div
按照垂直主轴向下排列
align-self
可以手动设置一个元素的对齐方式
它会针对一个 div
覆盖掉 align-items
属性,因为容器内元素属性都为 auto
, 所以每个 div
都会使用父容器的 align-items
属性值
#container {
align-items: flex-start;
}
.square#one {
align-self: center;
}
/* 只有 #one 这个 div 会居中 */
将前两个 div
设置 align-self 属性,后两个使用 align-items: center
和 flex-direction: row
如下
[toc]
我们每天都在使用模式为了了解使用模式的好处,我们来研究一个非常简单的元素选择问题,我们通常是用jQuery库来解决这种问题。试想,如果我们有一个脚本,想为页面上每个具有“foo”类(class属性)的DOM元素增加一个计数器,查询列表最简单有效的方法是什么?对,有几种不同的方法可以解决这个问题。
在页面上选择所有元素并存储。接着过滤该集合并使用正则表达式(或另一种方式)来存储那些具有“foo”类的元素。
使用浏览器原生的querySelectorAll()等功能来选择所有具有“foo”类的元素。
同样地,使用原生特性getElementsByClassName()等功能来重新获得所需的集合。那么,哪种方法最快?最快的实际上是第3种方法,它比其他方法快8至10倍。但在实际应用中,IE9以下的版本不支持第3种方法,因此必须使用第1种方法,而其他方法都行不通。但使用jQuery的开发人员不必担心这个问题,因为通过使用Facade(外观)模式,它已经被抽象出来了。正如我们将在后面详细介绍的,该模式为若干更复杂的底层代码体提供了一套简单的抽象接口(例如$el.css()和$el.animate())。正如我们所看到的,这意味着我们可以在“实现级”细节上花费更少的时间。
根据现有浏览器的支持范围,jQuery会在幕后选择最佳的元素选择方式,我们只需要使用抽象层即可。我们可能都很熟悉jQuery的$(“selector”)。用它选择页面上的HTML元素比需要手动处理的getElementById()、getElementsByClassName()、getElementByTagName等容易得多。虽然我们知道querySelectorAll()可以解决这个问题,但让我们来比较一下使用jQuery的Facade(外观)模式接口与自己选择最佳路径这两种方式所花费的精力。根本就不用比!使用设计模式的抽象可以体现真实价值。
编写易于维护的代码,其中一个最重要方面是能够找到代码中重复出现的主题并优化它们。这也是设计模式有价值的地方。
模式是一种可复用的解决方案,可用于解决软件设计中遇到的常见问题,如在我们编写的JavaScript应用程序的实例中。另一种模式的方式是将解决问题的方法制作成模板,并且这些模板可应用于多种不同的情况。
它们为解决软件开发中遇到的问题提供可靠的方法,也就是使用已经验证的解决方案,这些解决方案体现了开发人员的经验及见解,他们为定义和改进这些方法提供了帮助,从而形成现在的模式。
它们为解决软件开发中遇到的问题提供可靠的方法,也就是使用已经验证的解决方案,这些解决方案体现了开发人员的经验及见解,他们为定义和改进这些方法提供了帮助,从而形成现在的模式。
看到模式时,通常就表示有一个设置好的结构和表达解决方案的词汇,以帮助我们非常轻松地表达出所实现的大型解决方案。
模式不是一种确切的解决方案。重要的是,我们要知道模式的作用仅仅是为我们提供一个解决问题的方案。模式无法解决所有的设计问题,也无法取代优秀软件设计师的工作,但模式确实能够支持这些工作。接下来我们将了解一下模式的其他一些优点。
复用模式有助于防止在应用程序开发过程中小问题引发大问题。这意味着当我们在已经验证的模式基础上编写代码时,可以在代码结构上少花点时间,从而有更多的时间专注于整体解决方案的质量。这是因为模式支持我们用更结构化和更有组织性的方式编写代码,从而避免以后因代码的整洁性问题而重构代码。
模式可以提供通用的解决方案,并且其记录方式不需要与某个特定问题挂钩。这种通用的方法意味着不管现在开发的是哪种应用程序(在许多情况下是指编程语言),设计模式都可用于改进代码的结构。 •某些模式确实能够通过避免代码复用来减少代码的总体资源占用量。通过鼓励开发人员更密切地关注解决方案中可以即刻减少代码复用的部分,例如,减少类似处理过程的函数数量,用一个广义函数取而代之,那么就可以减小代码库的总大小。这也就是所谓的使代码更加简洁。 •模式添加到开发人员的词汇中,会使沟通更快速。
经常使用的模式可以逐步改进,因为其他开发人员使用这些模式后总结出的共同经验又贡献给了设计模式社区。在某些情况下,这会创造出全新的设计模式,而在其他情况下,会对有关如何更好地使用特定模式的指导做出改进。这样可
队列是一种列表,不同的是队列只能在队尾插入元素,在对首删除元素。队列用于存储按顺序排列的数据,先进先出。队列应用比较广泛,提交操作系统执行一些进程,打印任务池,日常排队买东西等等。
function Queue() {
this.dataStore = [];
this.enqueue = enqueue;
this.dequeue = dequeue;
this.front = front;
this.back = back;
this.toString = toString;
this.empty = empty;
}
function enqueue(element) {
this.dataStore.push(element)
}
function dequeue() {
this.dataStore.shift()
}
function front() {
return this.dataStore[0];
}
function back() {
return this.dataStore[this.dataStore.length - 1];
}
function toString() {
var str = '',
i = 0,
l = this.dataStore.length;
for ( ; i < l; i++) {
str += this.dataStore[i] + "\n";
}
return str;
}
function empty() {
return this.dataStore.length === 0;
}
var q = new Queue();
特别注意:安装过程不要勾选安装compass 相当慢,有时候甚至卡住一动不动
MongoDB将数据目录存储在 db 目录下。但是这个数据目录不会主动创建,我们在安装完成后需要创建它。请注意,数据目录应该放在根目录下((如: C:\ 或者 D:\ 等 )。
cd d:
mkdir mongdb
为了从命令提示符下运行 MongoDB 服务器,你必须从 MongoDB 目录的 bin 目录中执行 mongod.exe 文件。
C:\mongodb\bin\mongod --dbpath c:\data\db
// 执行成功后,会输出如下信息
2015-09-25T15:54:09.212+0800 I CONTROL Hotfix KB2731284 or later update is not
installed, will zero-out data files
2015-09-25T15:54:09.229+0800 I JOURNAL [initandlisten] journal dir=c:\data\db\j
ournal
2015-09-25T15:54:09.237+0800 I JOURNAL [initandlisten] recover : no journal fil
es present, no recovery needed
2015-09-25T15:54:09.290+0800 I JOURNAL [durability] Durability thread started
2015-09-25T15:54:09.294+0800 I CONTROL [initandlisten] MongoDB starting : pid=2
488 port=27017 dbpath=c:\data\db 64-bit host=WIN-1VONBJOCE88
2015-09-25T15:54:09.296+0800 I CONTROL [initandlisten] targetMinOS: Windows 7/W
indows Server 2008 R2
2015-09-25T15:54:09.298+0800 I CONTROL [initandlisten] db version v3.0.6
安装 MongoDB服务
通过执行mongod.exe,使用--install选项来安装服务,使用--config选项来指定之前创建的配置文件。
C:\mongodb\bin\mongod.exe --config "C:\mongodb\mongod.cfg" --install
要使用备用 dbpath,可以在配置文件(例如:C:\mongodb\mongod.cfg)或命令行中通过 --dbpath 选项指定。
如果需要,您可以安装 mongod.exe 或 mongos.exe 的多个实例的服务。只需要通过使用 --serviceName 和 --serviceDisplayName 指定不同的实例名。只有当存在足够的系统资源和系统的设计需要这么做。
net start MongoDB
net stop MongoDB
C:\mongodb\bin\mongod.exe --remove
命令行下运行 MongoDB 服务器 和 配置 MongoDB 服务 任选一个方式启动就可以。
[TOC]
有一天,我们写了关于如何在localStorage中保存图像和文件的文章,它是关于我们今天可用的实用主义。 然而,localStorage有一些性能影响 - 我们将在稍后的博客中讨论这个问题 - 并且未来期望的方法是使用IndexedDB。 在这里,我将向您介绍如何在IndexedDB中存储图像和文件,然后通过ObjectURL呈现它们。
本文是翻译过来的,原文在这里Storing images and files in IndexedDB
关于作者: Robert Nyman [Editor emeritus]
Technical Evangelist & Editor of Mozilla Hacks. Gives talks & blogs about HTML5, JavaScript & the Open Web. Robert is a strong believer in HTML5 and the Open Web and has been working since 1999 with Front End development for the web - in Sweden and in New York City. He regularly also blogs at http://robertnyman.com and loves to travel and meet people.
首先,我们来谈谈我们将创建一个IndexedDB数据库,将文件保存到其中然后将其读出并显示在页面中的步骤:
// IndexedDB
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.OIndexedDB || window.msIndexedDB,
IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.OIDBTransaction || window.msIDBTransaction,
dbVersion = 1;
/*
Note: The recommended way to do this is assigning it to window.indexedDB,
to avoid potential issues in the global scope when web browsers start
removing prefixes in their implementations.
You can assign it to a varible, like var indexedDB… but then you have
to make sure that the code is contained within a function.
*/
// Create/open database
var request = indexedDB.open("elephantFiles", dbVersion);
request.onsuccess = function (event) {
console.log("Success creating/accessing IndexedDB database");
db = request.result;
db.onerror = function (event) {
console.log("Error creating/accessing IndexedDB database");
};
// Interim solution for Google Chrome to create an objectStore. Will be deprecated
if (db.setVersion) {
if (db.version != dbVersion) {
var setVersion = db.setVersion(dbVersion);
setVersion.onsuccess = function () {
createObjectStore(db);
getImageFile();
};
}
else {
getImageFile();
}
}
else {
getImageFile();
}
}
// For future use. Currently only in latest Firefox versions
request.onupgradeneeded = function (event) {
createObjectStore(event.target.result);
};
使用它的预期方法是在创建数据库时触发onupgradeneeded事件或获取更高版本号。 目前仅在Firefox中支持此功能,但很快将在其他Web浏览器中支持。 如果Web浏览器不支持此事件,则可以使用已弃用的setVersion方法并连接到其onsuccess事件。
// Create an objectStore
console.log("Creating objectStore")
dataBase.createObjectStore("elephants");
在这里,您创建一个ObjectStore,您将存储数据 - 或者在我们的例子中,文件 - 并且一旦创建,您不需要重新创建它,只需更新其内容即可。
// Create XHR
var xhr = new XMLHttpRequest(),
blob;
xhr.open("GET", "elephant.png", true);
// Set the responseType to blob
xhr.responseType = "blob";
xhr.addEventListener("load", function () {
if (xhr.status === 200) {
console.log("Image retrieved");
// File as response
blob = xhr.response;
// Put the received blob into IndexedDB
putElephantInDb(blob);
}
}, false);
// Send XHR
xhr.send();
此代码直接将文件的内容作为blob获取。目前只支持Firefox。 收到整个文件后,将blob发送到函数以将其存储在数据库中。
// Open a transaction to the database
var transaction = db.transaction(["elephants"], IDBTransaction.READ_WRITE);
要开始向数据库写入内容,您需要使用objectStore名称和要执行的操作类型(在本例中为read和write)启动事务。
// Put the blob into the dabase
transaction.objectStore("elephants").put(blob, "image");
一旦事务到位,您将获得对所需objectStore的引用,然后将您的blob放入其中并为其提供密钥。
// Retrieve the file that was just stored
transaction.objectStore("elephants").get("image").onsuccess = function (event) {
var imgFile = event.target.result;
console.log("Got elephant!" + imgFile);
// Get window.URL object
var URL = window.URL || window.webkitURL;
// Create and revoke ObjectURL
var imgURL = URL.createObjectURL(imgFile);
// Set img src to ObjectURL
var imgElephant = document.getElementById("elephant");
imgElephant.setAttribute("src", imgURL);
// Revoking ObjectURL
URL.revokeObjectURL(imgURL);
};
使用相同的事务来获取刚刚存储的图像文件,然后创建一个objectURL并将其设置为页面中图像的src。
例如,这也可以是一个附加到脚本元素的JavaScript文件,然后它将解析JavaScript。
(function () {
// IndexedDB
var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.OIndexedDB || window.msIndexedDB,
IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.OIDBTransaction || window.msIDBTransaction,
dbVersion = 1.0;
// Create/open database
var request = indexedDB.open("elephantFiles", dbVersion),
db,
createObjectStore = function (dataBase) {
// Create an objectStore
console.log("Creating objectStore")
dataBase.createObjectStore("elephants");
},
getImageFile = function () {
// Create XHR
var xhr = new XMLHttpRequest(),
blob;
xhr.open("GET", "elephant.png", true);
// Set the responseType to blob
xhr.responseType = "blob";
xhr.addEventListener("load", function () {
if (xhr.status === 200) {
console.log("Image retrieved");
// Blob as response
blob = xhr.response;
console.log("Blob:" + blob);
// Put the received blob into IndexedDB
putElephantInDb(blob);
}
}, false);
// Send XHR
xhr.send();
},
putElephantInDb = function (blob) {
console.log("Putting elephants in IndexedDB");
// Open a transaction to the database
var transaction = db.transaction(["elephants"], IDBTransaction.READ_WRITE);
// Put the blob into the dabase
var put = transaction.objectStore("elephants").put(blob, "image");
// Retrieve the file that was just stored
transaction.objectStore("elephants").get("image").onsuccess = function (event) {
var imgFile = event.target.result;
console.log("Got elephant!" + imgFile);
// Get window.URL object
var URL = window.URL || window.webkitURL;
// Create and revoke ObjectURL
var imgURL = URL.createObjectURL(imgFile);
// Set img src to ObjectURL
var imgElephant = document.getElementById("elephant");
imgElephant.setAttribute("src", imgURL);
// Revoking ObjectURL
URL.revokeObjectURL(imgURL);
};
};
request.onerror = function (event) {
console.log("Error creating/accessing IndexedDB database");
};
request.onsuccess = function (event) {
console.log("Success creating/accessing IndexedDB database");
db = request.result;
db.onerror = function (event) {
console.log("Error creating/accessing IndexedDB database");
};
// Interim solution for Google Chrome to create an objectStore. Will be deprecated
if (db.setVersion) {
if (db.version != dbVersion) {
var setVersion = db.setVersion(dbVersion);
setVersion.onsuccess = function () {
createObjectStore(db);
getImageFile();
};
}
else {
getImageFile();
}
}
else {
getImageFile();
}
}
// For future use. Currently only in latest Firefox versions
request.onupgradeneeded = function (event) {
createObjectStore(event.target.result);
};
})();
最近在回顾一些基础知识,在目前枝繁叶茂的前端中把基础知识弄扎实。基础扎实了上层建筑才能牢固。这是一些笔记,虽然是一些简单的东西,但还是拿出来与大家分享一下。养成一种分享的习惯。
列表是一组有序的数据。每个列表中的数据项称为元素。在JavaScript中,列表中的元素可以是任意数据类型。
当列表中保存的元素不多时;不需要很长的序列中查找元素;不需要对齐进行排序。
如果数据结构非常复杂,列表的作用就没那么大了。例如人们经常使用的待办事项列表、购物清单、流行榜单等就很合适使用。
/**
* List
* @constructor
*/
function List() {
// 初始化数据
this.data = [];
// 添加
this.add = function (item){};
// 删除
this.remove = function (id){};
// 查找
this.find = function (id){};
// 清空
this.clear = function () {};
// 获取列表数据
this.getData = function (){};
}
var ins = new List();
栈是一种特殊的列表,栈内的元素只能通过一端访问,这一端称为栈顶。栈是后入先出的数据结构。
由于栈具有后入先出的特点,所以任何不在栈顶的元素都无法访问。为了得到栈顶的元素,必须先去掉上面的元素。
function Stack() {
this.dataStore = [];
this.top = 0; //栈顶
this.push = push; // 入栈
this.pop = pop; // 出栈并删除
this.peek = peek; // 出栈单不删除
this.clear = clear;
this.getLength = getLength;
}
function push(el) {
this.dataStore[this.top++] = el;
}
function pop() {
return this.dataStore[--this.top];
}
function peek() {
return this.dataStore[this.top-1];
}
function clear() {
this.top = 0;
}
function getLength() {
return this.top;
}
var ins = new Stack();
ins.push('a');
ins.push('b');
ins.push('c');
function mulBase(num, base) {
var s = new Stack();
do {
s.push(num % base);
num = Math.floor(num /= base);
} while (num > 0);
var converted = "";
while (s.getLength() > 0) {
converted += s.pop();
}
return converted;
}
console.log(mulBase(25,2));// 11001
回文:一个单词、短语或者数字,从前往后写都是一样的。例如 abba 倒过来还是abba
function isPalindrome(word) {
var stack = new Stack(),
i = 0,
l = word.length;
for (; i < l; i++) {
stack.push(word.charAt(i))
}
var rword = "";
while (stack.getLength() > 0) {
rword += stack.pop();
}
return rword === word;
}
console.log(isPalindrome("rar")) //true
console.log(isPalindrome("test"))//false
队列是一种列表,不同的是队列只能在队尾插入元素,在对首删除元素。队列用于存储按顺序排列的数据,先进先出。队列应用比较广泛,提交操作系统执行一些进程,打印任务池,日常排队买东西等等。
function Queue() {
this.dataStore = [];
this.enqueue = enqueue;
this.dequeue = dequeue;
this.front = front;
this.back = back;
this.toString = toString;
this.empty = empty;
}
function enqueue(element) {
this.dataStore.push(element)
}
function dequeue() {
this.dataStore.shift()
}
function front() {
return this.dataStore[0];
}
function back() {
return this.dataStore[this.dataStore.length - 1];
}
function toString() {
var str = '',
i = 0,
l = this.dataStore.length;
for ( ; i < l; i++) {
str += this.dataStore[i] + "\n";
}
return str;
}
function empty() {
return this.dataStore.length === 0;
}
// 实例化
var q = new Queue();
q..enqueue('a');
回顾了一下JavaScript严格模式,贴出来与大家一起温故而知新,一起巩固。
这个里面介绍比较详细。
ECMAScript 5的严格模式是JavaScript中的一种限制性更强的变种方式。严格模式不是一个子集:它在语义上与正常代码有着明显的差异。不支持严格模式的浏览器与支持严格模式的浏览器行为上也不一样, 所以不要在未经严格模式特性测试情况下使用严格模式。严格模式可以与非严格模式共存,所以脚本可以逐渐的选择性加入严格模式。
严格模式在语义上与正常的JavaScript有一些不同。 首先,严格模式会将JavaScript陷阱直接变成明显的错误。其次,严格模式修正了一些引擎难以优化的错误:同样的代码有些时候严格模式会比非严格模式下更快。 第三,严格模式禁用了一些有可能在未来版本中定义的语法。
如果你想让你的JavaScript代码在严格模式下运行,可以参考转换成严格模式。
有时,你会看到符合规范的、非严格模式被称为"懒散模式",这不是官方术语,但你应该注意到它.
严格模式可以应用到整个script标签或个别函数中。不要在封闭大括弧({} )内这样做;在这样的上下文中这么做是没有效果的。在eval 代码,Function 代码,事件处理属性,传入 setTimeout方法的字符串和包含整个脚本的块中开启严格模式会如预期一样工作。
为整个script标签开启严格模式, 需要在所有语句之前放一个特定语句
"use strict"; (或 'use strict';)
// 整个语句都开启严格模式的语法
"use strict";
var v ="Hi! I'm a strict mode script!";
这种语法存在陷阱,有一个大型网站已经被它坑倒了:不能盲目的合并冲突代码。试想合并一个严格模式的脚本和一个非严格模式的脚本:合并后的脚本代码看起来是严格模式。反之亦然:非严格合并严格看起来是非严格的。合并均为严格模式的脚本或均为非严格模式的都没问题,只有在合并严格模式与非严格模式有可能有问题。建议按一个个函数去开启严格模式(至少在学习的过渡期要这样做).
您也可以将整个脚本的内容用一个函数包括起来,然后在这个外部函数中使用严格模式。这样做就可以消除合并的问题,但是这就意味着您必须要在函数作用域外声明一个全局变量。
同样的,要给某个函数开启严格模式,得把 "use strict"; (或 'use strict'; )声明一字不漏地放在函数体所有语句之前。
functionstrict(){
// 函数级别严格模式语法
'use strict';
functionnested(){
return "And so am I!";
}
return "Hi! I'm a strict mode function! "+ nested();
}
function notStrict(){
return "I'm not strict.";
}
将问题直接转化为错误(如语法错误或运行时错误),简化了如何为给定名称的特定变量计算,简化了 eval 以及arguments,将写"安全“JavaScript的步骤变得更简单,以及改变了预测未来ECMAScript行为的方式。
在严格模式下, 先前被接受的拼写错误将会被认为是异常. JavaScript被设计为能使新人开发者更易于上手, 所以有时候会给本来错误操作赋予新的不报错误的语义(non-error semantics). 有时候这可以解决当前的问题, 但有时候却会给以后留下更大的问题. 严格模式则把这些失误当成错误, 以便可以发现并立即将其改正.
在普通的JavaScript里面给一个拼写错误的变量名赋值会使全局对象新增一个属性并继续“工作”(尽管后面可能出错:在现在的JavaScript中有可能)。严格模式中意外创建全局变量被抛出错误替代:
"
use strict";
//假如有一个全局变量叫做
mistypedVariablemistypedVaraible = 17;
// 因为变量名拼写错误
// 这一行代码就会抛出 ReferenceError
在正常模式下, 给 NaN 赋值不会产生任何作用; 开发者也不会受到任何错误反馈. 但在严格模式下, 给 NaN 赋值会抛出一个异常. 任何在正常模式下引起静默失败的赋值操作 (给不可写属性赋值, 给只读属性(getter-only)赋值赋值, 给不可扩展对象(non-extensible object)的新属性赋值) 都会抛出异常:
"use strict";
// 给不可写属性赋值
var obj1 = {};
Object.defineProperty(obj1, "x", { value: 42, writable: false });
obj1.x = 9; // 抛出TypeError错误
// 给只读属性赋值
var obj2 = { get x() { return 17; } };
obj2.x = 5; // 抛出TypeError错误
// 给不可扩展对象的新属性赋值
var fixed = {};
Object.preventExtensions(fixed);
fixed.newProp = "ohai"; // 抛出TypeError错误
"use strict";
delete Object.prototype; // 抛出TypeError错误
这个问题在ECMAScript6中已经不复存在(bug 1041128)。
"use strict";
var o = { p: 1, p: 2 }; // !!! 语法错误
function sum(a, a, c){ // !!! 语法错误
"use strict";
return a + b + c; // 代码运行到这里会出错
}
var a =0o10;// ES6: 八进制
有些新手开发者认为数字的前导零没有语法意义, 所以他们会用作对齐措施 — 但其实这会改变数字的意义! 八进制语法很少有用并且可能会错误使用, 所以严格模式下八进制语法会引起语法错误:
"use strict";
var sum = 015 + // !!! 语法错误
197 +
142;
(function() {
"use strict";
false.true = ""; //TypeError
(14).sailing = "home"; //TypeError
"with".you = "far away"; //TypeError
})();
严格模式简化了代码中变量名字映射到变量定义的方式. 很多编译器的优化是依赖存储变量X位置的能力:这对全面优化JavaScript代码至关重要. JavaScript有些情况会使得代码中名字到变量定义的基本映射只在运行时才产生. 严格模式移除了大多数这种情况的发生, 所以编译器可以更好的优化严格模式的代码.
"use strict";
var x = 17;
with (obj) // !!! 语法错误
{
// 如果没有开启严格模式,with中的这个x会指向with上面的那个x,还是obj.x?
// 如果不运行代码,我们无法知道,因此,这种代码让引擎无法进行优化,速度也就会变慢。
x;
}
一种取代 with 的简单方法是,将目标对象赋给一个短命名变量,然后访问这个变量上的相应属性.
var x = 17;
var evalX = eval("'use strict'; var x = 42; x");
console.assert(x === 17);
console.assert(evalX === 42);
相应的, 如果函数 eval 被在严格模式下的eval(...)以表达式的形式调用时, 其代码会被当做严格模式下的代码执行. 当然也可以在代码中显式开启严格模式, 但这样做并不是必须的.
function strict1(str){
"use strict";
return eval(str); // str中的代码在严格模式下运行
}
function strict2(f, str){
"use strict";
return f(str); // 没有直接调用eval(...): 当且仅当str中的代码开启了严格模式时
// 才会在严格模式下运行
}
function nonstrict(str){
return eval(str); // 当且仅当str中的代码开启了"use strict",str中的代码才会在严格模式下运行
}
strict1("'Strict mode code!'");
strict1("'use strict'; 'Strict mode code!'");
strict2(eval, "'Non-strict code.'");
strict2(eval, "'use strict'; 'Strict mode code!'");
nonstrict("'Non-strict code.'");
nonstrict("'use strict'; 'Strict mode code!'");
因此,在 eval 执行的严格模式代码下,变量的行为与严格模式下非 eval 执行的代码中的变量相同。
"use strict";
var x;
delete x; // !!! 语法错误
eval("var y; delete y;"); // !!! 语法错误
语法错误
严格模式让arguments和eval少了一些奇怪的行为。两者在通常的代码中都包含了很多奇怪的行为: eval会添加删除绑定,改变绑定好的值,还会通过用它索引过的属性给形参取别名的方式修改形参. 虽然在未来的ECMAScript版本解决这个问题之前,是不会有补丁来完全修复这个问题,但严格模式下将eval和arguments作为关键字对于此问题的解决是很有帮助的。
"use strict";
eval = 17;
arguments++;
++eval;
var obj = { set p(arguments) { } };
var eval;
try { } catch (arguments) { }
function x(eval) { }
function arguments() { }
var y = function eval() { };
var f = new Function("arguments", "'use strict'; return 17;");
function f(a){
"use strict";
a = 42;
return [a, arguments[0]];
}
var pair = f(17);
console.assert(pair[0] === 42);
console.assert(pair[1] === 17);
"use strict";
var f = function() { return arguments.callee; };
f(); // 抛出类型错误
严格模式下更容易写出“安全”的JavaScript。现在有些网站提供了方式给用户编写能够被网站其他用户执行的JavaScript代码。在浏览器环境下,JavaScript能够获取用户的隐私信息,因此这类Javascript必须在运行前部分被转换成需要申请访问禁用功能的权限。没有很多的执行时检查的情况,Javascript的灵活性让它无法有效率地做这件事。一些语言中的函数普遍出现,以至于执行时检查他们会引起严重的性能损耗。做一些在严格模式下发生的小改动,要求用户提交的JavaScript开启严格模式并且用特定的方式调用,就会大大减少在执行时进行检查的必要。
第一,在严格模式下通过this传递给一个函数的值不会被强制转换为一个对象。对一个普通的函数来说,this总会是一个对象:不管调用时this它本来就是一个对象;还是用布尔值,字符串或者数字调用函数时函数里面被封装成对象的this;还是使用undefined或者null调用函数式this代表的全局对象(使用call, apply或者bind方法来指定一个确定的this)。这种自动转化为对象的过程不仅是一种性能上的损耗,同时在浏览器中暴露出全局对象也会成为安全隐患,因为全局对象提供了访问那些所谓安全的JavaScript环境必须限制的功能的途径。所以对于一个开启严格模式的函数,指定的this不再被封装为对象,而且如果没有指定this的话它值是undefined:
"use strict";
function fun() { return this; }
console.assert(fun() === undefined);
console.assert(fun.call(2) === 2);
console.assert(fun.apply(null) === null);
console.assert(fun.call(undefined) === undefined);
console.assert(fun.bind(true)() === true);
第二,在严格模式中再也不能通过广泛实现的ECMAScript扩展“游走于”JavaScript的栈中。在普通模式下用这些扩展的话,当一个叫fun的函数正在被调用的时候,fun.caller是最后一个调用fun的函数,而且fun.arguments包含调用fun时用的形参。这两个扩展接口对于“安全”JavaScript而言都是有问题的,因为他们允许“安全的”代码访问"专有"函数和他们的(通常是没有经过保护的)形参。如果fun在严格模式下,那么fun.caller和fun.arguments都是不可删除的属性而且在存值、取值时都会报错:
function restricted()
{
"use strict";
restricted.caller; // 抛出类型错误
restricted.arguments; // 抛出类型错误
}
function privilegedInvoker()
{
return restricted();
}
privilegedInvoker();
第三,严格模式下的arguments不会再提供访问与调用这个函数相关的变量的途径。在一些旧时的ECMAScript实现中arguments.caller曾经是一个对象,里面存储的属性指向那个函数的变量。这是一个安全隐患,因为它通过函数抽象打破了本来被隐藏起来的保留值;它同时也是引起大量优化工作的原因。出于这些原因,现在的浏览器没有实现它。但是因为它这种历史遗留的功能,arguments.caller在严格模式下同样是一个不可被删除的属性,在赋值或者取值时会报错:
"use strict";
function fun(a, b)
{
"use strict";
var v = 12;
return arguments.caller; // 抛出类型错误
}
fun(1, 2); // 不会暴露v(或者a,或者b)
未来版本的ECMAScript很有可能会引入新语法,ECMAScript5中的严格模式就提早设置了一些限制来减轻之后版本改变产生的影响。如果提早使用了严格模式中的保护机制,那么做出改变就会变得更容易。
首先,在严格模式中一部分字符变成了保留的关键字。这些字符包括implements, interface, let, package, private, protected, public, static和yield。在严格模式下,你不能再用这些名字作为变量名或者形参名。
function package(protected){ // !!!
"use strict";
var implements; // !!!
interface: // !!!
while (true)
{
break interface; // !!!
}
function private() { } // !!!
}
function fun(static) { 'use strict'; } // !!!
两个针对Mozilla开发的警告:第一,如果你的JavaScript版本在1.7及以上(你的chrome代码或者你正确使用了)并且开启了严格模式的话,因为let和yield是最先引入的关键字,所以它们会起作用。但是网络上用或者加载的代码,let或者yield都不会作为关键字起作用;第二,尽管ES5无条件的保留了class, enum, export, extends, import和super关键字,在Firefox 5之前,Mozilla仅仅在严格模式中保留了它们。
其次,严格模式禁止了不在脚本或者函数层面上的函数声明。在浏览器的普通代码中,在“所有地方”的函数声明都是合法的。这并不在ES5规范中(甚至是ES3)!这是一种针对不同浏览器中不同语义的一种延伸。未来的ECMAScript版本很有希望制定一个新的,针对不在脚本或者函数层面进行函数声明的语法。在严格模式下禁止这样的函数声明对于将来ECMAScript版本的推出扫清了障碍:
"use strict";
if (true){
function f() { } // !!! 语法错误
f();
}
for (var i = 0; i < 5; i++){
function f2() { } // !!! 语法错误
f2();
}
function baz() { // 合法
function eit() { } // 同样合法
}
这种禁止放到严格模式中并不是很合适,因为这样的函数声明方式从ES5中延伸出来的。但这是ECMAScript委员会推荐的做法,浏览器就实现了这一点。
浏览器的严格模式Edit
主流浏览器现在实现了严格模式。但是不要盲目的依赖它,因为市场上仍然有大量的浏览器版本只部分支持严格模式或者根本就不支持(比如IE10之前的版本)。严格模式改变了语义。依赖这些改变可能会导致没有实现严格模式的浏览器中出现问题或者错误。谨慎地使用严格模式,通过检测相关代码的功能保证严格模式不出问题。最后,记得在支持或者不支持严格模式的浏览器中测试你的代码。如果你只在不支持严格模式的浏览器中测试,那么在支持的浏览器中就很有可能出问题,反之亦然。
[toc]
http://www.expressjs.com.cn/starter/installing.html
// 进入常用代码目录或者本地web服务器根目录
mkdir myapp
npm init
npm install express
npm install express --save
var express = require('express');
var app = express();
app.get('/', function (req, res) {
res.send('Hello World!');
});
var server = app.listen(3000, function () {
var host = server.address().address;
var port = server.address().port;
console.log('Example app listening at http://%s:%s', host, port);
});
node app.js
npm install express-generator -g
express myapp
cd myapp
npm install
// (MacOS 或 Linux 平台)
DEBUG=myapp npm start
// windows平台
set DEBUG=myapp & npm start
[toc]
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
return this.property;
};
function SubType() {
this.subproperty = false;
} //继承了SuperType
SubType.prototype = new SuperType(); //添加新方法
SubType.prototype.getSubValue = function () {
return this.subproperty;
};
//重写超类型中的方法
SubType.prototype.getSuperValue = function () {
return false;
};
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() { //继承了SuperType
SuperType.apply(this,this.arguments);
}
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function () {
alert(this.name);
};
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function () {
alert(this.age);
};
Object.create(proto)
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie"); //"Shelby,Court,Van,Rob,Barbie"alert(person.friends);
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function () {
alert(this.name);
};
function SubType(name, age) {
//第二次调用SuperType()
SuperType.call(this, name);
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
alert(this.age);
};
[toc]
npm install mysql
var mysql = require('mysql');
var conn = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'root',
database:'nodejs',
port: 3306
});
conn.connect();
conn.query('SELECT * from table', function(err, rows, fields) {
if (err) throw err;
console.log('The solution is: ', rows[0].solution);
});
conn.end();
var mysql = require('mysql');
var conn = mysql.createConnection({
host: 'localhost',
user: 'nodejs',
password: 'nodejs',
database: 'nodejs',
port: 3306
});
conn.connect();
var insertSQL = 'insert into t_user(name) values("conan"),("fens.me")';
var selectSQL = 'select * from t_user limit 10';
var deleteSQL = 'delete from t_user';
var updateSQL = 'update t_user set name="conan update" where name="conan"';
//delete
conn.query(deleteSQL, function (err0, res0) {
if (err0) console.log(err0);
console.log("DELETE Return ==> ");
console.log(res0);
//insert
conn.query(insertSQL, function (err1, res1) {
if (err1) console.log(err1);
console.log("INSERT Return ==> ");
console.log(res1);
//query
conn.query(selectSQL, function (err2, rows) {
if (err2) console.log(err2);
console.log("SELECT ==> ");
for (var i in rows) {
console.log(rows[i]);
}
//update
conn.query(updateSQL, function (err3, res3) {
if (err3) console.log(err3);
console.log("UPDATE Return ==> ");
console.log(res3);
//query
conn.query(selectSQL, function (err4, rows2) {
if (err4) console.log(err4);
console.log("SELECT ==> ");
for (var i in rows2) {
console.log(rows2[i]);
}
});
});
});
});
});
//conn.end();
var mysql = require('mysql');
var pool = mysql.createPool({
host: 'localhost',
user: 'nodejs',
password: 'nodejs',
database: 'nodejs',
port: 3306
});
var selectSQL = 'select * from t_user limit 10';
pool.getConnection(function (err, conn) {
if (err) console.log("POOL ==> " + err);
conn.query(selectSQL,function(err,rows){
if (err) console.log(err);
console.log("SELECT ==> ");
for (var i in rows) {
console.log(rows[i]);
}
conn.release();
});
});
var mysql = require('mysql');
var mysqlConfig = require('../conf/mysql');
// 使用DBConfig.js的配置信息创建一个MySQL连接池
var pool = mysql.createPool( mysqlConfig );
var userSql = {
insert:'INSERT INTO node_user(name,age) VALUES(?,?)',
queryAll:'SELECT * FROM node_user',
getUserById:'SELECT * FROM node_user WHERE id = ? '
};
var main = {
add: function (request,callback) {
// 从连接池获取连接
pool.getConnection(function(err, connection) {
var d = {};
if ( err ) {
d.connectError = err;
return callback(d);
}
// 获取前台页面传过来的参数
var param = request.query || request.params;
// 建立连接 增加一个用户信息
connection.query(userSql.insert, [param.name,param.age], function(err, result) {
d.response = { err: err, success: result };
callback(d);
// 释放连接
connection.release();
});
});
},
list: function (request,callback) {
// 从连接池获取连接
pool.getConnection(function(err, connection) {
var d = {};
if ( err ) {
d.connectError = err;
return callback(d);
}
// 建立连接 增加一个用户信息
connection.query(userSql.queryAll, function(err, result) {
d.response = { err: err, success: result };
callback(d);
// 释放连接
connection.release();
});
});
}
};
module.exports = main;
然后在路由routes/user.js里面引进来使用,如下。
var express = require('express');
var router = express.Router();
var userModel = require('../model/userModel');
// 添加用户
router.post('/addUser', function(req, res, next){
userModel.add(req,function (data) {
if (data.connectError) {
return res.json('connect database error')
}
if (data.response) {
res.json(data.query.success);
}
});
});
router.get('/list', function(req, res, next){
userModel.list(req,function (data) {
if (data.connectError) {
return res.json({code:'101',desc: 'db connect error!'})
}
if (data.response) {
return res.json({code:'200',desc: 'success',data:data.response.success})
}
});
});
module.exports = router;
[toc]
vagrant init
vagrant up
vagrant halt
vagrant ssh
cd ~/Homestead && vagrant provision && vagrant reload
[TOC]
Homestead 是 Laravel 官方推荐的开发环境。在本书里,我们会强制读者使用 Homestead,原因主要有以下:
Carbon 是 PHP 知名的 DateTime 操作扩展,Laravel 将其默认集成到了框架中。diffForHumans 是 Carbon 对象提供的方法
参考Carbon文档
excerpt 字段存储的是话题的摘录,将作为文章页面的 description 元标签使用,有利于 SEO 搜索引擎优化。摘录由文章内容中自动生成,生成的时机是在话题数据存入数据库之前。我们将使用 Eloquent 的 观察器 来实现此功能。
Eloquent 模型会触发许多事件(Event),我们可以对模型的生命周期内多个时间点进行监控: creating, created, updating, updated, saving, saved, deleting, deleted, restoring, restored。事件让你每当有特定的模型类在数据库保存或更新时,执行代码。当一个新模型被初次保存将会触发 creating 以及 created 事件。如果一个模型已经存在于数据库且调用了 save 方法,将会触发 updating 和 updated 事件。在这两种情况下都会触发 saving 和 saved 事件。
Eloquent 观察器允许我们对给定模型中进行事件监控,观察者类里的方法名对应 Eloquent 想监听的事件。每种方法接收 model 作为其唯一的参数。代码生成器已经为我们生成了一个观察器文件,并在 AppServiceProvider 中注册。接下来我们要定制此观察器,在 Topic 模型保存时触发的 saving 事件中,对 excerpt 字段进行赋值
参考文档Laravel 的资源任务编译器 Laravel Mix
HTMLPurifier for Laravel 是对 HTMLPurifier 针对 Laravel 框架的一个封装。本章节中,我们将使用此扩展包来对用户内容进行过滤。
composer require "mews/purifier:~2.0"
HTMLPurifier 本身就是一个独立的项目,运用『白名单机制』对 HTML 文本信息进行 XSS 过滤。
『白名单机制』指的是使用配置信息来定义『HTML 标签』、『标签属性』和『CSS 属性』数组,在执行 clean() 方法时,只允许配置信息『白名单』里出现的元素通过,其他都进行过滤。
很少会有人会特别注意CSS的注释,要么就快捷键注释,要么就没有注释,只有少部分的开发人员会特别留意CSS注释。看了BAT系的css注释,有规范,但无风格。
国外的开发人员对注释就有着比较好的思考,值得我们国内的开发者去学习。
从CSS注释当中可以看出开发人员的编码风格与习惯,进而判断出个人或者团队行事风格。对于有完美主义的开发者,对于自己的代码注释不重视,那是相当难受的。
主要内容:基本注释、变量注释(可选)、块级注释、群组注释、具体样式注释、继承注释、mixin注释(可选)、函数注释(可选)
其他注释待补充。如果未使用sass,less的可以忽略可选。
花费时间:1分钟即可阅读完毕。
下面的注释参考了Bootstrap源码的风格、normalize等的注释,给大家参考。
/* 单行注释 */
/**
* 多行注释
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in
* IE on Windows Phone and in iOS.
*/
//
// Variables
// --------------------------------------------------
/* 块级注释
========================================================================== */
//== 颜色
//
//## 用途范围
//** Background color for `<body>`.
$body-bg: #fff !default;
/**
* 继承注释
* Extend `.foo` in theme.css
*/
bootstrap风格
// CSS image replacement
//
// Heads up! v3 launched with only `.hide-text()`, but per our pattern for
// mixins being reused as classes with the same name, this doesn't hold up. As
// of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`.
//
// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757
// Deprecated as of v3.0.1 (has been removed in v4)
@mixin hide-text() {
font: 0/0 a;
color: transparent;
text-shadow: none;
background-color: transparent;
border: 0;
}
js系
/**
* mixin name and use
* @param
* @return
*/
@mixin hide-text() {
font: 0/0 a;
color: transparent;
text-shadow: none;
background-color: transparent;
border: 0;
}
组件一个团队,团队每个人都各自一套编程习惯。
任何开发者都不会在乎某个文件的作者是谁,没有必要花费时间和精力再去理解代码逻辑,并重新排版,这样节约时间。
当代码风格不同时,就很容易发现风格不一致的代码
缩进层级
使用四个空格,设置tab键 为四个空格
语句结尾
要么独占一行,要么以分号结尾。
// 合格代码
var team = "omwteam";
function sayTeam () {
return {
name: "freddy",
data: []
}
}
// 不合格代码
var team = "omwteam"
function sayTeam () {
return
{
name: "freddy",
data: []
}
}
// 这段代码会被编译器解析成如下这段代码
// 原意结果是返回一个对象,实际上却返回 undefined;
var team = "omwteam";
function sayTeam () {
return ;
{
name: "freddy",
data: []
}
}
行的长度
单行长度不应超过80行,超过80行应强制换行。
适当的地方换行
当一行超过80个时,需要手动换行,换行部分应使用两个缩进;
// 正确做法
callback(this,document,'test',[],'xxx','dsds',windows,
'ddsds');
// 不正确 换行部分只有一个缩进
callback(this,document,'test',[],'xxx','dsds',windows,
'ddsds');
// 不正确 换行部分带上 ","运算符前面
callback(this,document,'test',[],'xxx','dsds',windows
,'ddsds');
在合适的地方加上空行,以增加代码的可读性
在方法之间。
在方法中的局部变量和第一条语句
在多行或者单行注释之前
在方法内的逻辑片段之间插入空行,提高可读性
// 合理的写法
var list = [];
if (list && list.length) {
for (i = 0, l = list.length; i < l; i++) {
item = list[i];
type = object[item]
if (Object.hasOwnProperty(item)) {
if (type && type === 'object') {
return true;
} else {
return false;
}
}
}
}
// 不合理的
var list = [];
if (list && list.length) {
for (i = 0, l = list.length; i < l; i++) {
item = list[i];
type = object[item]
if ( Object.hasOwnProperty(item) ) {
if (type && type === 'object') {
return true;
} else {
return false;
}
}
}
}
变量与函数命名
一般采用驼峰法,语义化准则以增强代码的可读性
var anotherVarible;
var thisIsMyName;
// 好的写法
var count = '';
var myName = '';
// 不好的写法 变量写起来像函数
var getCount = '';
var isFound = '';
// 好的写法
function getName () {
return myName;
}
// 不好的写法: 函数看起来像变量
function theName () {
return count;
}
日常前端开发中我们很少用到位运算,容易让人遗忘,让我们一起回顾下一下js中的位运算。
下面主要回顾一下一些常用的位运算的巧用。
var number = 3;
var result = number.toString(2);
var result2 = 14..toString(2); // "1110"
var data = 2.2352524535;
var result = data | 0; // 2
var re2 = ~~data; // 2
var color = {r: 186, g: 218, b: 85};
// RGB to HEX
var rgb2hex = function(r, g, b) {
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).substr(1);
}
rgb2hex(color.r, color.g, color.b);//"#bada55"
// variables
var a = 9285;
var b = 3569;
// 取大
var max = a ^ ((a ^ b) & -(a < b));//9285;
// 取小
var min = b ^ ((a ^ b) & -(a < b);//3569
var a = 10;
var b = 99;
a = (b^=a^=b)^a;
console.log(a) // 99
console.log(b) // 10
function isPos(n) {
return (n === (n >>> 0)) ? true : false;
}
isPos(-1); // false
isPos(1); // true
方法使用指定函数检测数组中的所有元素:
如果数组中检测到有一个元素不满足,则整个表达式返回 false,且剩余的元素不会再进行检测。
如果所有元素都满足条件,则返回 true。
var numberArray= [2,3,4],
stringArray = [1,3333,"c",2];
//
var a = numberArray.every(function (p) {
return typeof p === "number"
});
var b = stringArray.every(function (p) {
return typeof p === "number"
});
console.log(a); // 返回 true
console.log(b);// 返回 false
var stringArray = [1,3333,"c",2];
var a = stringArray.every(function (p) {
return typeof p === "string"
});
console.log(a) // 返回["c"]
var numberArray= [2,3,4];
numberArray.forEach(function (value) {
console.log(value) //依次 2,3,4
});
arr.lastIndexOf(searchElement[, fromIndex = arr.length - 1])
var arr = [1,"a",2,"b","a"];
console.log(arr.lastIndexOf("a") //第一次出现的位置所以是 1 第二次即最后一次是 4 所以结果是4
const new_array = arr.map(callback()[, thisArg])
var numbers = [1, 4, 9];
var roots = numbers.map(Math.sqrt);
/* roots的值为[1, 2, 3], numbers的值仍为[1, 4, 9] */
var a = [1,2,3];
var newArr = a.map(function(v){
return v*2
})
console.log(newArr) // [2,4,6]
arr.reduce(callback,[initialValue])
var sum = [0, 1, 2, 3].reduce(function(acc, val) {
return acc + val;
}, 0);
console.log(sum);
// 6
arr.some(callback[, thisArg])
var re = [2, 5, 8, 1, 4].some(function(e){
return (e >= 10);
})
console.log(re); //false
var a = [2, 9, 9];
a.indexOf(2); // 0
a.indexOf(7); // -1
if (a.indexOf(7) === -1) {
// element doesn't exist in array
}
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function(searchElement, fromIndex) {
var k;
// 1. Let O be the result of calling ToObject passing
// the this value as the argument.
if (this == null) {
throw new TypeError('"this" is null or not defined');
}
var O = Object(this);
// 2. Let lenValue be the result of calling the Get
// internal method of O with the argument "length".
// 3. Let len be ToUint32(lenValue).
var len = O.length >>> 0;
// 4. If len is 0, return -1.
if (len === 0) {
return -1;
}
// 5. If argument fromIndex was passed let n be
// ToInteger(fromIndex); else let n be 0.
var n = +fromIndex || 0;
if (Math.abs(n) === Infinity) {
n = 0;
}
// 6. If n >= len, return -1.
if (n >= len) {
return -1;
}
// 7. If n >= 0, then Let k be n.
// 8. Else, n<0, Let k be len - abs(n).
// If k is less than 0, then let k be 0.
k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
// 9. Repeat, while k < len
while (k < len) {
// a. Let Pk be ToString(k).
// This is implicit for LHS operands of the in operator
// b. Let kPresent be the result of calling the
// HasProperty internal method of O with argument Pk.
// This step can be combined with c
// c. If kPresent is true, then
// i. Let elementK be the result of calling the Get
// internal method of O with the argument ToString(k).
// ii. Let same be the result of applying the
// Strict Equality Comparison Algorithm to
// searchElement and elementK.
// iii. If same is true, return k.
if (k in O && O[k] === searchElement) {
return k;
}
k++;
}
return -1;
};
}
[TOC]
Hybrid App,俗称混合应用,即混合了 Native技术 与 Web技术 进行开发的移动应用。现在比较流行的混合方案主要有三种,主要是在UI渲染机制上的不同:
1、基于 WebView UI 的基础方案,市面上大部分主流 App 都有采用,例如微信JS-SDK,通过 JSBridge 完成 H5 与 Native 的双向通讯,从而赋予H5一定程度的原生能力。
2、基于 Native UI 的方案,例如 React-Native、Weex。在赋予 H5 原生API能力的基础上,进一步通过 JSBridge 将js解析成的虚拟节点树(Virtual DOM)传递到 Native 并使用原生渲染。
3、近期比较流行的小程序方案,通过更加定制化的 JSBridge,并使用双 WebView 双线程的模式隔离了JS逻辑与UI渲染,形成了特殊的开发模式,加强了 H5 与 Native 混合程度,提高了页面性能及开发体验。
因此,JSBridge 也是整个混合应用最关键的部分,例如我们在设置微信分享时用到的 JS-SDK,wx对象 便是我们最常见的 JSBridge:
Hybrid App的本质,其实是在原生的 App 中,使用 WebView 作为容器直接承载 Web页面。因此,最核心的点就是 Native端 与 H5端 之间的双向通讯层,其实这里也可以理解为我们需要一套跨语言通讯方案,来完成 Native(Java/Objective-c/...) 与 JavaScript 的通讯。这个方案就是我们所说的 JSBridge,而实现的关键,便是作为容器的 WebView,一切的原理都是基于 WebView 的机制。
基于 WebView 的机制和开放的 API, 实现这个功能有三种常见的方案:
1)、API注入,原理其实就是 Native 获取 JavaScript环境上下文,并直接在上面挂载对象或者方法,使 js 可以直接调用,Android 与 IOS 分别拥有对应的挂载方式。
2)、WebView 中的 prompt/console/alert 拦截,通常使用 prompt,因为这个方法在前端中使用频率低,比较不会出现冲突;
3)、WebView URL Scheme 跳转拦截;
第二三种机制的原理是类似的,都是通过对 WebView 信息冒泡传递的拦截,从而达到通讯的,接下来我们主要从 原理-定制协议-拦截协议-参数传递-回调机制 5个方面详细阐述下第三种方案 -- URL拦截方案。
在 WebView 中发出的网络请求,客户端都能进行监听和捕获
我们需要制定一套URL Scheme规则,通常我们的请求会带有对应的协议开头,例如常见的 https://xxx.com 或者 file://1.jpg,代表着不同的含义。我们这里可以将协议类型的请求定制为:
xxcommand://xxxx?param1=1¶m2=2
xxcommand://getProxy?h=1
而定义 xxapp:// 为每个App单独的业务协议。
xxapp://openCamera?h=2
不同的协议头代表着不同的含义,这样便能清楚知道每个协议的适用范围。
(2)、 这里不要使用 location.href 发送,因为其自身机制有个问题是同时并发多次请求会被合并成为一次,导致协议被忽略,而并发协议其实是非常常见的功能。我们会使用创建 iframe 发送请求的方式。
(3)、 通常考虑到安全性,需要在客户端中设置域名白名单或者限制,避免公司内部业务协议被第三方直接调用。
客户端可以通过 API 对 WebView 发出的请求进行拦截:
当解析到请求 URL 头为制定的协议时,便不发起对应的资源请求,而是解析参数,并进行相关功能或者方法的调用,完成协议功能的映射。
由于协议的本质其实是发送请求,这属于一个异步的过程,因此我们便需要处理对应的回调机制。这里我们采用的方式是JS的事件系统,这里我们会用到 window.addEventListener 和 window.dispatchEvent这两个基础API;
发送协议时,通过协议的唯一标识注册自定义事件,并将回调绑定到对应的事件上。
客户端完成对应的功能后,调用 Bridge 的dispatch API,直接携带 data 触发该协议的自定义事件。
原生的事件机制是我们非常熟悉的,例如我们常用的
window.addEventListener('DOMContentLoaded', () => {});
通过事件的机制,会让开发更符合我们前端的习惯,例如当你需要监听客户端的通知时,同样只需要在通过 addEventListener 进行监听即可。
Tips: 这里有一点需要注意的是,应该避免事件的多次重复绑定,因此当唯一标识重置时,需要removeEventListener对应的事件。
由于 WebView 对 URL 会有长度的限制,因此常规的通过 search参数 进行传递的方式便具有一个问题,既 当需要传递的参数过长时,可能会导致被截断,例如传递base64或者传递大量数据时。‘’
因此我们需要制定新的参数传递规则,我们使用的是函数调用的方式。这里的原理主要是基于:
Native 可以直接调用 JS 方法并直接获取函数的返回值。
我们只需要对每条协议标记一个唯一标识,并把参数存入参数池中,到时客户端再通过该唯一标识从参数池中获取对应的参数即可。
由于 Native 可以算作 H5 的宿主,因此拥有更大的权限,上面也提到了 Native 可以通过 WebView API直接执行 Js 代码。这样的权限也就让这个方向的通讯变得十分的便捷。
// Swift
webview.stringByEvaluatingJavaScriptFromString("alert('NativeCall')")
// 调用js中的JSBridge.trigger方法
// 该方法的弊端是无法获取函数返回值;
webView.loadUrl("javascript:JSBridge.trigger('NativeCall')")
Tips: 当系统低于4.4时,evaluateJavascript 是无法使用的,因此单纯的使用 loadUrl 无法获取 JS 返回值,这时我们需要使用前面提到的 prompt 的方法进行兼容,让 H5端 通过 prompt 进行数据的发送,客户端进行拦截并获取数据。
// 4.4+后使用该方法便可调用并获取函数返回值;
mWebView.evaluateJavascript("javascript:JSBridge.trigger('NativeCall')", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
//此处为 js 返回的结果
}
});
基于上面的原理,我们已经明白 JSBridge 最基础的原理,并且能实现 Native <=> H5 的双向通讯机制了。
接下来,我们来理下代码上需要的资源。实现这套方案,从上图可以看出,其实可以分为两个部分:
我们这里的做法是,将这两部分一起封装成一个 Native SDK,由客户端统一引入。客户端在初始化一个 WebView 打开页面时,如果页面地址在白名单中,会直接在 HTML 的头部注入对应的 bridge.js。这样的做法有以下的好处:
App的接入十分方便,只需要按文档接入最新版本的SDK,即可直接运行整套Hybrid方案,便于在多个App中快速的落地;
H5端无需关注,这样有利于将 bridge 开放给第三方页面使用。
这里有一点需要注意的是,协议的调用,一定是需要确保执行在bridge.js 成功注入后。由于客户端的注入行为属于一个附加的异步行为,从H5方很难去捕捉准确的完成时机,因此这里需要通过客户端监听页面完成后,基于上面的回调机制通知 H5端,页面中即可通过window.addEventListener('bridgeReady', e => {})进行初始化。
将 H5 接入 App 中通常有两种方式:
但相对的,这种方式也有对应的缺点:
但相对的,这种方式也有对应的缺点:
通常,这种方式更适用在一些比较轻量级的页面上,例如一些帮助页、提示页、使用攻略等页面。这些页面的特点是功能性不强,不太需要复杂的功能协议,且不需要离线使用。在一些第三方页面接入上,也会使用这种方式,例如我们的页面调用微信JS-SDK。
但同时,它的劣势也十分明显:
这两种接入方式均有自己的优缺点,应该根据不同场景进行选择。
总结整理了网上的资料
[TOC]
随着业务量的增加,用到H5页面的越来越多;H5页面的性能怎么样?用户访问的体验怎么样?H5的出错率以及兼容性怎么样?目前来说,处于一个模糊状态,只能是通过公司测试的设备在不同的网络条件下体验一次,心里才有一个初步的印象。
没有一个数据系统支撑,仅仅凭内部人员测试和体验,这是远远不够的,我们之前在一些页面上加了百度统计做了一个初步的统计,就显示每天有数十万的访问量,而这是在一些个别页面测试得到的数据,不够精确。经过我们团队讨论决定要把H5性能监控系统做起来。
通过收集页面在用户设备的性能数据分析,可以方便知道页面在用户设备运行的性能情况,从而为开发人员提供选择合适的技术提供数据支撑。
通过错误监控与收集,降低用户遇到问题的概率,分析问题原因,减少问题重复出现概率,能够在用户碰到问题前发现并解决。
统计数据分析,根据数据分析做出相应的策略,从而选择合适的技术选型,从而提升页面性能和开发可维护性可扩展性
用户行为分析,给产品运营人员提供运营数据支持
jsHeapSizeLimit: 内存大小限制
totalJSHeapSize: 可使用的内存
usedJSHeapSize: JS对象(包括V8引擎内部对象)占用的内存,不能大于totalJSHeapSize,如果大于,有可能出现了内存泄漏
参考以下资料
通过APP大数据埋点接口数埋点和上报
分两个维度统计
[TOC]
JWT是我们用来实现基于token认证的一种实现方式,是目前实现web开发中跨域认证的一种常用方式。当然,你也可以基于这种方式实现自己签发和认证处理方式,这里首先介绍一下使用方式。
npm install jsonwebtoken
const jwt = require('jsonwebtoken');
const secret = '秘钥abcABC!@#';
//jwt生成token
const token = jwt.sign({
name: web
}, secret, {
expiresIn: 60*60*2 //2个小时到期时间
});
console.log(token);
//解密token
jwt.verify(token, secret, function (err, decoded) {
if (!err){
console.log(decoded.name); //如果超时则验证出错
}
})
客户端登录时请求服务器,签发一个token, json web token官方规定由三部分组成
Header(头部).Payload(负载).Signature(签名),并且以 " . "分隔,
{
"alg": "HS256",
"typ": "JWT"
}
这些是一组预定义声明,不是强制性的,但建议使用,以提供一组有用的,可互操作的声明。其中一些是:iss(签发人),exp(到期时间),sub(主题),aud(观众)等。
{
iss: "",
exp: "",
sub: "",
aud: ""
}
也可以自己定义一些字段
{
iss: "",
exp: "",
sub: "",
aud: "" ,
jid: ""//序号
effect: "",//生效时间
}
然后使用Base64Url对Payload进行编码,形成令牌token的第二部分
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
前面提到,Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同。
JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_ 。这就是 Base64URL 算法。
在HTTP中,HTTP基本认证是一种允许Web浏览器或者其他客户端在请求时提供用户名和密码形式的身份凭证的一种登录验证方式。
用户登录以后服务器会用session来保存用户的登录状态信息,用户下次请求时通过session来认证。
OAuth 是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表等),而无需将用户名和密码提供给第三方应用。
客户端登录时,服务器会签发一个token给客户端,这个toekn有过期时间和使用范围,客户端保存在本地,客户端每次请求需要认证的接口,都要带上token,服务器判断是否有权限访问。
这种方式最大的缺点就是,在token未过期时,其他人拿到token也能使用,所以使用这种方式最好考虑清楚应对方式,
W3C的WebAuthn API是一种可融入浏览器和相关Web平台基础架构的标准WebAPI,可为每个站点提供强大、唯一且基于公钥的凭证,消除了从某一站点窃取密码后被用于其他站点的风险。 使用FIDO身份验证器加载到设备上的在浏览器中运行的Web应用程序,可以通过密码操作代替密码交换,或除了密码交换之外,还可为服务提供者和用户带来诸多益处:
更简单的身份验证:用户只需使用一种手势登录PC、笔记本电脑和/或移动设备中的内部或内置认证器(如指纹或面部生物识别技术)使用CTAP进行设备到设备认证的外部认证器(如安全密钥和移动设备),一个由FIDO联盟开发的用于补充WebAuthn的外部认证器协议
更强的身份验证:FIDO身份验证比单纯依赖密码和相关身份验证方式要强大得多,并具有以下优点用户证书和生物识别模板永远不会离开用户的设备,也不会存储在服务器上帐户可以免受网络钓鱼,中间人攻击和使用被盗密码的反复攻击
开发人员可以开始在FIDO新的开发者资源页面上创建利用FIDO身份验证的应用程序和服务。
参考文档
[TOC]
history:接口允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录。
pushState:pushState用来新增历史记录。
pushState() 带有三个参数:一个状态对象,一个标题(现在被忽略了),以及一个可选的URL地址。下面将对这三个参数进行细致的检查:
state object — 状态对象是一个由 pushState()方法创建的、与历史纪录相关的JS对象。当用户定向到一个新的状态时,会触发popstate事件。事件的state属性包含了历史纪录的state对象。(译者注:总而言之,它存储JSON字符串,可以用在popstate事件中。)state 对象可以是任何可以序列化的东西。由于 火狐 会将这些对象存储在用户的磁盘上,所以用户在重启浏览器之后这些state对象会恢复,我们施加一个最大640k 的字符串在state对象的序列化表示上。如果你像pushState() 方法传递了一个序列化表示大于640k 的state对象,这个方法将扔出一个异常。如果你需要更多的空间,推荐使用sessionStorage或者localStorage。
title — 火狐浏览器现在已经忽略此参数,将来也许可能被使用。考虑到将来有可能的改变,传递一个空字符串是安全的做法。当然,你可以传递一个短标题给你要转变成的状态。(译者注:现在大多数浏览器不支持或者忽略这个参数,最好用null代替)
URL — 这个参数提供了新历史纪录的地址。请注意,浏览器在调用pushState()方法后不会去加载这个URL,但有可能在之后会这样做,比如用户重启浏览器之后。新的URL不一定要是绝对地址,如果它是相对的,它一定是相对于当前的URL。新URL必须和当前URL在同一个源下;否则,pushState() 将丢出异常。这个参数可选,如果它没有被特别标注,会被设置为文档的当前URL。
history.pushState({'history':'history'},'history','#hisory')
replaceState:pushState用来替换当前的增历史记录。
history.repalceState({'history':'repalceState'},'repalceState','#hisory')
带有三个参数:一个状态对象,一个标题(现在被忽略了),以及一个可选的URL地址。下面将对这三个参数进行细致的检查:
state object :— 状态对象是一个由 pushState()方法创建的、与历史纪录相关的JS对象。当用户定向到一个新的状态时,会触发popstate事件。事件的state属性包含了历史纪录的state对象。(译者注:总而言之,它存储JSON字符串,可以用在popstate事件中。)state 对象可以是任何可以序列化的东西。由于 火狐 会将这些对象存储在用户的磁盘上,所以用户在重启浏览器之后这些state对象会恢复,我们施加一个最大640k 的字符串在state对象的序列化表示上。如果你像pushState() 方法传递了一个序列化表示大于640k 的state对象,这个方法将扔出一个异常。如果你需要更多的空间,推荐使用sessionStorage或者localStorage。
title :— 火狐浏览器现在已经忽略此参数,将来也许可能被使用。考虑到将来有可能的改变,传递一个空字符串是安全的做法。当然,你可以传递一个短标题给你要转变成的状态。(译者注:现在大多数浏览器不支持或者忽略这个参数,最好用null代替)
URL :— 这个参数提供了新历史纪录的地址。请注意,浏览器在调用pushState()方法后不会去加载这个URL,但有可能在之后会这样做,比如用户重启浏览器之后。新的URL不一定要是绝对地址,如果它是相对的,它一定是相对于当前的URL。新URL必须和当前URL在同一个源下;否则,pushState() 将丢出异常。这个参数可选,如果它没有被特别标注,会被设置为文档的当前URL。
触发历史记录改变的事件,比如用户前进、后退等操作 会触发popstate事件。进而获取到通过pushState和replaceState传递的值。
// 监听state事件
window.addEventListener('popstate',function (event) {
console.log(event.state);
});
[toc]
本文图片、部分翻译来自原文 Even more about how Flexbox works — explained in big, colorful, animated gifs
上一篇 53.图解 Flexbox 介绍了 Flexbox 的基本属性,这次来深入理解下。
flex-basis
上一篇主要看了容器元素的属性,这次来看下子元素
flex-basis
控制元素的默认尺寸,然后再由其他 Flexbox 属性控制
下图表示可以和 width
属性互换
那 flex-basis
和 width
又有什么区别呢,因为 Flexbox 的容器有两个轴 如下:
flex-basis
是通过主轴 (main axis) 来影响元素尺寸的
我们保持 flex-basis
不变,然后切换主轴方向 如下:
注意,我们必须从手动设置高度切换成手动设置高度。因此通过 flex-direction
的不同,flex-basis
会交替的影响 width
或 height
flex-grow
首先设置每个元素 width
为 120px ,如下:
flex-grow
的默认值为 0,意味着每个元素不能在容器内增长 (grow) 他们的宽度
如果设置 flex-grow: 1
呢,如下图:
每个元素在容器内均匀的充满了整个空间,flex-grow
的值覆盖了 width
那 flex-grow: 1
到底意味着什么呢,如果我们改为 flex-grow: 999
完全一样,没有变化。那是因为 flex-grow
不是个绝对值,而是一个相对值。
起作用的不是一个元素的 flex-grow
的值,而是他相对于其他元素值。比如,我们改变三个元素的flex-grow:
其他元素不变
这个其实很容易理解,就是按照百分比来占据空间:
当每个元素都为 flex-grow: 1
,一共6块, 每个元素占据空间的 1/6 宽度
当第三个元素 flex-grow: 2
时,整个父级空间切成7份 1+1+2+1+1+1。 第三个元素占据 2/7 ,其他元素为 1/7
同理,第三个元素 flex-grow: 3
第三个元素占据 3/8 ,其他元素为 1/8
flex-grow
的值是按比例来计算的,比如设置每个元素 flex-grow: 4
,第三个元素 flex-grow: 12
, 其实和 1:3 一样的
flex-grow
和 flex-basis
一样是作用于主轴的。除非改变 flex-direction: column
否则元素只是改变宽度
flex-shrink
flex-shrink
的作用和 flex-grow
恰恰相反,来确定一个元素的缩小比例。(顾名思义,shrink 和 grow 就是一个缩小一个增长)
flex-shrink
的作用是指定哪些元素缩小哪些不缩小。默认情况每个元素 flex-shrink: 1
,意味着他们会随着父级容器的缩小而缩小。
下图中每个元素都是 flex-grow: 1
他们会充满容器,同时每个元素 flex-shrink: 1
, 他们是可以收缩的
如果把第三个元素设为 flex-shrink: 0
禁止缩小,所以他可以增长宽度适应容器,但是不会下降到 120px 以下
每个元素默认 flex-shrink: 1
flex-shrink
也是相对值,按比例来计算的,若果一个元素 flex-shrink
的值为6,其他的为2,当容器空间压缩时,该元素会以 3x 的速度缩小。这里的 3x 指的是将缩小 3倍,而不是缩小为 1/3 的宽度。
后面会有 flex-shrink
和 flex-grow
更详细的说明,先来个最后一个属性
flex
flex
属性是把 flex-grow
flex-shrink
flex-basis
综合到了一起
默认值为 0 (flex-grow
) 1 (flex-shrink
) auto (flex-basis
)
最后一个例子,我们设置2个元素
.square#one {
flex: 2 1 300px;
}
.square#two {
flex: 1 2 300px;
}
flex-basis
的值相同,他们都占据相同的空间 300px (容器算上 margin padding 是 600px)
但是 元素1 将会2x速度增长宽度, 元素2 将会2x速度缩小宽度 如下
##flex-grow
和 flex-shrink
是如何工作的
当 元素1 (.square#one) 增长时他不会增长到 元素2 (.square#two) 的两倍;同样缩小时 元素2 (.square#two) 也不会缩小为 元素1 (.square#one) 的一半
因此这不是他们的大小比例为 1:2 或者 2:1 ,而是他们的收缩和增长速度
容器初始宽度为 640px ,除去两边的各 20px 的 padding 后,两个元素各有 flex-basis
为 300px
当容器设置为 430px 时,我们失去了 210px 的空间 元素1 flex-shrink: 1
减少了 70px ; 元素2 flex-shrink: 2
少了 140px
当容器设置为 340px 时,我们失去了 300px 的空间 元素1 减少了 100px ; 元素2 少了 200px
他们减少的比例正好也是 2:1
flex-grow
也是同样道理。当容器设置为 940px 时,增长的 300px 空间分别来自 元素1 的 200px 和 元素2 的 100px
直接用 flex
即可表达:
上图很容易看出元素的宽度是如何根据比例进行调整的
flex-basis
控制着元素在沿着主轴上 grow 和 shrik 之前最终能够变化多大
flex-grow
决定了他将与兄弟元素成比例的增长多少,flex-shrink
决定了缩小多少
使用对象字面量,对象字面量不需要使用new运算符进行实例化,但不能用在一个语句的开头,因为开始的可能被解读为一个块的开始。
var myObjectLiteral = {
variableKey: variableValue,
functionKey: function () {}
};
Module模式最初被定义为一种在传统软件工程中为类提供私有和公有封装的方法。
Module模式使用闭包封装“私有”状态和组织。它提供了一种包装混合公有/私有方法和变量的方式,防止其泄露至全局作用域,并与别的开发人员的接口发生冲突。通过该模式,只需返回一个公有API,而其他的一切则都维持在私有闭包里。
var myNamespace = (function () {
// 私有计数器变量
var myPrivateVar = 0;
// 记录所有参数的私有函数
var myPrivateMethod = function (foo) {
console.log(foo);
};
return {
// 公有变量
myPublicVar: "foo",
// 调用私有变量和方法的公有函数
myPublicFunction: function (bar) {
// 增加私有计数器值
myPrivateVar++;
// 传入bar 调用私有方法
myPrivateMethod(bar);
}
};
})();
// 全局模块
var myModule = (function (jQ, _) {
function privateMethod1() {
jQ(".container").html("test");
}
function privateMethod2() {
console.log(_.min([10, 5, 100, 2, 1000]));
}
return {
publicMethod: function () {
privateMethod1();
}
}; // 引入jQuery 和Underscore
})(jQuery, _));
myModule.publicMethod();
// 全局模块
var myModule = (function () {
// 模块对象
var module = {},
privateVariable = "Hello World";
function privateMethod() {
// ...
}
module.publicProperty = "Foobar";
module.publicMethod = function () {
console.log(privateVariable);
};
return module;
})();
命名空间
var store = window.store || {};
if (!store["basket"]) {
store.basket = {};
}
if (!store.basket["core"]) {
store.basket.core = {};
}
store.basket.core = {
// ...剩余的逻辑
};
该模式可以使脚本语法更加一致。在模块代码底部,它也会很容易指出哪些函数和变量可以被公开访问,从而改善可读性。
var myRevealingModule = function () {
var privateCounter = 0;
function privateFunction() {
privateCounter++;
}
function publicFunction() {
publicIncrement();
}
function publicIncrement() {
privateFunction();
}
function publicGetCount() {
return privateCounter;
} // 将暴露的公有指针指向到私有函数和属性上
return {
start: publicFunction,
increment: publicIncrement,
count: publicGetCount
};
}();
myRevealingModule.start();
Singleton(单例)模式被熟知的原因是因为它限制了类的实例化次数只能一次。
var mySingleton = (function () {
// 实例保持了Singleton 的一个引用
var instance;
function init() {
// Singleton
// 私有方法和变量
function privateMethod() {
console.log("I am private");
}
var privateVariable = "Im also private";
var privateRandomNumber = Math.random();
return {
// 公有方法和变量
publicMethod: function () {
console.log("The public can see me!");
}, publicProperty: "I am also public",
getRandomNumber: function () {
return privateRandomNumber;
}
};
};
return {
// 获取Singleton 的实例,如果存在就返回,不存在就创建新实例
getInstance: function () {
if (!instance) {
instance = init();
}
return instance;
}
};
})();
一个或多个观察者对目标的状态感兴趣,它们通过将自己依附在目标对象上以便注册所感兴趣的内容。目标状态发生改变并且观察者可能对这些改变感兴趣,就会发送一个通知消息,调用每个观察者的更新方法。当观察者不再对目标状态感兴趣时,它们可以简单地将自己从中分离。
var pubsub = {}; // 定义发布者
(function (q) {
var list = [], //回调函数存放的数组,也就是记录有多少人订阅了我们东西
subUid = -1;
// 发布消息,遍历订阅者
q.publish = function (type, content) {
// type 为文章类型,content为文章内容
// 如果没有人订阅,直接返回
if (!list[type]) {
return false;
}
setTimeout(function () {
var subscribers = list[type],
len = subscribers ? subscribers.length : 0;
while (len--) {
// 将内容注入到订阅者那里
subscribers[len].func(type, content);
}
}, 0);
return true;
};
//订阅方法,由订阅者来执行
q.subscribe = function (type, func) {
// 如果之前没有订阅过
if (!list[type]) {
list[type] = [];
}
// token相当于订阅者的id,这样的话如果退订,我们就可以针对它来知道是谁退订了。
var token = (++subUid).toString();
// 每订阅一个,就把它存入到我们的数组中去
list[type].push({
token: token,
func: func
});
return token;
};
//退订方法
q.unsubscribe = function (token) {
for (var m in list) {
if (list[m]) {
for (var i = 0, j = list[m].length; i < j; i++) {
if (list[m][i].token === token) {
list[m].splice(i, 1);
return token;
}
}
}
}
return false;
};
} (pubsub));
//将订阅赋值给一个变量,以便退订
var girlA = pubsub.subscribe('js类的文章', function (type, content) {
console.log('girlA订阅的'+type + ": 内容内容为:" + content);
});
var girlB = pubsub.subscribe('js类的文章', function (type, content) {
console.log('girlB订阅的'+type + ": 内容内容为:" + content);
});
var girlC = pubsub.subscribe('js类的文章', function (type, content) {
console.log('girlC订阅的'+type + ": 内容内容为:" + content);
});
//发布通知
pubsub.publish('js类的文章', '关于js的内容');
// 输出:
// girlC订阅的js类的文章: 内容内容为:关于js的内容
// test3.html:78 girlB订阅的js类的文章: 内容内容为:关于js的内容
// test3.html:75 girlA订阅的js类的文章: 内容内容为:关于js的内容
//girlA退订了关于js类的文章
setTimeout(function () {
pubsub.unsubscribe(girlA);
}, 0);
//再发布一次,验证一下是否还能够输出信息
pubsub.publish('js类的文章', "关于js的第二篇文章");
// 输出:
// girlB订阅的js类的文章: 内容内容为:关于js的第二篇文章
// girlC订阅的js类的文章: 内容内容为:关于js的第二篇文章
最近因为项目上用到音乐播放,就仔细研究了一下html5 audio API,利用国庆休息的时间,进行了一些总结,有些坑还没填好已经备注文档中。
这篇文章主要是介绍一些基本使用,下一篇将主要与大家分享audio在各个浏览器和设备上存在的问题以及如何去解决。
属性 | 作用 |
---|---|----
src | 设置或返回是否在就绪(加载完成)后随即播放音频
currentSrc | 返回当前音频的 URL。
currentTime | 设置或返回音频中的当前播放位置(以秒计)。
duration | 返回音频的长度(以秒计)。
readyState | 返回音频当前的就绪状态。
networkState | 返回音频的当前网络状态。
属性 | 值 | 作用 |
---|---|---|
paused | bool | 设置或返回音频是否暂停。 |
ended | bool | 返回音频的播放是否已结束。 |
muted | bool | 设置或返回是否关闭声音。 |
controls | bool | 设置或返回音频是否应该显示控件(比如播放/暂停等)。 |
loop | bool | 设置或返回音频是否应在结束时再次播放。 |
autoplay | bool | 设置或返回是否在就绪(加载完成)后随即播放音频。 |
preload | bool | 设置或返回音频的 preload 属性的值。 |
volume | 范围 0-1 | 设置或返回音频的音量。 |
playbackRate | 1.0/2.0倍速度 -2后退两倍速度 | 设置或返回音频/视频播放的速度(留下一个坑 负值不起作用) |
名称 | 作用 |
---|---|
canPlayType() | 查浏览器是否可以播放指定的音频类型 "probably" - 浏览器最可能支持该音频/视频类型,"maybe" - 浏览器也许支持该音频/视频类型,"" - (空字符串)浏览器不支持该音频/视频类型 |
fastSeek() | 在音频播放器中指定播放时间 |
load() | 重新加载音频元素 |
play() | 开始播放音频 |
pause() | 暂停当前播放的音频 |
事件名称 | 事件描述 |
---|---|
loadstart | 客户端开始请求数据 |
progress | 客户端正在请求数据(或者说正在缓冲) |
play | 播放中 |
pause | 暂停 |
ended | 播放结束 |
timeupdate | 当前播放时间发生改变的时候。常用作显示进度 |
canplaythrough | 歌曲已经载入完全完成 |
canplay | 缓冲至目前可播放状态。 |
error | 播放发生错误时。 |
[toc]
1、在公共Js中根据生产环境、测试环境和开发环境域名不同来定义不同的域名
// 示例
const production = host.indexOf("static.watch.okii.com") > -1;
// 定位接口域名
export const location_domain = production
? `${protocol}//location.watch.okii.com`
: `${protocol}//location.module.okii.com`;
2、在构建的时候根据node环境变量来判断来构建打包
示例参考
webpack生产环境官方配置说明
http://deploy.test/front/static/css/static/fonts/ionicons.24712f6.ttf
http://deploy.test/front/static/fonts/ionicons.24712f6.ttf
在 webpack 出现之前,前端开发人员会使用 grunt 和 gulp 等工具来处理资源,并将它们从 /src 文件夹移动到 /dist 或 /build 目录中。同样方式也被用于 JavaScript 模块,但是,像 webpack 这样的工具,将动态打包(dynamically bundle)所有依赖项(创建所谓的依赖图(dependency graph))。这是极好的创举,因为现在每个模块都可以_明确表述它自身的依赖,我们将避免打包未使用的模块。
webpack 最出色的功能之一就是,除了 JavaScript,还可以通过 loader 引入任何其他类型的文件。也就是说,以上列出的那些 JavaScript 的优点(例如显式依赖),同样可以用来构建网站或 web 应用程序中的所有非 JavaScript 内容。让我们从 CSS 开始起步,或许你可能已经熟悉了这个设置过程。
设计的初衷是用于简单运算的,避免在模板中有太多的计算。
状态管理的问题
子路由、嵌套路由
axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
在请求或响应被 then 或 catch 处理前拦截它们。
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
如果你想在稍后移除拦截器,可以这样:
var myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);
可以使用 validateStatus 配置选项定义一个自定义 HTTP 状态码的错误范围。
axios.get('/user/12345', {
validateStatus: function (status) {
return status < 500; // 状态码在大于或等于500时才会 reject
}
})
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
Promise 实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
});
Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。
另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。
p.then((val) => console.log('fulfilled:', val))
.catch((err) => console.log('rejected', err));
// 等同于
p.then((val) => console.log('fulfilled:', val))
.then(null, (err) => console.log("rejected:", err));
用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。
Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
Promise 必须为以下三种状态之一:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。一旦Promise被resolve或reject,不能再迁移至其他任何状态(即状态 immutable)。
为保持代码清晰,暂无异常处理。同时为表述方便,约定如下:
fulfilled 使用 resolved 代替
onFulfilled 使用 onResolved 代替
从构造函数开始,我们一步步实现符合 Promsie A+ 规范的 Promise。大概描述下,Promise构造函数需要做什么事情。
function Promise (fn) {
// 省略非 new 实例化方式处理
// 省略 fn 非函数异常处理
// promise 状态变量
// 0 - pending
// 1 - resolved
// 2 - rejected
this._state = 0;
// promise 执行结果
this._value = null;
// then(..) 注册回调处理数组
this._deferreds = [];
// 立即执行 fn 函数
try {
fn(value => {
resolve(this, value);
},reason => {
reject(this, reason);
})
} catch (err) {
// 处理执行 fn 异常
reject(this, err);
}
}
_state 和 _value 变量很容易理解,_deferreds变量做什么?规范描述:then 方法可以被同一个 promise 调用多次。为满足多次调用 then 注册回调处理,内部选择使用 _deferreds 数组存储处理对象。具体处理对象结构,见 then 函数章节。
最后执行 fn 函数,并调用 promise 内部的私有方法 resolve 和 reject。resolve 和 reject 内部细节随后介绍。
Promise A+提到规范专注于提供通用的 then 方法。then 方法可以被同一个 promise 调用多次,每次返回新 promise 对象 。then 方法接受两个参数onResolved、onRejected(可选)。在 promise 被 resolve 或 reject 后,所有 onResolved 或 onRejected 函数须按照其注册顺序依次回调,且调用次数不超过一次。
根据上述,then 函数执行流程大致为:
实例化空 promise 对象用来返回(保持then链式调用)
构造 then(..) 注册回调处理函数结构体
判断当前 promise 状态,pending 状态存储延迟处理对象 deferred ,非pending状态执行 onResolved 或 onRejected 回调
...
Promise.prototype.then = function (onResolved, onRejected) {
var res = new Promise(function () {});
// 使用 onResolved,onRejected 实例化处理对象 Handler
var deferred = new Handler(onResolved, onRejected, res);
// 当前状态为 pendding,存储延迟处理对象
if (this._state === 0) {
this._deferreds.push(deferred);
return res;
}
// 当前 promise 状态不为 pending
// 调用 handleResolved 执行onResolved或onRejected回调
handleResolved(this, deferred);
// 返回新 promise 对象,维持链式调用
return res;
};
Handler 函数封装存储 onResolved、onRejected 函数和新生成 promise 对象。
function Handler (onResolved, onRejected, promise) {
this.onResolved = typeof onResolved === 'function' ? onResolved : null;
this.onRejected = typeof onRejected === 'function' ? onRejected : null;
this.promise = promise;
}
var promise2 = promise1.then(function (value) {
return Promise.reject(3)
})
复制代码假如 then 函数执行返回 this 调用对象本身,那么 promise2 === promise1,promise2 状态也应该等于 promise1 同为 resolved。而 onResolved 回调中返回状态为 rejected 对象。考虑到 Promise 状态一旦 resolved 或 rejected就不能再迁移,所以这里 promise2 也没办法转为回调函数返回的 rejected 状态,产生矛盾。
handleResolved 函数功能为根据当前 promise 状态,异步执行 onResolved 或 onRejected 回调函数。因在 resolve 或 reject 函数内部同样需要相关功能,提取为单独模块。往下翻阅查看。
集合是一组无序但彼此之间又有一定相关性的成员构成的,每个成员在集合中只能出现一次。
- 不包含任何成员的空集,全集则是包含一切的成员的集合。
- 如果两个集合的成员完全相同,则称为两个集合相等
- 如果一个集合中所有的成员都属于另外一个集合则称前一集和为另一集合的子集
资源参考与完善:https://github.com/debingfeng/javascript
/**
* 集合类JavaScript描述
* @constructor
*/
function Set() {
this.dataStore = [];
this.add = add;
this.remove = remove;
this.size = size;
this.union = union;
this.intersect = intersect;
this.subset = subset;
this.difference = difference;
this.show = show;
this.has = has;
}
/**
* 添加元素
* @param data
* @returns {boolean}
*/
function add(data) {
if (this.dataStore.indexOf(data) < 0) {
this.dataStore.push(data);
return true;
} else {
return false;
}
}
/**
* 移除数据
* @param data
* @returns {boolean}
*/
function remove(data) {
var index = this.dataStore.indexOf(data);
if (index > -1) {
this.dataStore.splice(index,1);
return true;
} else {
return false;
}
}
/**
* 获取集合所有数据
* @returns {Array}
*/
function show() {
return this.dataStore;
}
/**
* 检测是否含有某元素
* @param data
* @returns {boolean}
*/
function has(data) {
return this.dataStore.indexOf(data) >= 0;
}
/**
* 获取元素的长度
* @returns {Number}
*/
function size() {
return this.dataStore.length;
}
/**
* 并集
* @param set
* @returns {Set}
*/
function union(set) {
var tempSet = new Set();
var i = 0,
l = this.dataStore.length;
for (; i < l; i++) {
tempSet.add(this.dataStore[i]);
}
var j = 0,
ol = set.dataStore.length;
for ( ; j < ol; j++) {
if (!(tempSet.has(set.dataStore[j]))) {
tempSet.dataStore.push(set.dataStore[j]);
}
}
return tempSet;
}
//当一个元素属于一个集合,同时也属于另一个集合时,则把该元素加入到一个新集合。
/**
* 交集
* @param set
* @returns {Set}
*/
function intersect(set) {
var tempSet = new Set();
var i = 0,
l = this.dataStore.length;
for (; i < l; i++) {
if (set.has(this.dataStore[i])) {
tempSet.add(this.dataStore[i]);
}
}
return tempSet;
}
// 首先判断这个集合的长度是否大于待比较的集合,如果大于则不可能是他的子集,进而判断该集合的元素是否有不存在待比较的集合中,如果有则说明不是他的子集。
/**
* 子集
* @param set
* @returns {Set}
*/
function subset(set) {
if(this.size() > set.size()) {
return false;
}
var i = 0,
l = this.dataStore.length;
for (; i < l; i++) {
if (!set.has(this.dataStore[i])) {
return false
}
}
return true;
}
/**
* 补集
* @param set
* @returns {Set}
*/
function difference(set) {
var tempSet = new Set();
var i = 0,
l = this.dataStore.length;
for (; i < l; i++) {
if (!(set.has(this.dataStore[i]))) {
tempSet.add(this.dataStore[i]);
}
}
return tempSet;
}
var a = new Set();
a.add("a");
a.add("b");
var b = new Set();
b.add("b");
b.add("c");
console.log(a.union(b).show());
console.log(a.difference(b).show());
console.log(a.intersect(b).show());
px : css逻辑像素,浏览器使用的抽象单位
dp,pt: 设备独立像素
dpr: 设备缩放比
计算公式: 1 px = dpr的平方 * dp
PPI = 根号(物理像素的长^2 + 宽^2) / 4
<meta name="viewport" content="width=device-width, initial-scale=1.0,user-scalable=no">
以iphone6 640*1167宽度 320 来设计
缩放比0.5来设计 这时1px = pt
弹性布局 为了兼容安卓4.4以下的设备,可以使用flexbox,-webkit-box,其他最新的浏览器使用flex.
响应式布局
媒体查询,百分比
弹性图片
重新布局,显示与隐藏
自适应性
多行文本溢出
设置基础值,screen.width/20
使用zepto 有点透的bug
自己模拟tap效果
-webkit-transform:translateZ(0);
局部滚动开启弹性滚动:
body {
overflow: scroll;
-webkit-overflow-scrolling: touch;
}
Angular2出来了一段时间,通过更简单和更简洁的概念,如基于组件的架构,新的依赖注入或内置模块化,新版本的框架要学习得简单得多。在这个分步教程中,您将了解如何使用TypeScript和Gulp开始使用Angular2。源代码在Github上可用。
本文是翻译codeleak.pl上的一篇文章。(英语很挫,错误在所难免)
原文地址:http://blog.codeleak.pl/2016/03/quickstart-angular2-with-typescript-and.html
[toc]
15/09/2016升级到2.0.0重新angular2文章,添加新的代码库中的代码来反映。添加快速入门指南。
如果你不感兴趣的一步一步的教程,只需按照以下步骤迅速启动。
Nodejs 必须安装在您的系统和下面的全局节点包必须安装:
gulp
npm i -g gulp
gulp-cli
npm i -g gulp-cli
typings
npm i -g [email protected]
typescript
npm i -g [email protected]
ts-node
npm i -g [email protected]
git clone https://github.com/kolorobot/angular2-typescript-gulp.git
cd angular2-typescript-gulp
npm install
npm run clean & npm run build
// 构建后会产生Build目录
npm start
至今,Angular 1.X可能仍然是最流行的前端框架,毫无疑问Angular 1.X是一个伟大的框架。然而,这是非常难以掌握。复杂的API和许多概念推出以来,使人了解框架和有效地使用它确实很难。
angular2,另一方面,是一个新的开放。新版本的框架要简单得多,学习更简单和更简洁的概念,如基于组件的体系结构,新的依赖注入或内置模块化。
如果你想找一个比 angular.io 更好地 练习和开始学习Angular 2的地方,如果正在寻找用gulp构建来使用Angular 2的方式,那么这篇文档正适合你!
Note: 文章所使用的代码资源在github
https://github.com/kolorobot/angular2-typescript-gulp.
初始化的工程是基于Angular2 Quickstart:
对https://angular.io/docs/ts/latest/quickstart.html 做了一些改变。
最重要的变化是将源文件与生成文件分离:SRC目录包含所有源文件并生成包含所有已编译和处理的文件。
服务器使用build目录作为基本目录来资源文件。
angular2-typescript-gulp
| .gitignore
| bs-config.json -> BrowserSync configuration
| gulpfile.ts -> Gulp in TypeScript
| package.json -> npm configuration
| tsconfig.json -> TypeScript configuration
| typings.json -> TypeScript typings definitions
| tslint.json -> tslint configuration
|
\---src
│ │ index.html -> Starting point for the application
│ │ systemjs.config.js -> SystemJS configuration
│ │
│ \---app -> Application modules
│ │ app.component.ts -> Main application component
│ │ app.html -> Main application template
│ │ app.module.ts -> Application module definition
│ │ app.routing.ts -> Routing configuration
│ │ main.ts -> Application bootstrap
│ │
│ \---about
│ │ └───components
│ │ about.components.ts
│ │ about.html
│ │
│ \---todo
│ ├───components
│ │ task-list.component.ts
│ │ task-list.css
│ │ task-list.html
│ │ task.component.ts
│ │ task.html
│ │
│ \---models
│ │ task.ts
│ │
│ \---services
│ task-service.ts
假设Node和NPM已经安装,你可以通过调用下面的命令安装全局依赖:
npm i -g <dependency>
为了运行工程必须安装的全局依赖:
gulp and gulp-cli
npm i -g gulp
npm i -g gulp-cli
typings
npm i -g [email protected]
typescript
npm i -g [email protected]
ts-node
npm i -g [email protected]
// Note: To check global dependencies use the following command:
npm -g –depth 0 ls
创建如上文所述的目录和文件结构
编译文件会被保存至build/app;请注意gulpfile.ts要排除编译。
{
"compilerOptions": {
"outDir": "build/app",
"target": "es5",
"module": "system",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": false
},
"exclude": [
"gulpfile.ts",
"node_modules"
]
}
说明:如果你想导入你的工程到IDE(如IntelliJ),让这个IDE也使用这个文件。
To get started we need some definitions to be installed. Run the following commands:
为了能够正常运行开始,我们需要的一些定义被安装。运行一下命令:
typings install –global –save dt~core-js
typings install –global –save dt~node
这些会被添加至typings.json
{
"globalDependencies": {
"core-js": "registry:dt/core-js#0.0.0+20160725163759",
"node": "registry:dt/node#6.0.0+20160909174046",
}
}
Typings will be download to typings directory and they will be downloaded on npm install.
Typings 会被下载到typings目录并且他们会通过npm instal的方式被下载下来。
关于脚本命令的一些命令:
{
"name": "angular2-typescript-gulp",
"version": "1.0.0",
"description": "Angular2 with TypeScript and Gulp QuickStart",
"scripts": {
"clean": "gulp clean",
"compile": "gulp compile",
"build": "gulp build",
"start": "concurrent --kill-others \"gulp watch\" \"lite-server\"",
"postinstall": "typings install"
},
"repository": {
"type": "git",
"url": "https://github.com/kolorobot/angular2-typescript-gulp.git"
},
"author": "Rafał Borowiec",
"license": "MIT",
"bugs": {
"url": "https://github.com/kolorobot/angular2-typescript-gulp/issues"
},
"dependencies": {
"@angular/common": "2.0.0",
"@angular/compiler": "2.0.0",
"@angular/core": "2.0.0",
"@angular/forms": "2.0.0",
"@angular/http": "2.0.0",
"@angular/platform-browser": "2.0.0",
"@angular/platform-browser-dynamic": "2.0.0",
"@angular/router": "3.0.0",
"@angular/upgrade": "2.0.0",
"core-js": "^2.4.1",
"reflect-metadata": "^0.1.3",
"rxjs": "5.0.0-beta.12",
"systemjs": "0.19.27",
"zone.js": "^0.6.23"
},
"devDependencies": {
"concurrently": "^2.2.0",
"del": "^2.2.0",
"gulp": "^3.9.1",
"gulp-sourcemaps": "^1.6.0",
"gulp-tslint": "^6.1.1 ",
"gulp-typescript": "^2.13.6",
"lite-server": "^2.2.2",
"tslint": "^3.5.0",
"typescript": "^2.0.2",
"typings": "^1.3.3",
"ts-node": "^1.3.0"
}
}
默认情况下,内容从当前目录提供,因此需要更改;而且,由于Lite Server使用BrowserSync,足以提供配置服务器的bs-config.json来从构建目录提供内容。
{
"port": 8000,
"files": [
"build/**/*.{html,htm,css,js}"
],
"server": {
"baseDir": "build"
}
}
TSLint检查TypeScript代码是否可读性,可维护性和功能错误,而Gulp可以与gulp-tslint插件一起使用。
tslint.json用于配置哪些规则可以运行。只需将文件添加到项目的根目录。您应该根据需要调整规则。您可以在这里找到有关规则的更多信息:http://palantir.github.io/tslint/usage/tslint-json/
{
"rules": {
"class-name": true,
"curly": true,
"eofline": false,
"forin": true,
"indent": [
true,
4
],
"label-position": true,
"label-undefined": true,
"max-line-length": [
true,
140
],
"no-arg": true,
"no-bitwise": true,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-debugger": true,
"no-duplicate-key": true,
"no-duplicate-variable": true,
"no-empty": false,
"no-eval": true,
"no-string-literal": false,
"no-trailing-whitespace": true,
"no-unused-variable": false,
"no-unreachable": true,
"no-use-before-declare": true,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"radix": true,
"semicolon": true,
"triple-equals": [
true,
"allow-null-check"
],
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator"
]
}
}
要开始,我们需要一个编译TypeScript文件,将资产和依赖关系复制到构建目录的任务。为了实现这些任务需要几个任务。
注意:Gulp文件是以TypeScript而不是JavaScript创建的。它需要ts-node执行,如本教程开头所述。
"use strict";
const gulp = require("gulp");
const del = require("del");
const tsc = require("gulp-typescript");
const sourcemaps = require('gulp-sourcemaps');
const tsProject = tsc.createProject("tsconfig.json");
const tslint = require('gulp-tslint');
/**
* Remove build directory.
*/
gulp.task('clean', (cb) => {
return del(["build"], cb);
});
/**
* Lint all custom TypeScript files.
*/
gulp.task('tslint', () => {
return gulp.src("src/**/*.ts")
.pipe(tslint({
formatter: 'prose'
}))
.pipe(tslint.report());
});
/**
* Compile TypeScript sources and create sourcemaps in build directory.
*/
gulp.task("compile", ["tslint"], () => {
let tsResult = gulp.src("src/**/*.ts")
.pipe(sourcemaps.init())
.pipe(tsc(tsProject));
return tsResult.js
.pipe(sourcemaps.write(".", {sourceRoot: '/src'}))
.pipe(gulp.dest("build"));
});
/**
* Copy all resources that are not TypeScript files into build directory.
*/
gulp.task("resources", () => {
return gulp.src(["src/**/*", "!**/*.ts"])
.pipe(gulp.dest("build"));
});
/**
* Copy all required libraries into build directory.
*/
gulp.task("libs", () => {
return gulp.src([
'core-js/client/shim.min.js',
'systemjs/dist/system-polyfills.js',
'systemjs/dist/system.src.js',
'reflect-metadata/Reflect.js',
'rxjs/**',
'zone.js/dist/**',
'@angular/**'
], {cwd: "node_modules/**"}) /* Glob required here. */
.pipe(gulp.dest("build/lib"));
});
/**
* Watch for changes in TypeScript, HTML and CSS files.
*/
gulp.task('watch', function () {
gulp.watch(["src/**/*.ts"], ['compile']).on('change', function (e) {
console.log('TypeScript file ' + e.path + ' has been changed. Compiling.');
});
gulp.watch(["src/**/*.html", "src/**/*.css"], ['resources']).on('change', function (e) {
console.log('Resource file ' + e.path + ' has been changed. Updating.');
});
});
/**
* Build the project.
*/
gulp.task("build", ['compile', 'resources', 'libs'], () => {
console.log("Building the project ...");
});
现在是安装所有依赖关系的时候了。运行:
npm install
应该在安装期间创建node_modules和typing目录。
构建工程
npm run clean & npm run build
build目录应在构建期间创建
注意:如果在编译期间看到以下内容,请确保至少有ts-node 1.3.0:
[00:49:42] Failed to load external module ts-node/register
[00:49:42] Failed to load external module typescript-node/register
[00:49:42] Failed to load external module typescript-register
[00:49:42] Failed to load external module typescript-require
库是在构建任务期间创建的lib目录的引用
<html>
<head>
<title>Angular 2 TypeScript Gulp QuickStart</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 1. Load libraries -->
<!-- Polyfill(s) for older browsers -->
<script src="lib/core-js/client/shim.min.js"></script>
<script src="lib/zone.js/dist/zone.js"></script>
<script src="lib/reflect-metadata/Reflect.js"></script>
<script src="lib/systemjs/dist/system.src.js"></script>
<!-- 2. Configure SystemJS -->
<script src="systemjs.config.js"></script>
<script>
System.import('app')
.then(null, console.error.bind(console));
</script>
</head>
<!-- 3. Display the application -->
<body>
<app>Loading...</app>
</body>
</html>
function (global) {
System.config({
paths: {
// paths serve as alias
'npm:': 'lib/'
},
// map tells the System loader where to look for things
map: {
// our app is within the app folder
app: 'app',
// angular bundles
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
'@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
'@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
'@angular/http': 'npm:@angular/http/bundles/http.umd.js',
'@angular/router': 'npm:@angular/router/bundles/router.umd.js',
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
// other libraries
'rxjs': 'npm:rxjs'
},
// packages tells the System loader how to load when no filename and/or no extension
packages: {
app: {
main: './main.js',
defaultExtension: 'js'
},
rxjs: {
defaultExtension: 'js'
}
}
});
})(this);
1. 主入口组件 - src/app/app.component.ts
import {Component, OnInit} from "@angular/core";
@Component({
selector: "app",
templateUrl: "./app/app.html"
})
export class AppComponent implements OnInit {
ngOnInit() {
console.log("Application component initialized ...");
}
}
他的模板 (src/app/app.html):
<p>Angular 2 is running ... </p>
2. About 模块
关于模块由两个基本构件 - 组件及其模板组成。
src/app/about/components/about.component.ts
import {Component} from "@angular/core";
import {OnInit} from "@angular/core";
@Component({
templateUrl: './app/about/components/about.html'
})
export class AboutComponent implements OnInit {
ngOnInit() {
}
}
src/app/about/components/about.html:
<h1>About</h1>
<p>这是about</p>
Todo模块有点复杂 - 它包含组件,模型和服务。
src/app/todo/components/task.component.ts
import {Component} from "@angular/core";
import {Input} from "@angular/core";
import {Task} from "../models/task";
import {Output} from "@angular/core";
import {EventEmitter} from "@angular/core";
@Component({
selector: 'task',
templateUrl: './app/todo/components/task.html'
})
export class TaskComponent {
@Input() task:Task;
@Output() statusChanged:any = new EventEmitter<any>();
toggleDone() {
this.task.toggleDone();
this.statusChanged.emit(null);
}
}
src/app/todo/components/task.html
<form role="form">
<input title="Name" type="text" [(ngModel)]="task.name" name="name" [disabled]="task.done" />
<input title="Done" type="checkbox" (click)="toggleDone()" [checked]="task.done" />
</form>
src/app/todo/components/task-list.component.ts
import {Component} from "@angular/core";
import {Task} from "../models/task";
import {OnInit} from "@angular/core";
import {TaskService} from "../services/task-service";
import {TaskComponent} from "./task.component";
@Component({
selector: 'task-list',
templateUrl: './app/todo/components/task-list.html',
styleUrls: ['./app/todo/components/task-list.css'],
providers: [TaskService]
})
export class TaskListComponent implements OnInit {
todoCount:number;
selectedTask:Task;
tasks:Array<Task>;
constructor(private _taskService:TaskService) {
this.tasks = _taskService.getTasks();
this.calculateTodoCount();
}
ngOnInit() {
console.log("Todo component initialized with " + this.tasks.length + " tasks.");
}
calculateTodoCount() {
this.todoCount = this.tasks.filter(t => !t.done).length;
}
select(task:Task) {
this.selectedTask = task;
}
}
src/app/todo/components/task-list.css
li.selected {
background-color: #8a8a8a;
}
src/app/todo/components/task-list.html
<h1>Todo tasks ({{todoCount}})</h1>
<ul>
<li *ngFor="let task of tasks" [class.selected]="task == selectedTask">
<a href="javascript:void(0)" (click)="select(task)">
{{task.name}}
<span [ngSwitch]="task.done">
<template [ngSwitchCase]="true">[Done]</template>
<template [ngSwitchCase]="false">[Todo]</template>
</span>
</a>
</li>
</ul>
<task *ngIf="selectedTask" [task]="selectedTask" (statusChanged)="calculateTodoCount()"></task>
src/app/todo/models/task.ts
export class Task {
constructor(public name:string, public done:boolean) {
}
toggleDone() {
this.done = !this.done;
}
}
src/app/todo/services/task-service.ts
import {Injectable} from "@angular/core";
import {Task} from "../models/task";
@Injectable()
export class TaskService {
private tasks:Array<Task> = [
new Task("Task 1", false),
new Task("Task 2", false),
new Task("Task 3", false),
new Task("Task 4", false),
new Task("Task 5", false)
];
getTasks():Array<Task> {
return this.tasks;
}
addTask(name:string) {
this.tasks.push(new Task(name, false));
}
}
import {Routes, RouterModule} from '@angular/router';
import {TaskListComponent} from "./todo/components/task-list.component";
import {AboutComponent} from "./about/components/about.component";
import {ModuleWithProviders} from "@angular/core";
const appRoutes: Routes = [
{path: 'tasks', component: TaskListComponent, data: {title: 'TaskList'}},
{path: 'about', component: AboutComponent, data: {title: 'About'}}
];
export const appRoutingProviders: any[] = [];
export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes, { useHash: true });
Angular2模块有助于将应用程序整合到一个功能性的集合中。
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {AppComponent} from "./app.component";
import {TaskListComponent} from "./todo/components/task-list.component";
import {AboutComponent} from "./about/components/about.components";
import {TaskComponent} from "./todo/components/task.component";
import {routing, appRoutingProviders} from './app.routing';
import {FormsModule} from "@angular/forms";
@NgModule({
imports: [
BrowserModule,
FormsModule,
routing
],
declarations: [
AppComponent,
TaskComponent,
TaskListComponent,
AboutComponent
],
providers: [
appRoutingProviders
],
bootstrap: [AppComponent]
})
export class AppModule {
}
//<reference path="../../typings/index.d.ts"/>
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppModule} from './app.module';
const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);
npm run clean & npm run build
npm start
您应该看到应用程序在浏览器中运行
IntelliJ处理建议的设置没有问题。如果IntelliJ中使用TypeScript编译器,则应使用该项目的tsconfig.json。在这种情况下,ts文件的更改将立即反映在构建目录中。
https://github.com/kolorobot/angular2-typescript-gulp
需要更多的实际使用这个项目。因此,很快就会添加更多功能。如果您有任何建议,评论或添加Github问题。
如果您需要更成熟的启动器,请查看angular2-seed项目
https://github.com/mgechev/angular2-seed
angular2-seed以更先进的方式使用gulp,并且已经支持生产和开发构建,单元和集成测试等等。
[toc]
看过了pug(以前叫jade)的语法,确实简洁,
class提示功能等不方便,对于编写前端Html的人来说,真的不习惯。
格式要求比较严格
ejs语法比较符合 我们的需求,在Html里面嵌套,符合长期编写前端页面的人。
npm install ejs
var ejs = require('ejs');
//注册ejs模板为html页。简单的讲,就是原来以.ejs为后缀的模板页,现在的后缀名可以//是.html了
app.engine('.html', ejs.__express);
//设置视图模板的默认后缀名为.html,避免了每次res.Render("xx.html")的尴尬
app.set('view engine', 'html');
//设置模板文件文件夹,__dirname为全局变量,表示网站根目录
app.set('views', __dirname + '/views');
// 设置可访问静态资源目录
app.use(express.static(__dirname + '/'));
var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next) {
var users = [
{name: 'tobi', email: '[email protected]'},
{name: 'loki', email: '[email protected]'},
{name: 'jane', email: '[email protected]'}
];
//渲染模板
res.render('index', {
list: users,
title: "EJS example",
header: "Some users"
});
});
module.exports = router;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0,user-scalable=no">
<meta content="telephone=no" name="format-detection">
<meta content="email=no" name="format-detection">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-capable" content="yes"/>
<link rel="stylesheet" href="/bower_components/bootstrap/dist/css/bootstrap.min.css">
<meta name="apple-touch-fullscreen" content="yes"/>
<title> <%= title %></title>
</head>
<body>
<header class="navbar navbar-collapse">
<div class="container"><h2><%= header %></h2></div>
</header>
<div class="container">
<table class="table table-responsive table-hover">
<tr>
<th>名字</th>
<th>邮箱</th>
</tr>
<% for(var i in list) { %>
<tr>
<td><%= list[i].name %></td>
<td><%= list[i].email %></td>
</tr>
<% } %>
</table>
</div>
</body>
</html>
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.