Giter VIP home page Giter VIP logo

tonyzheng1990.github.io's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

tonyzheng1990.github.io's Issues

深度使用 JSON.stringify()

深度使用 JSON.stringify()

按照 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 函数

stringify 函数的定义为 JSON.stringify(value [, replacer [, space]])

后面还带有我不常用两个可选参数 replacer 和 space

value 参数不多解释,replacer 其实就是一个自定义函数,可以改变 JSON.stringify 的行为,space 就是格式化输出,最大值为 10,非整数时取值为 1

stringify 输出 Function

本质上无论怎么改,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
}

stringify 格式化输出

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 方法

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"

Vue开发手机端页面

适配终端:

代码规范

Vue1.x库

Vue2.x库

Vue全家桶

单个组件

Vue2.0样例

参考

ES7/8 Cheat Sheet

ES2016(ES7)新特性

1.Array.prototype.includes
2.Exponentiation Operator(求冥运算)

参考

ES2017(ES8)新特性

1.Object.values/Object.entries
2.String padding(字符串填充)
3.Object.getOwnPropertyDescriptors
4.函数参数列表和调用中的尾逗号(Trailing commas)

async/await

字符串填充

  • str.padStart(targetLength [, padString])
  • str.padEnd(targetLength [, padString])

values和entries函数

  • Object.values
  • Object.entries

getOwnPropertyDescriptors函数

结尾逗号

参考

JavaScript中call和apply的区别

  1. call和apply的定义
  • call方法

    • 语法: call(thisObj[, arg1[, arg2[, ...[, argN]]]])
    • 定义: 调用一个对象的一个方法,以另一个对象替换当前对象
    • 说明: call方法可以用来代替另一个对象调用一个方法。call方法可将一个函数的对象上下文从初始的上下文改变为由thisObj指定的新对象。
  • apply方法

    • 语法: apply(thisObj, [, argArray])
    • 定义: 应用某一个对象的一个方法,用另一个对象替换当前对象
    • 说明: 如果argArray不是一个有效的数组或者不是一个arguments对象,那么将导致一个TypeError。如果没有提供argArray和thisObj任何一个参数,那么Global对象将被用作thisObj, 并且无法被传递任何参数。
  1. 代码示例
  • 基本使用

    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中的Directive

Vue用了也有很长时间了,一直在思考一个问题,Vue中的directive跟component到底应该应用在什么场景下.Angular中有directive,因为它没有component的概念,很多对于通用组件的封装都会用directive来实现,里面可以操作dom等.所以我在想是不是Vue中对于这两个的区别就是对DOM操作的都是写成一个directive,没有对dom进行操作的就写成一个component.其实我看directive能做的事情,component都能完成,所以我还是觉得directive没什么必要.React中就没有directive.之后看了几篇文章,里面也说directive不是vue中很重要的一部分.

参考

多进程、多线程和协程

  • 多线程:依然是一个进程,一个进程只能用一个核来跑,所以单进程多线程,只能把一个核的内力吸尽。
  • 多进程:可以把多核 CPU 吸尽。
  • 协程:虽然很厉害,对资源的利用最高效,但依然是一个单线程的进程,因此只能利用 CPU 的那一个核,因为不需要线程的切换,而且还能实现 IO 非阻塞,所以是对资源利用度最高的一种方式。

CSS命名规范(BEM, OOCSS, SMACSS)

BEM

BEM的意思就是块(block)、元素(element)、修饰符(modifier),是由Yandex团队提出的一种前端命名方法论。这种巧妙的命名方法让你的CSS类对其他开发者来说更加透明而且更有意义。BEM命名约定更加严格,而且包含更多的信息,它们用于一个团队开发一个耗时的大项目。

重要的是要注意,我使用的基于BEM的命名方式是经过Nicolas Gallagher修改过的。这篇文章中介绍的这种命名技术并不是原始的BEM,但却是一个我更喜欢的改进版。无论实际使用了什么样的符号,它们其实都是基于同样的BEM原则。

命名约定的模式如下:

.block{}  
.block__element{}  
.block--modifier{}  
  • .block 代表了更高级别的抽象或组件。
  • .block__element 代表.block的后代,用于形成一个完整的.block的整体。
  • .block--modifier代表.block的不同状态或不同版本。

之所以使用两个连字符和下划线而不是一个,是为了让你自己的块可以用单个连字符来界定,如:

.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 (Bootstrap)

