tonyzheng121 / tonyzheng1990.github.io Goto Github PK
View Code? Open in Web Editor NEW平常学习中一些问题的记录和总结
Home Page: https://tonyzheng1990.github.io
平常学习中一些问题的记录和总结
Home Page: https://tonyzheng1990.github.io
按照 JSON 的规范,使用 JSON.stringify() 做对象序列化时,如果一个属性为函数,那这个属性就会被忽略。
const data1 = {
a: 'aaa',
fn: function() {
return true
}
}
JSON.stringify(data)
// 结果是 "{"a":"aaa"}"
还有一种情况,一个属性的值为 undefined
const data2 = {
a: 'abc',
b: undefined
}
JSON.stringify(data2)
// 结果是 "{"a":"abc"}"
如果属性为 null 则可以正常序列化这个属性:
const data3 = {
a: 'abc',
b: null
}
JSON.stringify(data3)
// 结果是 "{"a":"abc","b":null}"
因为 null 可表示已经赋值,而 undefined 表示未定义、未赋值,所以执行 JSON.stringify 不会处理。
stringify 函数的定义为 JSON.stringify(value [, replacer [, space]])
后面还带有我不常用两个可选参数 replacer 和 space
value 参数不多解释,replacer 其实就是一个自定义函数,可以改变 JSON.stringify 的行为,space 就是格式化输出,最大值为 10,非整数时取值为 1
本质上无论怎么改,stringify 还是不会输出 Function,但是 Function 可以调用 toString() 方法的,所以思路就很明了了。
const data1 = {
a: 'aaa',
fn: function() {
return true
}
}
const replace = function(k ,v) {
if(typeof v === 'function') {
return Function.prototype.toString.call(v)
}
return v
}
JSON.stringify(data1, replace)
// 结果 "{"a":"aaa","fn":"function () {\n return true\n }"}"
同理可证 undefined 也能输出了
const replace = function(k ,v) {
if(v === undefined){
return 'undefined'
}
return v
}
JSON.stringify 的第三个参数很简单,相当于我们编辑器的 tab_size
const data4 = {
a: 'abc',
b: null,
c: {
x: 'xxx',
y: 'yyy',
z: 2046
},
d: 9527
}
JSON.stringify(data4, null, 2);
// 输出结果
/*
"{
"a": "abc",
"b": null,
"c": {
"x": "xxx",
"y": "yyy",
"z": 2046
},
"d": 9527
}"
*/
toJSON 是个覆盖函数,尽量少用,看了代码就懂了:
const data5 = {
a: 'abc',
b: null,
c: {
x: 'xxx',
y: 'yyy',
z: 2046
},
d: 9527,
toJSON: function() {
return 'WTF'
}
}
JSON.stringify(data5, null, 2);
// 结果返回 "WTF"
直接覆盖掉所有的序列化处理,返回了 "WTF"
下面的参考链接中也包含了跟ES6的import和export的区别
1.Array.prototype.includes
2.Exponentiation Operator(求冥运算)
1.Object.values/Object.entries
2.String padding(字符串填充)
3.Object.getOwnPropertyDescriptors
4.函数参数列表和调用中的尾逗号(Trailing commas)
call方法
apply方法
基本使用
function Animal(){
this.name = "Animal";
this.showName = function(){
alert(this.name);
}
}
function Cat(){
this.name = "Cat";
}
var animal = new Animal();
var cat = new Cat();
//通过call或apply方法,将原本属于Animal对象的showName()方法交给对象cat来使用了。
//输入结果为"Cat"
animal.showName.call(cat,","); //animal.showName.apply(cat,[]);
```
实现继承
function Animal(name) {
this.name = name;
this.showName = function(){
alert(this.name);
}
}
function Cat(name) {
Animal.call(this, name);
}
var cat = new Cat("Black Cat");
cat.showName();
```
Animal.call(this)的意思就是使用Cat对象代替this对象,那么Cat中就有Animal的所有属性和方法了。
多重继承
function Class10() {
this.showSub = function(a,b) {
alert(a-b);
}
}
function Class11() {
this.showAdd = function(a,b) {
alert(a+b);
}
}
function Class2() {
Class10.call(this);
Class11.call(this);
}
```
使用两个call就可以实现多重继承了。
除了call,还有apply,这两个方法基本是一个意思,只是call的第二个参数可以是任意类型,但是apply的第二个参数必须是数组或者arguments。
Vue用了也有很长时间了,一直在思考一个问题,Vue中的directive跟component到底应该应用在什么场景下.Angular中有directive,因为它没有component的概念,很多对于通用组件的封装都会用directive来实现,里面可以操作dom等.所以我在想是不是Vue中对于这两个的区别就是对DOM操作的都是写成一个directive,没有对dom进行操作的就写成一个component.其实我看directive能做的事情,component都能完成,所以我还是觉得directive没什么必要.React中就没有directive.之后看了几篇文章,里面也说directive不是vue中很重要的一部分.
在grunt中执行脚本,里面有个maxBuffer参数,当报
maxBuffer exceeded
的错的时候,记得提高这个值,或者设置为maxBuffer: Infinity
使用r.js来打包requirejs的项目
BEM的意思就是块(block)、元素(element)、修饰符(modifier),是由Yandex团队提出的一种前端命名方法论。这种巧妙的命名方法让你的CSS类对其他开发者来说更加透明而且更有意义。BEM命名约定更加严格,而且包含更多的信息,它们用于一个团队开发一个耗时的大项目。
重要的是要注意,我使用的基于BEM的命名方式是经过Nicolas Gallagher修改过的。这篇文章中介绍的这种命名技术并不是原始的BEM,但却是一个我更喜欢的改进版。无论实际使用了什么样的符号,它们其实都是基于同样的BEM原则。
命名约定的模式如下:
.block{}
.block__element{}
.block--modifier{}
之所以使用两个连字符和下划线而不是一个,是为了让你自己的块可以用单个连字符来界定,如:
.coupon-list{} /* 块 /
.coupon-list__field{} / 元素 /
.coupon-list--full{} / 修饰符 */
BEM的关键是光凭名字就可以告诉其他开发者某个标记是用来干什么的。通过浏览HTML代码中的class属性,你就能够明白模块之间是如何关联的:有一些仅仅是组件,有一些则是这些组件的子孙或者是元素,还有一些是组件的其他形态或者是修饰符。我们用一个类比/模型来思考一下下面的这些元素是怎么关联的:
.person{}
.person__hand{}
.person--female{}
.person--female__hand{}
.person__hand--left{}
顶级块是person
,它拥有一些元素,如hand
。一个人也会有其他形态,比如女性,这种形态进而也会拥有它自己的元素。下面我们把他们写成常规CSS:
.person{}
.hand{}
.female{}
.female-hand{}
.left-hand{}
BEM(或BEM的变体)是一个非常有用,强大,简单的命名约定,以至于让你的前端代码更容易阅读和理解,更容易协作,更容易控制,更加健壮和明确而且更加严密。
[OOCSS](Object Oriented CSS),字面意思是面向对象的CSS,是由Nicole Sullivan提出的css理论,其主要的两个原则是:
用一个例子来说明。请看下面这样的图文排列:
<div class="media media-shadow">
<div class="media-image-container">
<img class="media-image" src="rean.jpg" alt="">
</div>
<div class="media-body">
<p class="media-text">本作的主角,帝国北部地方贵族施瓦泽男爵的养子,也是托尔兹士官学校特科班“Ⅶ组”的成员。</p>
</div>
</div>
.media {
padding: 10px;
}
.media:after {
display: table;
clear: both;
content: " ";
}
.media-image-container {
float: left;
margin-right: 10px;
}
.media-image {
display: block;
}
.media-body {
overflow: hidden;
}
.media-shadow {
box-shadow: 1px 1px 3px rgba(0, 0, 0, .5);
}
上面这段代码用media
表示了这种图文排列的页面元素。如果把构成它的html、css及javascript(如果有)看做一个整体,那就相当于这是一个元件,或者说对象(object)。它可以在站点的任何地方被重用。
这样是如何体现OOCSS的两个原则的呢?
分离结构和主题是在于将一些视觉样式效果(例如background
、color
)作为单独的“主题”来应用。在上面的例子中的阴影效果,没有被直接写在media
的样式规则内,而是被单独写在了一个名为media-shadow的class中。因此,它成为了可选择、可拆分的主题。如果不需要对应主题,什么也不要加,如果需要,加上对应的class,就是这样的思路。
分离容器和内容要求使页面元素不依赖于其所处位置。在上面的例子中,css的选择符都很短,无继承选择符(例如.header
.media`` { }
),所以,这个图文排列的元件,可以在任何地方使用,且会有一致的外观。
如果需要在特定的地方让这个元件看起来不一样一些,继续为这个元件增加class,将“不一样的部分”作为可配置的选项。元件的外观仍不依赖其所处位置。
可以看出,OOCSS风格的css可以描述为两点:
OOCSS追求元件的复用,其class命名比较抽象,一般不体现具体内容。
SMACSS提出的css理论。其主要原则有3条:
这些原则分别是什么意思呢?
这一点是SMACSS的核心。SMACSS认为css有5个类别,分别是:
Base Rules, 基础样式,描述的是任何场合下,页面元素的默认外观。它的定义不会用到class和ID。css reset也属于此类。
Layout Rules, 布局样式。它和后面的Module Rules一同,描述的是页面中的各类具体元素。元素是有层次级别之分的,Layout Rules属于较高的一层,它可以作为层级较低的Module Rules元素的容器。左右分栏、栅格系统等都属于布局样式。
Module Rules, 模块样式。它可以是一个产品列表,一个导航条。一般来说,Module Rules定义的元素放置于前面说的Layout Rules元素之内。模块是独立的,可以在各种场合重用。
State Rules, 状态样式,描述的是任一元素在特定状态下的外观。例如,一个消息框可能有success
和error
两种状态,导航条中的任一项都可能有current
状态。
继续OOCSS中的例子,下面新增的让元素不显示的is-hidden
就属于State Rules:
<div class="media media-shadow is-hidden">
...
</div>
.is-hidden{
display: none;
}
Theme Rules, 主题样式,描述了页面主题外观,一般是指颜色、背景图。Theme Rules可以修改前面4个类别的样式,且应和前面4个类别分离开来(便于切换,也就是“换肤”)。SMACSS的Theme Rules不要求使用单独的class命名,也就是说,你可以在Module Rules中定义.mod { }
然后在Theme Rules中也用.mod { }
来定义需要修改的部分。
Naming Rules是说在想class等的命名时,考虑用命名体现样式对应的类别。
按照前面5种的划分,Layout Rules用l-
或layout-
这样的前缀,例如:.l-header
、.l-sidebar
。
Module Rules用模块本身的命名,例如图文排列的.media
、.media-image
。
State Rules用is-前缀,例如:.is-active
、.is-hidden
。
Theme Rules如果作为单独class,用theme-前缀,例如.theme-a-background
、.theme-a-shadow
。
Base Rules不会用到class和ID,是以标签选择符为主的样式,例如p
、a
,无需命名。
命名规则不需要严格遵守,可以根据实际情况和自身喜好做其他的约定。记录自己的约定(写文档),然后遵守,就是可行的。
字面翻译是最小化适配深度。通过一个简单的描述来说明:
/* depth 1 */
.sidebar ul h3 { }
/* depth 2 */
.sub-title { }
上下两端css的区别在于html和css的耦合度。可以想到,由于上面的样式规则使用了继承选择符,因此对于html的结构实际是有一定依赖的。如果把h3元素搬到另一个位置,就有可能不再具有这些样式。对应的,下面的样式规则只有一个选择符,因此不依赖于特定html结构,只要为元素添加class,就可以获得对应样式。
当然,继承选择符是有用的,它可以减少因相同命名引发的样式冲突(常发生于多人协作开发)。但是,我们不应过度使用,在不造成样式冲突的允许范围之内,尽可能使用短的、不限定html结构的选择符。这就是SMACSS的最小化适配深度的意义。
看起来,这一点和OOCSS的分离容器和内容的原则非常相似。
SMACSS着力于实现两个主要目标:
<?php
abstract class Father {
function meth1() {
echo "meth1...<br>";
}
abstract function meth2();
public $var1="var1";
public static $var2="var2";
const Var3="Var3";
}
class Son extends Father {
function meth2() {
echo "meth2 of Son...<br>";
}
}
$s=new Son();
echo $s->var1."<br>";
echo Father::$var2."<br>";
echo Father::Var3."<br>";
Interface IFather {
//public $iVar1="iVar1"; 此处接口定义中不能包含成员变量
//public static $iVar2="iVar2"; 此处接口定义中不能包含静态变量
const iVar3="iVar3";
function iMeth1();
}
Class ISon implements IFather {
function iMeth1() {
echo "iMeth1...<br>";
}
}
$is=new ISon();
echo IFather::iVar3;
?>
一个commit message由header,body和footer组成.这里的header有特殊的格式,它含有type, scope和subject:
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
这个header是必须要有的,scope是可选的
如果这个commit是revert一个之前的提交,那么它应该是以revert:
开头,然后是之前commit的header,加上之前commit的hash
它必须是下列中的某一个
这里的scope可以是模块名或者项目中的组件名称
这个subject包含这个提交的简短的描述
change
,而不是changed
或者changes
.
<?php
abstract class Father {
function meth1() {
echo "meth1...<br>";
}
abstract function meth2();
public $var1="var1";
public static $var2="var2";
const Var3="Var3";
}
class Son extends Father {
function meth2() {
echo "meth2 of Son...<br>";
}
}
$s=new Son();
echo $s->var1."<br>";
echo Father::$var2."<br>";
echo Father::Var3."<br>";
Interface IFather {
//public $iVar1="iVar1"; 此处接口定义中不能包含成员变量
//public static $iVar2="iVar2"; 此处接口定义中不能包含静态变量
const iVar3="iVar3";
function iMeth1();
}
Class ISon implements IFather {
function iMeth1() {
echo "iMeth1...<br>";
}
}
$is=new ISon();
echo IFather::iVar3;
?>
前端的视图层和数据层有时需要实现双向绑定(two-way-binding),例如mvvm框架,数据驱动视图,视图状态机等,研究了几个目前主流的数据双向绑定框架,总结了下。目前实现数据绑定主要有以下三种。
比较老的实现方式,有点像观察者编程模式,主要思路是通过在数据对象上定义get和set方法(当然还有其它方法),调用时手动调用get或set数据,改变数据后触发UI层的渲染操作;以视图驱动数据变化的场景主要应用于input、select、textarea等元素,当UI层变化时,通过监听dom的change,keypress,keyup等事件来触发事件改变数据层的数据。整个过程均通过函数调用完成。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>data-binding-method-set</title>
</head>
<body>
<input q-value="value" type="text" id="input">
<div q-text="value" id="el"></div>
<script>
var elems = [document.getElementById('el'), document.getElementById('input')];
var data = {
value: 'hello!'
};
var command = {
text: function(str){
this.innerHTML = str;
},
value: function(str){
this.setAttribute('value', str);
}
};
var scan = function(){
/**
* 扫描带指令的节点属性
*/
for(var i = 0, len = elems.length; i < len; i++){
var elem = elems[i];
elem.command = [];
for(var j = 0, len1 = elem.attributes.length; j < len1; j++){
var attr = elem.attributes[j];
if(attr.nodeName.indexOf('q-') >= 0){
/**
* 调用属性指令,这里可以使用数据改变检测
*/
command[attr.nodeName.slice(2)].call(elem, data[attr.nodeValue]);
elem.command.push(attr.nodeName.slice(2));
}
}
}
}
/**
* 设置数据后扫描
*/
function mvSet(key, value){
data[key] = value;
scan();
}
/**
* 数据绑定监听
*/
elems[1].addEventListener('keyup', function(e){
mvSet('value', e.target.value);
}, false);
scan();
/**
* 改变数据更新视图
*/
setTimeout(function(){
mvSet('value', 'fuck');
},1000)
</script>
</body>
</html>
以典型的mvvm框架angularjs为代表,angular通过检查脏数据来进行UI层的操作更新。关于angular的脏检测,有几点需要了解:
脏检测如何去做,主要是通过设置的数据去找与该数据相关的所有元素,然后再比较数据变化,如果变化则进行指令操作
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>data-binding-drity-check</title>
</head>
<body>
<input q-event="value" ng-bind="value" type="text" id="input">
<div q-event="text" ng-bind="value" id="el"></div>
<script>
var elems = [document.getElementById('el'), document.getElementById('input')];
var data = {
value: 'hello!'
};
var command = {
text: function(str) {
this.innerHTML = str;
},
value: function(str) {
this.setAttribute('value', str);
}
};
var scan = function(elems) {
/**
* 扫描带指令的节点属性
*/
for (var i = 0, len = elems.length; i < len; i++) {
var elem = elems[i];
elem.command = {};
for (var j = 0, len1 = elem.attributes.length; j < len1; j++) {
var attr = elem.attributes[j];
if (attr.nodeName.indexOf('q-event') >= 0) {
/**
* 调用属性指令
*/
var dataKey = elem.getAttribute('ng-bind') || undefined;
/**
* 进行数据初始化
*/
command[attr.nodeValue].call(elem, data[dataKey]);
elem.command[attr.nodeValue] = data[dataKey];
}
}
}
}
/**
* 脏循环检测
* @param {[type]} elems [description]
* @return {[type]} [description]
*/
var digest = function(elems) {
/**
* 扫描带指令的节点属性
*/
for (var i = 0, len = elems.length; i < len; i++) {
var elem = elems[i];
for (var j = 0, len1 = elem.attributes.length; j < len1; j++) {
var attr = elem.attributes[j];
if (attr.nodeName.indexOf('q-event') >= 0) {
/**
* 调用属性指令
*/
var dataKey = elem.getAttribute('ng-bind') || undefined;
/**
* 进行脏数据检测,如果数据改变,则重新执行指令,否则跳过
*/
if(elem.command[attr.nodeValue] !== data[dataKey]){
command[attr.nodeValue].call(elem, data[dataKey]);
elem.command[attr.nodeValue] = data[dataKey];
}
}
}
}
}
/**
* 初始化数据
*/
scan(elems);
/**
* 可以理解为做数据劫持监听
*/
function $digest(value){
var list = document.querySelectorAll('[ng-bind='+ value + ']');
digest(list);
}
/**
* 输入框数据绑定监听
*/
if(document.addEventListener){
elems[1].addEventListener('keyup', function(e) {
data.value = e.target.value;
$digest(e.target.getAttribute('ng-bind'));
}, false);
}else{
elems[1].attachEvent('onkeyup', function(e) {
data.value = e.target.value;
$digest(e.target.getAttribute('ng-bind'));
}, false);
}
setTimeout(function() {
data.value = 'fuck';
/**
* 这里问啥还要执行$digest这里关键的是需要手动调用$digest方法来启动脏检测
*/
$digest('value');
}, 2000)
</script>
</body>
</html>
第三种方法则是avalon等框架使用的数据劫持方式。基本思路是使用Object.defineProperty对数据对象做属性get和set的监听,当有数据读取和赋值操作时则调用节点的指令,这样使用最通用的=等号赋值就可以了。具体实现如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>data-binding-hijacking</title>
</head>
<body>
<input q-value="value" type="text" id="input">
<div q-text="value" id="el"></div>
<script>
var elems = [document.getElementById('el'), document.getElementById('input')];
var data = {
value: 'hello!'
};
var command = {
text: function(str) {
this.innerHTML = str;
},
value: function(str) {
this.setAttribute('value', str);
}
};
var scan = function() {
/**
* 扫描带指令的节点属性
*/
for (var i = 0, len = elems.length; i < len; i++) {
var elem = elems[i];
elem.command = [];
for (var j = 0, len1 = elem.attributes.length; j < len1; j++) {
var attr = elem.attributes[j];
if (attr.nodeName.indexOf('q-') >= 0) {
/**
* 调用属性指令
*/
command[attr.nodeName.slice(2)].call(elem, data[attr.nodeValue]);
elem.command.push(attr.nodeName.slice(2));
}
}
}
}
var bValue;
/**
* 定义属性设置劫持
*/
var defineGetAndSet = function(obj, propName) {
try {
Object.defineProperty(obj, propName, {
get: function() {
return bValue;
},
set: function(newValue) {
bValue = newValue;
scan();
},
enumerable: true,
configurable: true
});
} catch (error) {
console.log("browser not supported.");
}
}
/**
* 初始化数据
*/
scan();
/**
* 可以理解为做数据劫持监听
*/
defineGetAndSet(data, 'value');
/**
* 数据绑定监听
*/
if(document.addEventListener){
elems[1].addEventListener('keyup', function(e) {
data.value = e.target.value;
}, false);
}else{
elems[1].attachEvent('onkeyup', function(e) {
data.value = e.target.value;
}, false);
}
setTimeout(function() {
data.value = 'fuck';
}, 2000)
</script>
</body>
</html>
但值得注意的是defineProperty支持IE8以上的浏览器,这里可以使用_defineGetter_和_defineSetter_来做兼容但是浏览器兼容性的原因,直接用defineProperty就可以了。至于IE8浏览器仍需要使用其它方法来做hack。如下代码可以对IE8进行hack,defineProperty支持IE8。例如使用es5-shim.js就可以了。(IE8以下浏览器忽略)
Flex Container
display: flex; // 设置布局为 flex 布局
flex-direction: row; // 设置主轴方向
flex-wrap: nowrap; // 设置一行显示不完时是否通过多行显示来显示所有 item
flex-flow: row nowrap; // 可以使用该属性分别指定 flex-direction flex-wrap 的值
justify-content: space-around; // 设置主轴方向的对齐方式,默认值为 flex-start, space-between 在 item 间添加间隔, space-around 在 item 两边添加间隔
align-items: center; // 设置主轴垂直方向的对齐方式,默认值为 stretch 表示主轴垂直方向上拉伸的意思
align-content: center; // 设置多行显示时主轴垂直方向的对齐方式
Flex Item
flex-grow: 1; // item 占用主轴方向剩余空间的比例,默认值为0
flex-shrink: 2; // 设置 item 在主轴方向缩小的宽度,默认值为 1,越大表示越容易缩小,0 表示不会缩小
flex-basis: 100px; // 设置 item 的初始宽度,默认为 auto
flex: 0 1 auto; // 可以使用该属性分别指定 flex-grow flex-shrink flex-basis 的值
align-self: center; // 单独设置 item 在主轴垂直方向的对齐方式。stretch 表示在主轴垂直方向上拉伸当前 item
TypeScript 由 Microsoft(算上 Angular 2 的话加上 Google)开发和维护的一种开源编程语言。 它支持 JavaScript 的所有语法和语义,同时通过作为 ECMAScript 的超集来提供一些额外的功能,如类型检测和更丰富的语法。下图显示了 TypeScript 与 ES5,ES2015,ES2016 之间的关系。
JavaScript 是一门弱类型语言,变量的数据类型具有动态性,只有执行时才能确定变量的类型,这种后知后觉的认错方法会让开发者成为调试大师,但无益于编程能力的提升,还会降低开发效率。TypeScript 的类型机制可以有效杜绝由变量类型引起的误用问题,而且开发者可以控制对类型的监控程度,是严格限制变量类型还是宽松限制变量类型,都取决于开发者的开发需求。添加类型机制之后,副作用主要有两个:增大了开发人员的学习曲线,增加了设定类型的开发时间。总体而言,这些付出相对于代码的健壮性和可维护性,都是值得的。
此外,类型注释是 TypeScript 的内置功能之一,允许文本编辑器和 IDE 可以对我们的代码执行更好的静态分析。 这意味着我们可以通过自动编译工具的帮助,在编写代码时减少错误,从而提高我们的生产力。
对 TypeScript 的简介到此,接下来对其特有的知识点进行简单概括总结,(网上很多教程实际上把 ES6, ES7 的知识点也算进 ts 的知识点了,当然这没错~)
一个保存字符串的文本,类型声明为 string。可以发现类型声明可大写也可小写,后文同理。
let name: string = 'muyy'
let name2: String = 'muyy'
boolean是 true 或 false 的值,所以 let isBool3: boolean = new Boolean(1) 就会编译报错,因为 new Boolean(1) 生成的是一个 Bool 对象。
let isBool1: boolean = false
let number: number = 10;
数组是 Array 类型。然而,因为数组是一个集合,我们还需要指定在数组中的元素的类型。我们通过 Array or type[] 语法为数组内的元素指定类型
let arr:number[] = [1, 2, 3, 4, 5];
let arr2:Array<number> = [1, 2, 3, 4, 5];
let arr3:string[] = ["1","2"];
let arr4:Array<string> = ["1","2"];
列出所有可用值,一个枚举的默认初始值是0。你可以调整一开始的范围:
enum Role {Employee = 3, Manager, Admin}
let role: Role = Role.Employee
console.log(role) // 3
any 是默认的类型,其类型的变量允许任何类型的值:
let notSure:any = 10;
let notSure2:any[] = [1,"2",false];
JavaScript 没有空值 Void 的概念,在 TypeScirpt 中,可以用 void 表示没有任何返回值的函数:
function alertName(): void {
console.log('My name is muyy')
}
我们可以给每个参数添加类型之后再为函数本身添加返回值类型。 TypeScript能够根据返回语句自动推断出返回值类型,因此我们通常省略它。下面函数 add, add2, add3 的效果是一样的,其中是 add3 函数是函数完整类型。
function add(x: string, y: string): string{
return "Hello TypeScript";
}
let add2 = function(x: string, y: string): string{
return "Hello TypeScript";
}
let add3: (x: string, y: string) => string = function(x: string, y: string): string{
return "Hello TypeScript";
}
JavaScript 里,每个参数都是可选的,可传可不传。 没传参的时候,它的值就是 undefined 。 在 TypeScript 里我们可以在参数名旁使用?实现可选参数的功能。 比如,我们想让 lastname 是可选的:
function buildName(firstName: string, lastname?: string){
console.log(lastname ? firstName + "" + lastname : firstName)
}
let res1 = buildName("鸣","人"); // 鸣人
let res2 = buildName("鸣"); // 鸣
let res3 = buildName("鸣", "人", "君"); // Supplied parameters do not match any signature of call target.
如果带默认值的参数出现在必须参数前面,用户必须明确的传入 undefined 值来获得默认值。 例如,我们重写上例子,让 firstName 是带默认值的参数:
function buildName2(firstName = "鸣", lastName?: string){
console.log(firstName + "" + lastName)
}
let res4 = buildName2("人"); // undefined人
let res5 = buildName2(undefined, "人"); // 鸣人
传统的JavaScript程序使用函数和基于原型的继承来创建可重用的组件,但对于熟悉使用面向对象方式的程序员来讲就有些棘手,因为他们用的是基于类的继承并且对象是由类构建出来的。 从ECMAScript 2015,也就是ECMAScript 6开始,JavaScript程序员将能够使用基于类的面向对象的方式。 使用TypeScript,我们允许开发者现在就使用这些特性,并且编译后的JavaScript可以在所有主流浏览器和平台上运行,而不需要等到下个JavaScript版本。
class Person{
name:string; // 这个是对后文this.name类型的定义
age:number;
constructor(name:string,age:number){
this.name = name;
this.age = age;
}
print(){
return this.name + this.age;
}
}
let person:Person = new Person('muyy',23)
console.log(person.print()) // muyy23
我们在引用任何一个类成员的时候都用了 this。 它表示我们访问的是类的成员。其实这本质上还是 ES6 的知识,只是在 ES6 的基础上多上了对 this 字段和引用参数的类型声明。
class Person{
public name:string; // public、private、static 是 typescript 中的类访问修饰符
age:number;
constructor(name:string,age:number){
this.name = name;
this.age = age;
}
tell(){
console.log(this.name + this.age);
}
}
class Student extends Person{
gender:string;
constructor(gender:string){
super("muyy",23);
this.gender = gender;
}
tell(){
console.log(this.name + this.age + this.gender);
}
}
var student = new Student("male");
student.tell(); // muyy23male
这个例子展示了 TypeScript 中继承的一些特征,可以看到其实也是 ES6 的知识上加上类型声明。不过这里多了一个知识点 —— 公共,私有,以及受保护的修饰符。TypeScript 里,成员默认为 public ;当成员被标记成 private 时,它就不能在声明它的类的外部访问;protected 修饰符与private 修饰符的行为很相似,但有一点不同,protected 成员在派生类中仍然可以访问。
TypeScript 支持通过 getters/setters 来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。
对于存取器有下面几点需要注意的:
首先,存取器要求你将编译器设置为输出 ECMAScript 5 或更高。 不支持降级到 ECMAScript 3。 其次,只带有 get 不带有 set 的存取器自动被推断为 readonly。 这在从代码生成 .d.ts 文件时是有帮助的,因为利用这个属性的用户会看到不允许够改变它的值。
class Hello{
private _name: string;
private _age: number;
get name(): string {
return this._name;
}
set name(value: string) {
this._name = value;
}
get age(): number{
return this._age;
}
set age(age: number) {
if(age>0 && age<100){
console.log("年龄在0-100之间"); // 年龄在0-100之间
return;
}
this._age = age;
}
}
let hello = new Hello();
hello.name = "muyy";
hello.age = 23
console.log(hello.name); // muyy
TypeScript的核心原则之一是对值所具有的结构进行类型检查。在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。
interface LabelValue{
label: string;
}
function printLabel(labelObj: LabelValue){
console.log(labelObj.label);
}
let myObj = {
"label":"hello Interface"
};
printLabel(myObj);
LabelledValue 接口就好比一个名字,它代表了有一个 label 属性且类型为 string 的对象。只要传入的对象满足上述必要条件,那么它就是被允许的。
另外,类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对的就可以。
带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个 ? 符号。可选属性的好处之一是可以对可能存在的属性进行预定义,好处之二是可以捕获引用了不存在的属性时的错误。
interface Person{
name?:string;
age?:number;
}
function printInfo(info:Person){
console.log(info);
}
let info = {
"name":"muyy",
"age":23
};
printInfo(info); // {"name": "muyy", "age": 23}
let info2 = {
"name":"muyy"
};
printInfo(info2); // {"name": "muyy"}
接口能够描述 JavaScript 中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型。定义的函数类型接口就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。定义后完成后,我们可以像使用其它接口一样使用这个函数类型的接口。
interface SearchFunc{
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string,subString: string){
return source.search(subString) !== -1;
};
console.log(mySearch("鸣人","鸣")); // true
console.log(mySearch("鸣人","缨")); // false
与使用接口描述函数类型差不多,我们也可以描述那些能够“通过索引得到”的类型,比如 a[10] 或 ageMap["daniel"]。 可索引类型具有一个索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。 让我们看如下例子:
interface StringArray{
[index: number]: string;
}
let MyArray: StringArray;
MyArray = ["是","云","随","风"];
console.log(MyArray[2]); // 随
与 C# 或 Java 里接口的基本作用一样,TypeScript 也能够用它来明确的强制一个类去符合某种契约。
我们可以在接口中描述一个方法,在类里实现它,如同下面的 setTime 方法一样:
interface ClockInterface{
currentTime: Date;
setTime(d: Date);
}
class Clock implements ClockInterface{
currentTime: Date;
setTime(d: Date){
this.currentTime = d;
}
constructor(h: number, m: number) {}
}
和类一样,接口也可以相互继承。 这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。
interface Shape{
color: string;
}
interface PenStroke{
penWidth: number;
}
interface Square extends Shape,PenStroke{
sideLength: number;
}
let s = <Square>{};
s.color = "blue";
s.penWidth = 100;
s.sideLength = 10;
TypeScript 与 ECMAScript 2015 一样,任何包含顶级 import 或者 export 的文件都被当成一个模块。
export interface StringValidator{
isAcceptable(s:string): boolean;
}
var strReg = /^[A-Za-z]+$/;
var numReg = /^[0-9]+$/;
export class letterValidator implements StringValidator{
isAcceptable(s:string): boolean{
return strReg.test(s);
}
}
export class zipCode implements StringValidator{
isAcceptable(s: string): boolean{
return s.length == 5 && numReg.test(s);
}
}
软件工程中,我们不仅要创建一致的定义良好的 API ,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。
在像 C# 和 Java 这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
如下代码,我们给 Hello 函数添加了类型变量 T ,T 帮助我们捕获用户传入的类型(比如:string)。我们把这个版本的 Hello 函数叫做泛型,因为它可以适用于多个类型。 代码中 output 和 output2 是效果是相同的,第二种方法更加普遍,利用了类型推论 —— 即编译器会根据传入的参数自动地帮助我们确定T 的类型:
function Hello<T>(arg:T):T{
return arg;
}
let outPut = Hello<string>('Hello Generic');
let output2 = Hello('Hello Generic')
console.log(outPut);
console.log(outPut2);
今天由于项目中引入的echarts的文件太大,requirejs经常加载超时,不得不分开来加载echarts的各个图表。但是使用echarts自带的在线构建工具生成的支持AMD 标准的模块报错,所以不得不使用echarts的全局函数,使用requirejs的shim进行加载。借此机会学习一下AMD, CMD, CommonJS和UMD各自的规范,和它们之间的区别。
在了解这些规范之前,还是先了解一下什么是模块化。
模块化是指在解决某一个复杂问题或者一系列的杂糅问题时,依照一种分类的思维把问题进行系统性的分解以之处理。模块化是一种处理复杂系统分解为代码结构更合理,可维护性更高的可管理的模块的方式。可以想象一个巨大的系统代码,被整合优化分割成逻辑性很强的模块时,对于软件是一种何等意义的存在。对于软件行业来说:解耦软件系统的复杂性,使得不管多么大的系统,也可以将管理,开发,维护变得“有理可循”。
还有一些对于模块化一些专业的定义为:模块化是软件系统的属性,这个系统被分解为一组高内聚,低耦合的模块。那么在理想状态下我们只需要完成自己部分的核心业务逻辑代码,其他方面的依赖可以通过直接加载被人已经写好模块进行使用即可。
首先,既然是模块化设计,那么作为一个模块化系统所必须的能力:
好了,**有了,那么总要有点什么来建立一个模块化的规范制度吧,不然各式各样的模块加载方式只会将局搅得更为混乱。那么在JavaScript中出现了一些非传统模块开发方式的规范 CommonJS的模块规范,AMD(Asynchronous Module Definition),CMD(Common Module Definition)等。
CommonJS是服务器端模块的规范,Node.js采用了这个规范。
根据CommonJS规范,一个单独的文件就是一个模块。加载模块使用require方法,该方法读取一个文件并执行,最后返回文件内部的exports对象。
例如:
// foobar.js
//私有变量
var test = 123;
//公有方法
function foobar () {
this.foo = function () {
// do someing ...
}
this.bar = function () {
//do someing ...
}
}
//exports对象上的方法和变量是公有的
var foobar = new foobar();
exports.foobar = foobar;
//require方法默认读取js文件,所以可以省略js后缀
var test = require('./boobar').foobar;
test.bar();
CommonJS 加载模块是同步的,所以只有加载完成才能执行后面的操作。像Node.js主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式,所以CommonJS规范比较适用。但如果是浏览器环境,要从服务器加载模块,这是就必须采用异步模式。所以就有了 AMD CMD 解决方案。
AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义".
AMD设计出一个简洁的写模块API:
define(id?, dependencies?, factory);
第一个参数 id 为字符串类型,表示了模块标识,为可选参数。若不存在则模块标识应该默认定义为在加载器中被请求脚本的标识。如果存在,那么模块标识必须为顶层的或者一个绝对的标识。
第二个参数,dependencies ,是一个当前模块依赖的,已被模块定义的模块标识的数组字面量。
第三个参数,factory,是一个需要进行实例化的函数或者一个对象。
通过参数的排列组合,这个简单的API可以从容应对各种各样的应用场景,如下所述。
define( {
add : function( x, y ){
return x + y ;
}
} );
define(["alpha"], function( alpha ){
return {
verb : function(){
return alpha.verb() + 1 ;
}
}
});
define({
users: [],
members: []
});
define("alpha", [ "require", "exports", "beta" ], function( require, exports, beta ){
export.verb = function(){
return beta.verb();
// or:
return require("beta").verb();
}
});
define(function(require, exports, module) {
var a = require('a'),
b = require('b');
exports.action = function() {};
} );
不考虑多了一层函数外,格式和Node.js是一样的:使用require获取依赖模块,使用exports导出API。
除了define外,AMD还保留一个关键字require。require 作为规范保留的全局标识符,可以实现为 module loader,也可以不实现。
require([module], callback)
AMD模块化规范中使用全局或局部的require函数实现加载一个或多个模块,所有模块加载完成之后的回调函数。
其中:
[module]:是一个数组,里面的成员就是要加载的模块;
callback:是模块加载完成之后的回调函数。
例如:加载一个math模块,然后调用方法 math.add(2, 3);
require(['math'], function(math) {
math.add(2, 3);
});
RequireJS 是一个前端的模块化管理的工具库,遵循AMD规范,它的作者就是AMD规范的创始人 James Burke。所以说RequireJS是对AMD规范的阐述一点也不为过。
RequireJS 的基本**为:通过一个函数来将所有所需要的或者说所依赖的模块实现装载进来,然后返回一个新的函数(模块),我们所有的关于新模块的业务代码都在这个函数内部操作,其内部也可无限制的使用已经加载进来的以来的模块。
<script data-main='scripts/main' src='scripts/require.js'></script>
那么scripts下的main.js则是指定的主代码脚本文件,所有的依赖模块代码文件都将从该文件开始异步加载进入执行。
define用于定义模块,RequireJS要求每个模块均放在独立的文件之中。按照是否有依赖其他模块的情况分为独立模块和非独立模块。
define({
method1: function(){},
method2: function(){}
});
也等价于
define(function() {
return {
method1: function(){},
method2: function(){}
}
});
define([ 'module1', 'module2' ], function(m1, m2) {
...
});
或者:
define(function(require) {
var m1 = require('module1'),
m2 = require('module2');
...
});
简单看了一下RequireJS的实现方式,其 require 实现只不过是提取 require 之后的模块名,将其放入依赖关系之中。
在require进行调用模块时,其参数与define类似。
require(['foo', 'bar'], function(foo, bar) {
foo.func();
bar.func();
} );
在加载 foo 与 bar 两个模块之后执行回调函数实现具体过程。
当然还可以如之前的例子中的,在define定义模块内部进行require调用模块
define(function(require) {
var m1 = require( 'module1' ),
m2 = require( 'module2' );
...
});
define 和 require 这两个定义模块,调用模块的方法合称为AMD模式,定义模块清晰,不会污染全局变量,清楚的显示依赖关系。AMD模式可以用于浏览器环境并且允许非同步加载模块,也可以按需动态加载模块。
官网 (http://www.requirejs.org/)
API (http://www.requirejs.org/docs/api.html)
CMD是SeaJS 在推广过程中对模块定义的规范化产出
对于依赖的模块AMD是提前执行,CMD是延迟执行。不过RequireJS从2.0开始,也改成可以延迟执行(根据写法不同,处理方式不通过)。
CMD推崇依赖就近,AMD推崇依赖前置。
//AMD
define(['./a','./b'], function (a, b) {
//依赖一开始就写好
a.test();
b.test();
});
//CMD
define(function (requie, exports, module) {
//依赖可以就近书写
var a = require('./a');
a.test();
...
//软依赖
if (status) {
var b = requie('./b');
b.test();
}
});
虽然 AMD也支持CMD写法,但依赖前置是官方文档的默认模块定义写法。
UMD是AMD和CommonJS的糅合
AMD模块以浏览器第一的原则发展,异步加载模块。
CommonJS模块以服务器第一原则发展,选择同步加载,它的模块无需包装(unwrapped modules)。
这迫使人们又想出另一个更通用的模式UMD (Universal Module Definition)。希望解决跨平台的解决方案。
UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。
在判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。
(function (window, factory) {
if (typeof exports === 'object') {
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
define(factory);
} else {
window.eventUtil = factory();
}
})(this, function () {
//module ...
});
今天写了一个正则替换一段文本的时候,使用了(.*)
,原本以为是匹配所有的字符,结果却不是,它是匹配换行符以外的任意字符,恰好我的那段文本里是一段一段的,折腾了好久,故总结下javascript的正则表达式
var reg = new RegExp('<%string%>', 'g');
var reg = /<%string%>/g
正则表达式让人望而却步的一个重要原因就是其转义字符太多了,组合非常之多,但是正则表达式的元字符(在正则表达式中具有特殊意义的专用字符,可以用来规定其前导字符)并不多
元字符:([{^$|)?*+.
并不是每个元字符都有其特定意义,在不同的组合中元字符有不同的意义,分类看一下
字符 | 含义 |
---|---|
\t | 水平制表符 |
\r | 回车符 |
\n | 换行符 |
\f | 换页符 |
\cX | 与X对应的控制字符(Ctrl + X) |
\v | 垂直制表符 |
\0 | 空字符 |
一般情况下正则表达式一个字符(转义字符算一个)对应字符串一个字符,比如ab\t
但是我们可以使用元字符[]来构建一个简单的类,所谓类是指,符合某些特征的对象,是一个泛指,而不是特指某个字符,我们可以使用表达式[abc]把字符a或b或c归为一类,表达式可以匹配这类的字符
元字符[]组合可以创建一个类,我们还可以使用元字符^创建反向类/负向类,反向类的意思是不属于XXX类的内容,表达式[^abc]表示不是字符a或b或c的内容
.
除了换行符之外的任意字符,等价于[^\n]按照上面的说明要是我们希望匹配单个数字那么表达式是这样的[0123456789]
如果是字母那怎么办。。。,好麻烦,正则表达式还提供了范围类,我们可以使用x-y来连接两个字符表示从x到y的任意字符,这是个闭区间,也就是说包含x和y本身,这样匹配小写字母就很简单了[a-z]
要是想匹配所有字母呢?在[]组成的类内部是可以连写得,我们还可以这样写[a-zA-Z]
刚才使用正则我们创建了几个类,来表示数字,字母等,但这样写也很麻烦,正则表达式为我们提供了几个常用的预定义类来匹配常见的字符
字符 | 等价类 | 含义 |
---|---|---|
. | [^\n\r] | 除了回车符和换行符之外的所有字符 |
\d | [0-9] | 数字字符 |
\D | [^0-9] | 非数字字符 |
\s | [ \t\n\x0B\f\r] | 空白符 |
\S | [^ \t\n\x0B\f\r] | 非空白符 |
\w | [a-zA-Z_0-9] | 单词字符(字母、数字、下划线) |
\W | [^a-zA-Z_0-9] | 非单词字符 |
有了这些预定义类,写一些正则就很方便了,比如我们希望匹配一个ab+数字+任意字符的字符串,就可以这样写了ab\d
正则表达式还提供了几个常用的边界匹配字符
字符 | 含义 |
---|---|
^ | 以xx开头 |
$ | 以xx结尾 |
\b | 单词边界,指[a-zA-Z_0-9]之外的字符 |
\B | 非单词边界 |
看个不负责任的邮箱正则匹配\w+@\w+\.(com)$
之前我们介绍的方法都是一一匹配的,如果我们希望匹配一个连续出现20次数字的字符串,难道我们需要写成 这样\d\d\d\d...
为此正则表达式引入了一些量词
字符 | 含义 |
---|---|
? | 出现零次或一次(最多出现一次) |
+ | 出现一次或多次(至少出现一次) |
* | 出现零次或多次(任意次) |
{n} | 出现n次 |
{n,m} | 出现n到m次 |
{n,} | 至少出现n次 |
看了上面介绍的量词,也许爱思考的同学会想到关于匹配原则的一些问题,比如{3,5}这个量词,要是在句子种出现了十次,那么他是每次匹配三个还是五个,反正3、4、5都满足3~5的条件,量词在默认下是尽可能多的匹配的,也就是大家常说的贪婪模式
`'123456789'.match(/\d{3,5}/g); //["12345", "6789"] `
既然有贪婪模式,那么肯定会有非贪婪模式,让正则表达式尽可能少的匹配,也就是说一旦成功匹配不不再继续尝试,做法很简单,在量词后加上 ? 即可
`'123456789'.match(/\d{3,5}?/g); //["123", "456", "789"]`
Gulp 的定位是 Task Runner, 就是用来跑一个一个任务的。
放在以前比如我想用sass写css, coffee写js, 我必须手动的用相应的compiler去编译各自的文件,然后各自minify。这时候designer给你了两张新图片,好嘞,接着用自己的小工具手动去压缩图片。
后来前端人不能忍了,搞出个自动化这个流程的 Grunt/Gulp, 比如你写完代码后要想发布production版本,用一句 gulp build
就可以
但是它没发解决的是 js module 的问题,是你写代码时候如何组织代码结构的问题.
之前大家可以用 require.js, sea.js 来 require dependency, 后来出了一个 webpack 说 我们能不能把所有的文件(css, image, js) 都用 js 来 生成依赖,最后生成一个bundle呢? 所以webpack 也叫做file bundler.
同时 webpack 为了解决可以 require 不同文件的需求引入了loader, 比如面对sass文件有
本来这就是 webpack 的初衷,require everything, bundle everything. 一开始 webpack 刚出来的时候大家都是把它结合着 gulp 一起用的, gulp 里面有个 gulp-webpack,就是让 webpack 专门去做module dependency的事情, 生成一个bundle.js文件,然后再用 gulp 去做一些其他杂七杂八minify, uglify的事情。 后来人们发现 webpack 有个plugins的选项, 可以用来进一步处理经过loader 生成的bundle.js,于是有人写了对应的插件, 所以minify/uglify, 生成hash的工作也可以转移到webpack本身了,挤掉了gulp这部分的市场份额。 再后来大家有发现 npm/package.json 里面的scripts 原来好好用啊,调用任务的时候就直接写一个简单的命令,因为 gulp 也不就是各种插件命令的组合呀,大部分情况下越来越不需要 gulp/grunt 之类的了 ref. 所以你现在看到的很多新项目都是package.json里面scripts 写了一堆,外部只需要一个webpack就够了。
Grunt和Gulp属于一类的都是构建工具,只是Grunt是根据配置来的,Gulp是采用代码优于配置的原则,Gulp的性能要比Grunt的性能要高。
~
会匹配最近的小版本依赖包,比如~1.2.3会匹配所有1.2.x版本,但是不包括1.3.0^
会匹配最新的大版本依赖包,比如^1.2.3会匹配所有1.x.x的包,包括1.3.0,但是不包括2.0.0*
这意味着安装最新版本的依赖包推荐使用~,只会修复版本的bug,比较稳定
使用^ ,有的小版本更新后会引入新的问题导致项目不稳定
chunkhash
也改变的问题// server.js
var express = require('express');
var compress = require('compression');
var app = express();
app.use(compress());
对于一般的SPA项目,如果node服务器的作用比较简单,比如只是做个接口转发之类的,很多人更倾向用Nginx作服务器,Nginx在转发接口、压缩、缓存等功能上更胜一筹。不过,大部分前端对Nginx应该陌生一些,为了实践技术,用熟悉的node即可,真正的项目部署,有专业的实施人员来搞。
设置Http Header里面缓存相关的字段,做进一步的优化。
express里面也有对静态资源相关的设置,只不过平时没怎么注意:
可以设置etag、maxAge等,进一步会有200缓存和304缓存的区别:
200 OK (from cache) 是浏览器没有跟服务器确认,直接用了浏览器缓存;而 304 Not Modified 是浏览器和服务器多确认了一次缓存的有效性,然后再使用的缓存。
相关的讨论可以参考 知乎:阿里云存储如何让浏览器始终以200 (from cache)缓存图片?
这种优化因问题而异吧,最重要的是善于使用Google DevTools里面的Performance面板和Memory面板去分析、查找问题,进而找到优化的点。
最近发布的 ECMAScript(ES6)新增内容很多,在 ES5 发布近 6 年(2009-11 至 2015-6)之后才将其标准化。两个发布版本之间时间跨度如此之大主要有两大原因:
因此,从 ECMAScript 2016(ES7)开始,版本发布将会变得更加频繁,每年发布一个新版本,这么一来新增内容也会更小。新版本将会包含每年截止时间之前完成的所有特性。
所有appDir中的文件都会先copy到dir文件中,进行压缩,然后根据build.js中的配置进行相应的合并,包括img等;
是的,还在
需要同步paths和shim配置
config.js里的baseUrl默认是相对于加载require.js的页面;
build.js里的appDir是相对于build.js所在的路径,baseUrl相对于appDir;
是的,可以,实际url=appDir+baseUrl+path
在build.js里的modules中进行相关配置,有配置的module会合并成一个js文件;
这个是由于r.js会把每个文件都定义成具名模块,引用的地方有些模块引用的是同一个js,就会报这个错
magic
作用域是程序中的一个标准,LESS中也是。如果你在你样式表的root级声明一个变量,它在整个文档中都是可以调用的。然而,如果你在一个选择器,比如id或者class中,重新定义了这个变量,那么,它就只能在这个选择器中可用了——当然是重新定义后的新值。
@color: #00c; /* 蓝色 */
#header {
@color: #c00; /* 红色 */
border: 1px solid @color; /* 红色边框 */
}
#footer {
border: 1px solid @color; /*蓝色边框 */
}
因为我们在#header中重新定义了color变量,变量的值将会是不同的而且只会在该选择器中有效。它之前或者之后的所有地方,如果没有被重新定义,都会保持那个原始的值。
作用域在Sass中稍有不同。在上面的代码中,当@color变量变为红色后,代码中,此处之后的该变量的值,将会被重写(成为红色)。
Yarn 是 Facebook, Google, Exponent 和 Tilde 开发的一款新的 JavaScript 包管理工具。就像我们可以从官方文档了解那样,它的目的是解决这些团队使用 npm 面临的少数问题,即:
它并没有试图完全取代 npm。Yarn 同样是一个从 npm 注册源获取模块的新的 CLI 客户端。注册的方式不会有任何变化 —— 你同样可以正常获取与发布包.
乍一看 Yarn 与 npm 很类似,但通过引擎的对比就能察觉 Yarn 的不同。
npm 和 Yarn 都使用 package.json 来跟踪项目的依赖,版本号并非一直准确,因为你可以定义版本号范围,这样你可以选择一个主版本和次要版本的包,但让 npm 安装最新的补丁也许可以修改一些 bug。
理想状态下使用语义化版本发布补丁不会包含大的变化,但不幸的是这必非真理。npm 的这种策略可能导致两台拥有相同 package.json 文件的机子安装了不同版本的包,这可能导致一些错误。
为了避免包版本的错误匹配,一个确定的安装版本被固定在一个锁文件中。每次模块被添加时,Yarn 就会创建(或更新)yarn.lock 文件,这样你就可以保证其它机子也安装相同版本的包,同时包含了 package.json 中定义的一系列允许的版本。
在 npm 中同样可以使用 npm shrinkwrap 命令来生成一个锁文件,这样在使用 npm install 时会在读取 package.json 前先读取这个文件,就像 Yarn 会先读取yarn.lock 一样。这里的区别是 Yarn 总会自动更新 yarn.lock,而 npm 需要你重新操作。
除了一些功能差异,Yarn 命令也存在一些区别。例如移除或修改了一些 npm 命令以及添加了几个有趣的命令。
Yarn 被炒得这么火热会不会有问题?它正式发布当天就收到很多问题反馈,但官方处理问题的速度极快。这些表明社区正努力开发并修复bug。查看问题反馈的数量和类型可以发现 Yarn 在大多数用户的机子上表现的很稳定,但可能个别机子会有问题。
请注意虽然一个包管理器可能对你的项目非常重要,但它仅仅只是个工具,如果出了状况,恢复包不会困难,也并非要回归 npm。
一般看JQuery插件里的写法是这样的
(function($) {
//...
})(jQuery);
今天看到bootstrap的javascript组件是这样写的
!function( $ ){
//...
}( window.jQuery );
为什么要在前面加一个 " ! " 呢?
我们都知道,函数的声明方式以下这两种:
function fnA() {alert('msg');} //声明式定义函数
var fnB = function() {alert('msg');} //函数赋值表达式定义函数
但是,如果我们尝试为一个“定义函数”末尾加上(),解析器是无法理解的。
function msg() {
alert('message');
}(); //解析器是无法理解的
定义函数的调用方式应该是 msg() ;
如果将函数体部分用()包裹起来就可以运行并且解析器是不报错的,如:
(function($) {
//...
})(jQuery);
原来,使用括号包裹定义函数体,解析器将会以函数表达式的方式去调用定义函数。也就是说,任何能将函数变成一个函数表达式的作法,都可以使解析器正确的调用定义函数。而 ! 就是其中一个,而 + - || 都有这样的功能。
另外,用 ! 可能更多的是一个习惯问题,不同的运算符,性能是不同的。
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.