[OOCSS](Object Oriented CSS),字面意思是面向对象的CSS,是由Nicole Sullivan提出的css理论,其主要的两个原则是:

  • Separate structure and skin(分离结构和主题)
  • Separate container and content(分离容器和内容)

用一个例子来说明。请看下面这样的图文排列:

media

<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的两个原则的呢?

Separate structure and skin

分离结构和主题是在于将一些视觉样式效果(例如backgroundcolor)作为单独的“主题”来应用。在上面的例子中的阴影效果,没有被直接写在media的样式规则内,而是被单独写在了一个名为media-shadow的class中。因此,它成为了可选择、可拆分的主题。如果不需要对应主题,什么也不要加,如果需要,加上对应的class,就是这样的思路。

Separate container and content

分离容器和内容要求使页面元素不依赖于其所处位置。在上面的例子中,css的选择符都很短,无继承选择符(例如.header .media`` { }),所以,这个图文排列的元件,可以在任何地方使用,且会有一致的外观。

如果需要在特定的地方让这个元件看起来不一样一些,继续为这个元件增加class,将“不一样的部分”作为可配置的选项。元件的外观仍不依赖其所处位置。

操作指南

可以看出,OOCSS风格的css可以描述为两点:

  • 增加class
  • 不使用继承选择符

OOCSS追求元件的复用,其class命名比较抽象,一般不体现具体内容。

SMACSS (Semantic UI)

SMACSS提出的css理论。其主要原则有3条:

  • Categorizing CSS Rules(为css分类)
  • Naming Rules(命名规则)
  • Minimizing the Depth of Applicability(最小化适配深度)

这些原则分别是什么意思呢?

Categorizing CSS Rules

这一点是SMACSS的核心。SMACSS认为css有5个类别,分别是:

  1. Base
  2. Layout(Major Components)
  3. Module(Minor Components)
  4. State
  5. Theme

Base Rules, 基础样式,描述的是任何场合下,页面元素的默认外观。它的定义不会用到class和ID。css reset也属于此类。

Layout Rules, 布局样式。它和后面的Module Rules一同,描述的是页面中的各类具体元素。元素是有层次级别之分的,Layout Rules属于较高的一层,它可以作为层级较低的Module Rules元素的容器。左右分栏、栅格系统等都属于布局样式。

Module Rules, 模块样式。它可以是一个产品列表,一个导航条。一般来说,Module Rules定义的元素放置于前面说的Layout Rules元素之内。模块是独立的,可以在各种场合重用。

State Rules, 状态样式,描述的是任一元素在特定状态下的外观。例如,一个消息框可能有successerror两种状态,导航条中的任一项都可能有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

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,是以标签选择符为主的样式,例如pa,无需命名。

命名规则不需要严格遵守,可以根据实际情况和自身喜好做其他的约定。记录自己的约定(写文档),然后遵守,就是可行的。

Minimizing the Depth of Applicability

字面翻译是最小化适配深度。通过一个简单的描述来说明:

/* depth 1 */
.sidebar ul h3 { }

/* depth 2 */
.sub-title { }

上下两端css的区别在于html和css的耦合度。可以想到,由于上面的样式规则使用了继承选择符,因此对于html的结构实际是有一定依赖的。如果把h3元素搬到另一个位置,就有可能不再具有这些样式。对应的,下面的样式规则只有一个选择符,因此不依赖于特定html结构,只要为元素添加class,就可以获得对应样式。

当然,继承选择符是有用的,它可以减少因相同命名引发的样式冲突(常发生于多人协作开发)。但是,我们不应过度使用,在不造成样式冲突的允许范围之内,尽可能使用短的、不限定html结构的选择符。这就是SMACSS的最小化适配深度的意义。

看起来,这一点和OOCSS的分离容器和内容的原则非常相似。

主要目标

SMACSS着力于实现两个主要目标:

  • 更语义化的html和css
  • 降低对特定html结构的依赖

常见的后端面试题

接口和抽象类的区别

  1. 对接口的使用是通过关键字implements,对抽象类的使用是通过关键字extends。
  2. 接口中不可以声明成员变量(包括类静态变量),但是可以声明类常量。抽象类中可以声明各种类型成员变量,实现数据的封装。
  3. 接口没有构造函数,抽象类可以有构造函数。
  4. 接口中的方法默认都是public类型的,而抽象类中的方法可以使用private,protected,public来修饰。
  5. 一个类可以同时实现多个接口,但一个类只能继承于一个抽象类。
<?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;  
?>  

Git提交规范

commit message 格式

一个commit message由header,body和footer组成.这里的header有特殊的格式,它含有type, scope和subject:

<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>

这个header是必须要有的,scope是可选的

Revert

如果这个commit是revert一个之前的提交,那么它应该是以revert:开头,然后是之前commit的header,加上之前commit的hash

Type

它必须是下列中的某一个

  • feat: 一个新的功能
  • fix: 修复一个bug
  • docs: 文档的改动
  • style: 不影响代码的改动 (white-space, formatting, missing semi-- colons, etc)
  • refactor: 不是修复某一个bug或者是增加某一个新功能的代码,是一些refine的代码
  • perf: 提高性能的代码
  • test: 增加缺少的或修复之前的tests的代码
  • chore: 构建过程的改动

Scope

这里的scope可以是模块名或者项目中的组件名称

Subject

这个subject包含这个提交的简短的描述

  • 使用祈使句,现在时,比如: change,而不是changed或者changes
  • 首字母小写
  • 末尾不要加.

常见的面试题

前端

阿里云

  1. 如何选择前端这个方向
  2. Vue双向数据绑定的实现
  3. react和vue有哪些不同 说说你对这两个框架的看法
  4. let和const的区别
  5. const类型的数组可以向其再push一个元素吗
  6. 平时用了es6的哪些特性,体验如何
  7. 浏览器原生支持module吗,如果支持,会带来哪些便利
  8. 介绍一下你对webpack的理解,和gulp有什么不同
  9. webpack打包速度慢,你觉得可能的原因是什么,该如何解决
  10. http响应中content-type包含哪些内容
  11. 浏览器缓存有哪些,通常缓存有哪几种方式
  12. 如何取出一个数组里的图片并按顺序显示出来
  13. 平时是怎么学新技术的
  14. Node,Koa用的怎么样
  15. 使用模块化加载时,模块加载的顺序是怎样的,如果不知道,根据已有的知识,你觉得顺序应该是怎么样的

蚂蚁金服一面

  1. 为什么选择前端这个方向,说一下自己的学习历程
  2. 介绍一下闭包和闭包常用场景
  3. 为什么会出现闭包这种东西,解决了什么问题
  4. 介绍一下你所了解的作用域链,作用域链的尽头是什么,为什么
  5. 一个Ajax建立的过程是怎样的,主要用到哪些状态码
  6. 说说你还知道的其他状态码,状态码的存在解决了什么问题
  7. 知道语义化吗?说说你理解的语义化,如果是你,平时会怎么做来保证语义化
  8. 说说content-box和border-box,为什么看起来content-box更合理,但是还是经常使用border-box
  9. 介绍一下你知道的浏览器缓存,分哪几种,通过什么方式实现,各有什么优缺点,有那些坑要注意

蚂蚁金服二面

  1. 自我介绍,我提到了对数据可视化很感兴趣
  2. 说说你对数据可视化的理解,和普通的WEB开发有什么不同
  3. 知道常用的数据结构有哪些
  4. 数据可视化的常用工具有哪些
  5. 数据分析和数据挖掘的常用方法有哪些
  6. 如果给你一个四维一亿条数据,如何找出其中关联性比较大的几条数据
  7. 如果让你设计一个展示人际关系网的可视化界面,你会怎么设计,依据是什么
  8. 如果让你设计一个展示某个地区包裹存留数量的可视化界面,你会怎么设计,依据是什么
  9. 如果要表示密集性,你觉得可以通过哪些方式来表示
  10. 如果要表示层次性,你觉得可以通过哪些方式来表示

蚂蚁金服三面

  1. 自我介绍,又提到了数据可视化
  2. 介绍一下你的学习历程和学习方法
  3. 介绍一下你的项目
  4. 你是怎么协调团队项目的,你觉得要注意哪些问题
  5. 你在团队项目中遇到过最大的难题是什么,怎么解决的
  6. 介绍一下HTML5的新特性,怎么理解这些新特性
  7. 平时关注新技术吗,通过哪些渠道,怎么看待新技术
  8. 任意连续的三个正整数相乘,一定能被6整除吗,为什么

CVTE一面

  1. 自我介绍,怎么学习,做了什么东西
  2. 介绍一下做过的项目
  3. [代码题]在一个UI李有10个li,实现点击对应的li,输出对应的下标
  4. 如果不用let应该怎么实现,写一下代码,为什么
  5. [代码题]实现三个DIV等分排布在一行(考察border-box)
  6. 为DIV设置的背景颜色默认会延伸到哪里

CVTE二面

  1. 说说你知道JavaScript的内存回收机制
  2. [代码题]给出一个绑定点击事件的innerHTML操作,让我讲讲有什么问题(函数防抖)
  3. [编程题]编程实现输出一个数组中第N大的数据

今日头条

  1. 介绍一下你的学习历程
  2. 介绍一下你做的项目
  3. Github上的一个轮播图组件是怎么实现的
  4. 实现两栏布局有哪些方法
  5. 设置width的flex元素,flex属性值是多少
  6. 平时用ES6吗?用了哪些特性
  7. 介绍一下你知道的浏览器的缓存
  8. 实际开发中,通常用那几个字段配合使用来达到缓存的目的
  9. get和post有什么不同,越多越好
  10. 常见的状态码有哪些,常出现在哪些具体的场景中
  11. cookie和session有什么联系和区别
  12. [编程题] 判断链表是否有环
  13. [编程题] 输出二叉树的最小深度

去哪儿一面

  1. 你做过什么项目吗?有什么作用?
  2. 让我给他现场演示和解释我做的一个组件
  3. 让我分析一下之前做过的一个项目的市场前景和实施的可行性
  4. 让我帮他解决一个问题(kindle使用微信推送书籍)

腾讯一面

  1. 自我介绍,为什么选这个方向
  2. 为什么会走上IT这个方向
  3. 平时是怎么学习的,学过哪些东西
  4. 介绍一下你简历上的项目
  5. 知道Vue的双向数据绑定是怎么实现的吗,和其他框架有什么不同
  6. [代码题]手写一个组合继承
  7. [代码题]深拷贝方案有哪些,手写一个深拷贝
  8. 判断数组有哪些方法,能够100%准确吗,100%准确的方法是哪个
  9. 跨域通信有哪些方案,各有什么不同
  10. JSONP的具体实现原理是什么,它是怎么工作的
  11. 多页面通信有哪些方案,各有什么不同
  12. 平时用了ES6哪些特性,体验如何
  13. 用Node干过什么,发布过自己的NPM包吗
  14. 用Node实现一个用户上传文件的后台服务应该怎么做

腾讯二面

  1. 自我介绍
  2. 介绍一下你做的项目
  3. 你做的最成功的项目是什么,为什么,你觉得你从中收获了什么
  4. 你觉得你做的最成功这个项目对你来说难度大吗,难在哪里
  5. 在团队协作中,你是怎么解决团队协调的问题的
  6. 你觉得你在团队中最核心的工作是什么,对这个团队有什么不可替代的贡献吗
  7. 跨域通信有哪些方案,说说你了解的
  8. 现在用的比较多的是什么方案,使用场景有什么差别
  9. 为什么要选用Vue这个框架,他有什么特点,与react和angluar有什么不同
  10. XSS和CSRF攻击知道吗,是什么原理,怎么检测,怎么防范,有什么区别
  11. HTMLEncode通常在哪个阶段做,可以被破解吗

后端

接口和抽象类的区别

  1. 对接口的使用是通过关键字implements,对抽象类的使用是通过关键字extends。
  2. 接口中不可以声明成员变量(包括类静态变量),但是可以声明类常量。抽象类中可以声明各种类型成员变量,实现数据的封装。
  3. 接口没有构造函数,抽象类可以有构造函数。
  4. 接口中的方法默认都是public类型的,而抽象类中的方法可以使用private,protected,public来修饰。
  5. 一个类可以同时实现多个接口,但一个类只能继承于一个抽象类。
<?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;  
?>  

sublime text 3 插件

JavaScript实现双向绑定的三种方式

前端数据的双向绑定方法

前端的视图层和数据层有时需要实现双向绑定(two-way-binding),例如mvvm框架,数据驱动视图,视图状态机等,研究了几个目前主流的数据双向绑定框架,总结了下。目前实现数据绑定主要有以下三种。

  1. 手动绑定

比较老的实现方式,有点像观察者编程模式,主要思路是通过在数据对象上定义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>
  1. 脏检查机制

以典型的mvvm框架angularjs为代表,angular通过检查脏数据来进行UI层的操作更新。关于angular的脏检测,有几点需要了解:

  • 脏检测机制并不是使用定时检测
  • 脏检测的时机是在数据发生变化时进行
  • angular对常用的dom事件,xhr事件等做了封装,在里面触发进入angular的digest流程
  • 在digest流程里面,会从rootscope开始遍历,检查所有的watcher

脏检测如何去做,主要是通过设置的数据去找与该数据相关的所有元素,然后再比较数据变化,如果变化则进行指令操作

<!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>
  1. 前端数据劫持(Hijacking)

第三种方法则是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以下浏览器忽略)

参考

Flexbox和Grid

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

参考

从 JavaScript 到 TypeScript

TypeScript 简介

TypeScript 由 Microsoft(算上 Angular 2 的话加上 Google)开发和维护的一种开源编程语言。 它支持 JavaScript 的所有语法和语义,同时通过作为 ECMAScript 的超集来提供一些额外的功能,如类型检测和更丰富的语法。下图显示了 TypeScript 与 ES5,ES2015,ES2016 之间的关系。

TypeScript

使用 TypeScript 的原因

JavaScript 是一门弱类型语言,变量的数据类型具有动态性,只有执行时才能确定变量的类型,这种后知后觉的认错方法会让开发者成为调试大师,但无益于编程能力的提升,还会降低开发效率。TypeScript 的类型机制可以有效杜绝由变量类型引起的误用问题,而且开发者可以控制对类型的监控程度,是严格限制变量类型还是宽松限制变量类型,都取决于开发者的开发需求。添加类型机制之后,副作用主要有两个:增大了开发人员的学习曲线,增加了设定类型的开发时间。总体而言,这些付出相对于代码的健壮性和可维护性,都是值得的。

此外,类型注释是 TypeScript 的内置功能之一,允许文本编辑器和 IDE 可以对我们的代码执行更好的静态分析。 这意味着我们可以通过自动编译工具的帮助,在编写代码时减少错误,从而提高我们的生产力。

对 TypeScript 的简介到此,接下来对其特有的知识点进行简单概括总结,(网上很多教程实际上把 ES6, ES7 的知识点也算进 ts 的知识点了,当然这没错~)

数据类型

String 类型

一个保存字符串的文本,类型声明为 string。可以发现类型声明可大写也可小写,后文同理。

let name: string = 'muyy'
let name2: String = 'muyy'

Boolean 类型

boolean是 true 或 false 的值,所以 let isBool3: boolean = new Boolean(1) 就会编译报错,因为 new Boolean(1) 生成的是一个 Bool 对象。

let isBool1: boolean = false

Number 类型

let number: number = 10;

Array 类型

数组是 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"];

Enums 类型

列出所有可用值,一个枚举的默认初始值是0。你可以调整一开始的范围:

enum Role {Employee = 3, Manager, Admin}
let role: Role = Role.Employee
console.log(role) // 3

Any 类型

any 是默认的类型,其类型的变量允许任何类型的值:

let notSure:any = 10;
let notSure2:any[] = [1,"2",false];

Void 类型

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);

参考资料

AMD, CMD, CommonJS和UMD

今天由于项目中引入的echarts的文件太大,requirejs经常加载超时,不得不分开来加载echarts的各个图表。但是使用echarts自带的在线构建工具生成的支持AMD 标准的模块报错,所以不得不使用echarts的全局函数,使用requirejs的shim进行加载。借此机会学习一下AMD, CMD, CommonJS和UMD各自的规范,和它们之间的区别。

Javascript模块化

在了解这些规范之前,还是先了解一下什么是模块化。

模块化是指在解决某一个复杂问题或者一系列的杂糅问题时,依照一种分类的思维把问题进行系统性的分解以之处理。模块化是一种处理复杂系统分解为代码结构更合理,可维护性更高的可管理的模块的方式。可以想象一个巨大的系统代码,被整合优化分割成逻辑性很强的模块时,对于软件是一种何等意义的存在。对于软件行业来说:解耦软件系统的复杂性,使得不管多么大的系统,也可以将管理,开发,维护变得“有理可循”。

还有一些对于模块化一些专业的定义为:模块化是软件系统的属性,这个系统被分解为一组高内聚,低耦合的模块。那么在理想状态下我们只需要完成自己部分的核心业务逻辑代码,其他方面的依赖可以通过直接加载被人已经写好模块进行使用即可。

首先,既然是模块化设计,那么作为一个模块化系统所必须的能力:

  1. 定义封装的模块。
  2. 定义新模块对其他模块的依赖。
  3. 可对其他模块的引入支持。

好了,**有了,那么总要有点什么来建立一个模块化的规范制度吧,不然各式各样的模块加载方式只会将局搅得更为混乱。那么在JavaScript中出现了一些非传统模块开发方式的规范 CommonJS的模块规范,AMD(Asynchronous Module Definition),CMD(Common Module Definition)等。

CommonJS

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和RequireJS

AMD

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

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方法调用模块

在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

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写法,但依赖前置是官方文档的默认模块定义写法。

  • AMD的API默认是一个当多个用,CMD严格的区分推崇职责单一。例如:AMD里require分全局的和局部的。CMD里面没有全局的 require,提供 seajs.use()来实现模块系统的加载启动。CMD里每个API都简单纯粹。

UMD

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正则表达式

今天写了一个正则替换一段文本的时候,使用了(.*),原本以为是匹配所有的字符,结果却不是,它是匹配换行符以外的任意字符,恰好我的那段文本里是一段一段的,折腾了好久,故总结下javascript的正则表达式

定义

  1. 构造函数
var reg = new RegExp('<%string%>', 'g');
  1. 字面量
var reg = /<%string%>/g
  • g:global,全文搜索,默认搜索到第一个结果就停止
  • i:ignore case,忽略大小写,默认大小写敏感
  • m: multiple lines,多行搜索(更改^和$的含义,使它们分别在任意一行对待行首和行尾匹配,而不仅仅在整个字符串的开头和结尾匹配)

元字符

正则表达式让人望而却步的一个重要原因就是其转义字符太多了,组合非常之多,但是正则表达式的元字符(在正则表达式中具有特殊意义的专用字符,可以用来规定其前导字符)并不多

元字符:([{^$|)?*+.

并不是每个元字符都有其特定意义,在不同的组合中元字符有不同的意义,分类看一下

预定义特殊字符

字符 含义
\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"]`

Grunt,Gulp和webpack的区别

Gulp 的定位是 Task Runner, 就是用来跑一个一个任务的。
放在以前比如我想用sass写css, coffee写js, 我必须手动的用相应的compiler去编译各自的文件,然后各自minify。这时候designer给你了两张新图片,好嘞,接着用自己的小工具手动去压缩图片。
后来前端人不能忍了,搞出个自动化这个流程的 Grunt/Gulp, 比如你写完代码后要想发布production版本,用一句 gulp build 就可以

  • rm 掉 dist文件夹中以前的旧文件
  • 自动把sass编译成css, coffee编译成js
  • 压缩各自的文件,压缩图片,生成图片sprite
  • 拷贝minified/uglified 文件到 dist 文件夹

但是它没发解决的是 js module 的问题,是你写代码时候如何组织代码结构的问题.

之前大家可以用 require.js, sea.js 来 require dependency, 后来出了一个 webpack 说 我们能不能把所有的文件(css, image, js) 都用 js 来 生成依赖,最后生成一个bundle呢? 所以webpack 也叫做file bundler.

同时 webpack 为了解决可以 require 不同文件的需求引入了loader, 比如面对sass文件有

  • sass-loader, 把sass 转换成 css
  • css-loader, 让 webpack 能识别处理 css
  • style-loader, 把识别后的 css 插入到 html style中
    类似的识别es6 有babel-loader

本来这就是 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的性能要高。

NPM依赖包版本号~和^和*的区别

  • ~ 会匹配最近的小版本依赖包,比如~1.2.3会匹配所有1.2.x版本,但是不包括1.3.0
  • ^ 会匹配最新的大版本依赖包,比如^1.2.3会匹配所有1.x.x的包,包括1.3.0,但是不包括2.0.0
  • * 这意味着安装最新版本的依赖包

推荐使用~,只会修复版本的bug,比较稳定

使用^ ,有的小版本更新后会引入新的问题导致项目不稳定

Webpack使用总结

如何修复app代码改变时,vendor的chunkhash也改变的问题

hash和chunkhash的区别

Webpack插件

Webpack3

参考

React实践和总结

前端性能优化的一些方法

减少HTTP请求

  • CSS/JS 合并打包
  • 小图标等用iconfont代替:作为单个DOM节点使用,可以设置大小、颜色等,非常便利。个人建议前端来维护这个字体包,每次有新增的图标,让设计师给我们对应的svg文件即可,前端自己去 https://icomoon.io/ 这个网站,导入原来的selection.json文件,增量生成新的css,无比方便。之前,我一直以为iconfont只能是单色的呢,其实也可以是多色的,svg里面多一些path而已,设计师会搞定的。生成字体后,前端正常引用即可(引用的时候,多色字体会多一些标签)(http://iconfont.cn/ 阿里云的)
  • 使用base64格式的图片:有些小图片,可能色彩比较复杂,这个时候再用iconfont就有点不合适了,此时可以将其转化为base64格式(不能缓存),直接嵌在src中,比如webpack的url-loader设置limit参数即可
  • 使用雪碧图:设置背景图尺寸大小,感觉很麻烦,而且雪碧图的维护也不怎么便利,好像使用率越来越低了,都被iconfont取代了

减少静态资源的体积

  • 压缩静态资源:合并打包的js、css文件体积一般会比较大,一些图片也会比较大,这个时候必须要压缩处理。前后端分离项目,不论是gulp还是webpack,都有相应的工具包。针对个别图片,有时候也可以单独拿出来处理,我个人经常使用这个网站 https://tinypng.com/ 在线压缩
  • 编写高效率的CSS:涉及到代码层面的优化比较多也比较细,不同水平的技术人员写出来的肯定不一样,这里不做进一步的分析。但是为什么要把CSS拿出来说一说呢?因为现在项目里面基本上都在使用CSS预处理器,Less、SaaS、Stylus等等,这导致了某些初级前端的滥用:嵌套5、6层,甚者能达到7、8层,吓死个人!嵌套这么深,影响浏览器查找选择器的速度不说,这也一定程度上产出了很多冗余的字节,这个要改、要提醒,一般建议嵌套3层即可。关于编写高效率的CSS,推荐一篇文章,《Writing efficient CSS selectors》
  • 服务端开启gzip压缩:大招,最近刚知晓,真是太牛逼了,一般的css、js文件能压缩60、70%,当然,这个比率可以设定的。前后端分离,如果前端部署用node、express作服务器的话,使用中间件compression即可开启gzip压缩:
// 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里面也有对静态资源相关的设置,只不过平时没怎么注意:

image

可以设置etag、maxAge等,进一步会有200缓存和304缓存的区别:

200 OK (from cache) 是浏览器没有跟服务器确认,直接用了浏览器缓存;而 304 Not Modified 是浏览器和服务器多确认了一次缓存的有效性,然后再使用的缓存。

相关的讨论可以参考 知乎:阿里云存储如何让浏览器始终以200 (from cache)缓存图片?

内存溢出

这种优化因问题而异吧,最重要的是善于使用Google DevTools里面的Performance面板和Memory面板去分析、查找问题,进而找到优化的点。

ES6(ES2015) Cheat Sheet

ECMAScript简介

最近发布的 ECMAScript(ES6)新增内容很多,在 ES5 发布近 6 年(2009-11 至 2015-6)之后才将其标准化。两个发布版本之间时间跨度如此之大主要有两大原因:

  • 比新版率先完成的特性,必须等待新版的完成才能发布。
  • 那些需要花长时间完成的特性,也顶着很大的压力被纳入这一版本,因为如果推迟到下一版本发布意味着又要等很久,这种特性也会推迟新的发布版本。

因此,从 ECMAScript 2016(ES7)开始,版本发布将会变得更加频繁,每年发布一个新版本,这么一来新增内容也会更小。新版本将会包含每年截止时间之前完成的所有特性。

设计阶段

  • 阶段 0:Strawman 初稿
  • 阶段 1:Proposal 建议
  • 阶段 2:Draft 草案
  • 阶段 3:Candidate 候选
  • 阶段 4:Finished 完成

ES2015(ES6)新特性

特性

  • arrows
  • classes
  • enhanced object literals
  • template strings
  • destructuring
  • default + rest + spread
  • let + const
  • iterators + for..of
  • generators
  • unicode
  • modules
  • module loaders
  • map + set + weakmap + weakset
  • proxies
  • symbols
  • subclassable built-ins
  • promises
  • math + number + string + array + object APIS
  • binary and octal literals
  • reflect api
  • tail calls

参考

转译工具

RequireJS和r.js

问与答

  1. 怎么处理require.js这些不需要被合并的东西

所有appDir中的文件都会先copy到dir文件中,进行压缩,然后根据build.js中的配置进行相应的合并,包括img等;

  1. 样式合并后原来单独的模块是不是也还在?

是的,还在

  1. config.js与build.js的同步问题

需要同步paths和shim配置

  1. baseUrl是相对谁来说的?build.js与config.js里的baseUrl对应关系?

config.js里的baseUrl默认是相对于加载require.js的页面;
build.js里的appDir是相对于build.js所在的路径,baseUrl相对于appDir;

  1. 当buildjs里有appDir时,相应的baseUrl可省略掉appDir这一层?

是的,可以,实际url=appDir+baseUrl+path

  1. 如何减少js请求数?即将某个js中所有require到的js都合并到这一个js中。

在build.js里的modules中进行相关配置,有配置的module会合并成一个js文件;

错误及解决办法

  • The following modules share the same URL. This could be a misconfiguration if that URL only has one anonymous module in it:

这个是由于r.js会把每个文件都定义成具名模块,引用的地方有些模块引用的是同一个js,就会报这个错

例子

参考

Sass和Less

Sass和Less在语法上的共性

  • 混合(Mixins):class中的class;
  • 参数混合(Parametric):可以像函数一样传递参数的class;
  • 嵌套规则(Nested Rules):class中嵌套class,从而减少重复的代码;
  • 运算(Operations):css中的数学计算;
  • 颜色功能(Color function):可以编辑你的颜色;
  • 命名空间(Namespaces):样式分组,从而方便被调用;
  • 作用域(Scope):局部修改样式;
  • JavaScript表达式(Javascript evaluation):在CSS样式中使用Javascript表达式赋值。

理解sass中变量的作用域

作用域是程序中的一个标准,LESS中也是。如果你在你样式表的root级声明一个变量,它在整个文档中都是可以调用的。然而,如果你在一个选择器,比如id或者class中,重新定义了这个变量,那么,它就只能在这个选择器中可用了——当然是重新定义后的新值。

@color: #00c; /* 蓝色 */

#header {
  @color: #c00; /* 红色 */
  border: 1px solid @color; /* 红色边框 */
}

#footer {
  border: 1px solid @color; /*蓝色边框 */
}

因为我们在#header中重新定义了color变量,变量的值将会是不同的而且只会在该选择器中有效。它之前或者之后的所有地方,如果没有被重新定义,都会保持那个原始的值。

作用域在Sass中稍有不同。在上面的代码中,当@color变量变为红色后,代码中,此处之后的该变量的值,将会被重写(成为红色)。

参考

Yarn vs npm

Yarn 是 Facebook, Google, Exponent 和 Tilde 开发的一款新的 JavaScript 包管理工具。就像我们可以从官方文档了解那样,它的目的是解决这些团队使用 npm 面临的少数问题,即:

  • 安装的时候无法保证速度/一致性
  • 安全问题,因为 npm 安装时允许运行代码

它并没有试图完全取代 npm。Yarn 同样是一个从 npm 注册源获取模块的新的 CLI 客户端。注册的方式不会有任何变化 —— 你同样可以正常获取与发布包.

Yarn vs npm: 功能差异

乍一看 Yarn 与 npm 很类似,但通过引擎的对比就能察觉 Yarn 的不同。

yarn.lock 文件

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 vs npm: CLI 的差异

除了一些功能差异,Yarn 命令也存在一些区别。例如移除或修改了一些 npm 命令以及添加了几个有趣的命令。

yarn global

yarn install

yarn add [–dev]

yarn licenses [ls|generate-disclaimer]

yarn why

yarn upgrade

yarn generate-lock-entry

稳定性与可靠性

Yarn 被炒得这么火热会不会有问题?它正式发布当天就收到很多问题反馈,但官方处理问题的速度极快。这些表明社区正努力开发并修复bug。查看问题反馈的数量和类型可以发现 Yarn 在大多数用户的机子上表现的很稳定,但可能个别机子会有问题。

请注意虽然一个包管理器可能对你的项目非常重要,但它仅仅只是个工具,如果出了状况,恢复包不会困难,也并非要回归 npm。

JS函数前加分号和感叹号的作用

一般看JQuery插件里的写法是这样的

(function($) {         
  //...  
})(jQuery);

今天看到bootstrap的javascript组件是这样写的

!function( $ ){
  //...
}( window.jQuery );

为什么要在前面加一个 " ! " 呢?


我们都知道,函数的声明方式以下这两种:

function fnA() {alert('msg');} //声明式定义函数
var fnB = function() {alert('msg');} //函数赋值表达式定义函数

但是,如果我们尝试为一个“定义函数”末尾加上(),解析器是无法理解的。

function msg() {
  alert('message');
}(); //解析器是无法理解的

定义函数的调用方式应该是 msg() ;
如果将函数体部分用()包裹起来就可以运行并且解析器是不报错的,如:

(function($) {         
  //...  
})(jQuery);

原来,使用括号包裹定义函数体,解析器将会以函数表达式的方式去调用定义函数。也就是说,任何能将函数变成一个函数表达式的作法,都可以使解析器正确的调用定义函数。而 ! 就是其中一个,而 + - || 都有这样的功能。

另外,用 ! 可能更多的是一个习惯问题,不同的运算符,性能是不同的。

参考

一个项目的技术栈

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.