Giter Site home page Giter Site logo

archeru / notes Goto Github PK

View Code? Open in Web Editor NEW
6.0 6.0 1.0 20.88 MB

博客笔记 预计写七个系列:JavaScript深入系列、JavaScript专题系列、数据结构和算法系列、设计模式系列、React系列、Webpack 系列、前端开发日常优化方案

CSS 73.15% HTML 24.58% JavaScript 2.27%
blog

notes's People

Contributors

archeru avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

oniubi

notes's Issues

JavaScript深入系列之混合对象“类”

“类”是什么?"类"是一种设计模式。

“类”的特点:封装、继承、多态。

封装:数据和操作数据相关的行为打包起来。
多态:父类的通用行为可以被子类用更特殊的行为重写。

构造函数

通常和类名相同的类方法称为构造函数。

class CoolGuy {
    specialTrick = nothing
    
    // 构造函数
    CoolGuy(trick) { 
        specialTrick = trick
    }
    
    showOff() {
        output("Here's my trick:", specialTrick)
    }
}

Joe = new CoolGuy("jumping rope");
Joe.showOff(); // Here's my trick: jumping rope

CoolGuy 类有一个 CoolGuy() 构造函数,执行 new CoolGuy() 时实际上调用的就是它。构造函数会返回一个对象(也就是一个类的实例)。

类的继承

class Vehicle {
    engines = 1
    ignition() {
        output("Turning on my engine.")
    }
    drive() {
        ignition();
        output("Steering and moving forward!")
    }
}

class Car inherits Vehicle {
    wheels = 4
    drive() {
        inherited: drive()
        output("Rolling on all", wheels, " wheels!")
    }
}

class SpeedBoat inherits Vehicle {
    engines = 2 
    ignition() {
        output("Turning on my", engines, "engines.")
    }
    pilot() {
        inherited: drive()
        output("Speeding through the water with ease!")
    }
}

CarSpeedBoat 都从 Vehicle 继承了通用特性并根据自身类别修改了某些特性。

多态

依照上面的例子,Car 重写了继承自父类的 drive() 方法,Car 调用了inherited: drive()方法,这表明 Car 可以引用继承来的原始drive() 方法。SpeedBoat 同样引用了原始drive()方法。这种技术就是多态。

多态并不表示子类和父类有关联,子类得到的只是父类的一份副本。类的继承其实就是复制

在许多语言中可以使用 super 来代替本例中的 inherited,它的含义是“超类”(superclass)。

混入

模拟类的复制行为

显式混入

// 非常简单的 mixin(..) 例子
function mixin(sourceObj,targetObj) {
    for (var key in sourceObj) {
        // 只会在不存在的情况下复制
        if (!(key in targetObj)) {
            targetObj[key] = sourceObj[key];
        }   
    }    
    return targetObj;
}
var Vehicle = {
    engines: 1,
    
    ignition: function() {
        console.log("Turning on my engine");
    },
    drive: function() {
        this.ignition();
        console.log("Steering and moving forward!");
    }
};
var Car = mixin(Vehicle, {
    wheels: 4,
    drive: function() {
        Vehicle.drive.call(this);
        console.log(
            "Rolling on all" + this.wheels + " wheels!"
        );
    }
});

Car 有了一份 Vehicle 属性和函数的副本。函数实际上并没有被复制,复制的是函数的引用。

JavaScript深入系列之数字

数字

number(数字),包括 “整数” 和带小数的十进制数。

JavaScript 中的数字类型是基于 IEEE 754 标准来实现的,该标准通常也被称为“浮点数”。JavaScript 使用的是 “双精度” 格式(即 64 位二进制)。

机器精度

二进制浮点数最大的问题(不仅 JavaScript,所有遵循 IEEE 754 规范的语言都是如此),会出现如下情况:

0.1 + 0.2 === 0.3; // false

二进制浮点数中的 0.1 和 0.2 并不是十分精确,它们相加的结果并非刚好等于 0.3,而是一个比较接近的数字 0.30000000000000004,所有条件判断结果为 false。

处理以上问题的方法是设置一个误差范围值,通常称为 “机器精度”(machine epsilon),对 JavaScript 的数字来说,这个值通常是 2^-52 (2.220446049250313e-16)。

// ES6 polyfill
if (!Number.EPSILON) {
    Number.EPSILON = Math.pow(2,-52);
}

比较两个数字是否相等(在指定的误差范围内):

function numberCloseEnoughToEqual(n1,n2) {
    return Math.abs(n1 - n2 ) < Number.EPSILON;
}

var a = 0.1 + 0.2;
var b = 0.3;

numberCloseEnoughToEqual(a,b); // true
numberCloseEnoughToEqual(0.0000001,0.0000002); // false

特殊的数字

NaN 指 “不是一个数字”(not a number),其实将它理解为 “无效数值” “失败数值” 或者 “坏数值” 可能更准确些。

var a = 2 / "foo"; // NaN

typeof a === "number"; // true

NaN 是一个 “警戒值”,用于指出数字类型中的错误情况,即 “执行数学运算没有成功,这是失败后返回的结果”。

var a = 2 / "foo";

a == NaN; // false
a === NaN; // false

NaN 是一个特殊值,它和自身不相等。

JavaScript深入系列之Promise

异步编程中通过回调表达程序异步和管理并发的两个主要缺陷:缺乏顺序性和可信任性。

Promise

Promise 是一种封装和组合未来值的易于复用的机制,用于解决异步编程中可信任性问题。

顺序的大脑

// A 
setTimeout(function() {
    // C
},1000);
// B

// 交换x和y,通过临时变量z
z = x;
x = y;
y = z;

思考以上两个伪代码片段的执行顺序。第一个我们大脑这么描述:执行 A,设定延时 1000ms,然后执行 B,然后 1000ms 后执行 C。
第二个我们大脑这么描述: z=x 执行完毕后,然后执行 x=y,最后执行 y=z。第二个描述,我们的大脑思考方式是一步一步的,但是第一个不是。

回调

缺乏顺序性,回调函数会导致代码变得更加难以理解(跳来跳去查看流程)、追踪(跟踪异步流困难)、调试维护

doA(function() {
    doB();
    
    doC(function() {
        doD(); 
    });
    
    doE();
});
doF();

如果 doA,doC 是异步执行的,伪代码的执行顺序是这样的:

  • doA()
  • doF()
  • doB()
  • doC()
  • doE()
  • doD()

如果 doA,doC 是同步执行的,伪代码的执行顺序是这样的:

  • doA()
  • doB()
  • doC()
  • doD()
  • doE()
  • doF()

回调导致我们跟踪异步流非常困难。同步或异步行为引起的不确定性导致bug追踪困难。并且嵌套在异步流中的代码通常无法复用。一旦控制流程变化,回调中的代码也会导致很难以维护和更新。

信任

回调最大的问题是控制反转导致的信任问题。因为回调暗中把控制权交给第三方(通常是不受你控制的第三方工具!)来调用你代码中的 continuation 。这种控制转移导致一系列麻烦的信任问题。

// A
ajax("..",function(..) {
    // C 
});
// B

这样的代码通常不会有问题。如果 ajax(..) 不是你编写的代码,而是第三方的提供的工具,那就相当于把自己程序一部分的执行控制权交给了第三方,这就会导致一系列信任问题。

Promise 信任问题

调用回调过早

promise 提供给 then(..) 的回调总是会被异步调用。

调用回调过晚

promise 决议后,所有通过 then(..) 注册的回调都会在下一个异步时机点上依次被立即调用。

回调未调用

promise 在决议时总是会调用一个完成回调或一个拒绝回调。

调用回调次数过少或过多

promise 只能被决议一次。

未能传递参数/环境值

promise 至多只能有一个决议值(完成或拒绝)。如果没有任何值显示决议,那么这个值就是 undefined。

吞掉错误或异常

promise 会把出错消息传给拒绝回调。

Promise 模式

Pormise.all([..])

promise.all([..]) 返回的主 promise 在且仅在所有的成员 promise 都完成后才会完成。如果这些 promise 中有任何一个被拒绝的话,主 Promise.all([..]) promise 就会立即被拒绝,并丢弃来自其他所有 promise 的全部结果。

Promise.race([..])

promise.race([..]) 一旦有任何一个 Promise 决议为完成,Promise.race([..]) 就会完成,一旦有任何一个 Promise 决议为拒绝,它就会拒绝。

JavaScript深入系列之词法作用域和动态作用域

作用域

作用域是根据名称查找变量的一套规则。

作用域规则用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找。

任何声明在某个作用域内的变量,都将附属于这个作用域。

JavaScript 采用词法作用域(lexical scoping),也就是静态作用域。

词法作用域与动态作用域

词法作用域只由函数被声明时所处的位置决定,不论函数在哪里被调用,也不论它如何被调用。

让我们认真看个例子就能明白之间的区别:

var a = 2;

function foo() {
    console.log(a); 
}

function bar() {
    var a = 3;
    foo();
}

bar();

// 结果是 ???

假设JavaScript采用静态作用域,让我们分析下执行过程:

执行 foo 函数,先从 foo 函数内部查找是否有局部变量 a,如果没有,就根据书写的位置,查找上面一层的代码,也就是 a 等于 2,所以结果会打印 2。

假设JavaScript采用动态作用域,让我们分析下执行过程:

执行 foo 函数,当foo()无法找到a的变量引用时,会顺着调用栈在调用foo()的地方查找a,而不是在嵌套的词法作用域链中向上查找。由于foo()是在bar()中调用的,引擎会检查 bar()的作用域,并在其中找到值为3的变量a

前面我们已经说了,JavaScript采用的是静态作用域,所以这个例子的结果是 2。

动态作用域

动态作用域是基于调用栈的,而不是代码中的作用域嵌套。

也许你会好奇什么语言是动态作用域?

bash 就是动态作用域,不信的话,把下面的脚本存成例如 scope.bash,然后进入相应的目录,用命令行执行 bash ./scope.bash,看看打印的值是多少。

value=1
function foo () {
    echo $value;
}
function bar () {
    local value=2;
    foo;
}
bar

LHS 查询还是 RHS 查询

理解 LHS 还是 RHS 之前,我们需要完全理解JavaScript的工作原理。

  • 引擎

    从头到尾负责整个JavaScript程序的编译及执行过程。

  • 编译器

    引擎的好朋友之一,负责语法分析及代码生成等

  • 作用域

    引擎的另一个好朋友,负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。

引擎对一个变量进行LHS查询还是RHS查询,取决于这个变量是出现在赋值操作的左侧还是右侧。

如果引擎执行LHS查询无法找到目标变量,在非严格模式下会自动创建一个具有该名称的变量,并将其返还给引擎。严格模式禁止自动或隐式地创建全局变量,因此会抛出ReferenceError异常。

如果RHS查询在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出ReferenceError异常。

如果RHS查询找到一个变量,但是你尝试对这个变量的值进行不合理的操作,会抛出TypeError异常。

ReferenceError同作用域判别失败相关,而TypeError则代表作用域判别成功了,但是对结果的操作是非法或不合理的。

思考题

最后,让我们看一个《JavaScript权威指南》中的例子:

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();

猜猜两段代码各自的执行结果是多少?

这里直接告诉大家结果,两段代码都会打印:local scope。

原因也很简单,因为JavaScript采用的是词法作用域,函数的作用域基于函数创建的位置。

而引用《JavaScript权威指南》的回答就是:

JavaScript 函数的执行用到了作用域链,这个作用域链是在函数定义的时候创建的。嵌套的函数 f() 定义在这个作用域链里,其中的变量 scope 一定是局部变量,不管何时何地执行函数 f(),这种绑定在执行 f() 时依然有效。

发布-订阅模式

发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。

发布-订阅模式优点:1、时间上解耦,2、对象之间解耦。
发布-订阅模式适用场景:异步编程,代码松耦合。

发布-订阅模式缺点:1、订阅消息未发生导致浪费内存,2、过度使用导致程序难以追踪维护和理解。

发布-订阅模式例子

发布-订阅模式核心明确谁来当发布者,订阅者和缓存列表。

const salesoffices = { // 定义售楼处

	clientList: [], // 定义缓存列表,存放订阅者的回调函数

	listen: function(fn) { // 增加订阅者
		this.clientList.push(fn);  // 订阅的消息添加进缓存列表
	},

	trigger: function() {  // 发布消息
		for(let i =0; i < this.clientList.length; i++) {
			let fn = this.clientList[i]; 
			fn.apply(this,arguments);  // arguments 是发布消息时带上的参数
		}
	}
};

salesoffices.listen(function(price,squareMeter){
	console.log("价格:" + price);
	console.log("squareMeter:"+ squareMeter);
});

salesoffices.listen(function(price,squareMeter){
	console.log("价格:" + price);
	console.log("squareMeter:"+ squareMeter);
});

salesoffices.trigger(20000,89);
salesoffices.trigger(30000,110);

改进订阅者只订阅自己感兴趣的事件

const salesoffices = {

	clientList: {}, 

	listen: function(key,fn) {
		if (!this.clientList[key]) {
			this.clientList[key] = [];
		}
		this.clientList[key].push(fn);
	},

	trigger: function() {
		const key = Array.prototype.shift.call(arguments);
		const fns = this.clientList[key];

		if (!fns || fns.length === 0) {
			return false;
		}

		for(let i=0,length=fns.length;i<length;i++) {
			let fn = fns[i];
			fn.apply(this,arguments);
		}
	},
	
	remove: function(key, fn) {
		const fns = this.clientList[key];

		if (!fns) { // 如果key对应的消息没有被人订阅,则直接返回
			return false;
		}

		if (!fn) { // 如果没有传入具体的回调函数,表示需要取消key对应消息的所有订阅
			fns && (fns.length = 0);
		} else {
			for (let i= fns.length -1;i >=0;i--) { // 反向遍历订阅的回调函数列表
				let _fn = fns[i];
				if ( _fn === fn) {
					fns.splice(i ,1);  // 删除订阅者的回调函数
				}
			}
		}
	}
};

salesoffices.listen("squareMeter89", function(price) {
	console.log("价格:" + price);
});                                              
salesoffices.listen("squareMeter110", function(price) {
	console.log("价格:" + price);
});

salesoffices.trigger("squareMeter89",20000);
salesoffices.trigger("squareMeter110",30000);

模块间通信

发布-订阅模式可用于模块间通信。

<!DOCTYPE html>
<html>

<body> 
    <button id="count">点我</button>
    <div id="show"></div>
</body>
<script type="text/javascript">
    var a = (function(){
        var count = 0;
        var button = document.getElementById("count");
        button.onClick = function() {
            Event.trigger("add",count++);
        }
    })();
    var b = (function(){
        var div = document.getElementById("show");
        Event.listen("add",function(count) {
            div.innerHTML = "count";
        });
    })();
</script>
</html>

例如,上面两个模块,a模块里面的按钮,每次点击按钮之后,b模块里的div中会显示按钮的总点击次数。a,b模块相互独立,但是可以通过全局发布-订阅模式进行通信。

JavaScrip深入系列之生成器和迭代器

生成器

生成器(Generator)就是一类特殊的函数,可以一次或多次启动和停止,并不一定非得要完成。

生成器使代码变成一种顺序,看似同步的异步流程。

生成器解决了异步编程中缺乏顺序性问题。

let x = 1;
function *foo() {
    x++;
    yield; // 暂停
    console.log("x:",x);
}

function bar() {
    x++;
}

// 构造一个迭代器 it 来控制这个生成器
let it = foo();

// 这里启动foo();
it.next();
x;  // 2
bar(); 
x; // 3
it.next(); // x: 3

双向消息传递系统

function *foo(x) {
    const y = x * (yield "hello");
    return y;
}
let it = foo(6);
let res = it.next();
res.value;  // "hello"
res = it.next(7);
res.value;

多个迭代器

let z = 1;
function *foo() {
    const x = yield 2;
    z++;
    const y = yield (x * z);
    console.log(x,y,z);
}
const it1 = foo();
const it2 = foo();

let val1 = it1.next().value; // 2
let val2 = it2.next().value; // 2

val1 = it1.next(val2 * 10).value; // 40 <-- x:20, z:2
val2 = it2.next(val1 * 5).value; // 600 <-- x:200, z:3

it1.next(val2 / 2);  // 20,300,3
it2.next(val1 / 4);  // 200,10,3

生成器 + Promise 协同运作

function run(gen) {
    const args = [].slice.call(arguments, 1),it;
    
    // 在当前上下文中初始化生成器
    it = gen.apply(this, args);
    
    // 返回一个promise用于生成器完成
    return Promise.resolve()
            .then(function handleNext(value) {
                // 对下一个 yield 出的值运行
                const next = it.next(value);
                
                return (function handleResult(next){
                    // 生成器运行完毕了吗?
                    if (next.done) {
                        return next.value;
                    }
                    // 否则继续运行
                    else {
                        return Promise.resolve(next.value)
                            .then(
                                // 成功就恢复异步循环,把决议的值发回生成器
                                handleNext,
                                
                                // 如果value是被拒绝的 promise,就把错误传回生成器进行出错处理
                                function handleErr(e) {
                                    return Promise.resolve(it.throw(err))
                                    .then(handleResult);
                                } 
                            );
                    }
                })(next);
            })
}

function *main() {
    // ...
}
run(main);

运行 run(...) 的方式,它会自动异步运行你传给它的生成器。

生成器中 promise 的并发

function *foo() {
    // 让两个请求“并行”
    const p1 = request("http://some.url.1");
    const p2 = request("http://some.url.2");
    
    // 等待两个 promise 都决议
    const r1 = yield p1;
    const r2 = yield p2;
    
    const r3 = yield request(`http://some.url.3/?v=${r1},${r2}`);
    
    console.log(r3);
}

// 使用前面定义的工具 run(..)
run(foo);

生成器委托

形实转换程序

形实转换程序(thunk) 指一个用于调用另外一个函数的函数,没有任何参数。

function foo(x,y) {
    return x + y;
}
function fooThunk() {
    return foo(3,4);
}

console.log(fooThunk()); // 7

生成器原理

手工变换

自动转换

JavaScript深入系列之强制类型转换

内置类型

JavaScript 有七种内置类型。

  • 空值(null)
  • 未定义(undefined)
  • 字符串(string)
  • 数字(number)
  • 布尔值(boolean)
  • 对象(object)
  • 符号(symbol,ES6 中新增)

检测

typeof undefined === "undefined"; // true
typeof "42" === "string"; // true
typeof 42 === "number"; // true
typeof true === "boolean"; // true
typeof { life: 42 } === "object"; // true

比较特殊的存在

typeof null === "object"; // true
typeof function a(){/*..*/} === "function"; // true

function(函数)也是 JavaScript 的一个内置类型。它实际上是 Object 的一个“子类型”。

常用原生函数

  • String()
  • Number()
  • Boolean()
  • Array()
  • Object()
  • Function()
  • RegExp()
  • Date()
  • Error()
  • Symbol() --- ES6 中新加入的!
var a = new String("abc");

typeof a; // 是 "object",不是 "String"

a instanceof String; // true

Object.prototype.toString.call( a ); // "[object String]"

typeof 只能用于判断基本数据类型,无法判断复杂数据类型。对于所有 typeof 返回值为 "object" 的对象(如数组)都包含一个内部属性 [[Class]] (我们可以把它看作一个内部的分类,而非传统的面向对象意义上的类)。这个属性无法直接访问,一般通过 Object.prototype.toString(..) 来查看。

Object.prototype.toString.call([1,2,3]);
// "[object Array]"

Object.prototype.toString.call(/regex-literal/i);
// "[object RegExp]"

Null() 和 Undefined() 这样的原生构造函数并不存在,但是内部 [[Class]] 属性值仍然是 "Null" 和 "Undefined"。其它基本类型被各自的封装对象自动包装起来,所以它们的内部 [[Class]] 属性值分别为 "String","Number","Boolean"。

Object.prototype.toString.call("abc");
// "[object String]"

Object.prototype.toString.call(null);
// "[object Null]"

Object.prototype.toString.call(1);
// "[object Number]"

Object.prototype.toString.call(true);
// "[object Boolean]"

Object.prototype.toString.call(undefined);
// "[object Undefined]"

Object.prototype.toString.call(Symbol(123))
// "[object Symbol]"

这里的 Symbol 非常特殊,我们可以使用 Symbol(..) 原生构造函数来自定义符号。但不能带 new 关键字,否则会出错。

强制类型转换

ToString

toString() 负责处理非字符串到字符串的强制类型转换。

基本类型的字符串化规则:null 转换为 "null"undefined 转换为 "undefined"true 转换为 "true"。数字的字符串化遵守通用规则,极小和极大的数字使用指数形式。

// 1.07 连续乘以七个 1000
var a = 1.07 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000;

// 七个 1000 一共 21 位数字
a.toString(); // "1.07e21"

普通对象的字符串化规则:除非对象自定义了 toString() 方法,否则 toString() (Object.prototype.toString()) 返回内部属性 [[Class]] 的值。

// 普通对象
var a = {b: "123"};

a.toString(); // "[object Object]"
// 数组
var a = [1,2,3];

a.toString(); // "1,2,3"

数组的默认 toString() 方法经过了重新定义,将所有单元字符串化后再用 "," 连接起来。

ToNumber

基本类型的数字化规则:true 转换为 1,false 转换为 0。undefined 转换为 NaNnull 转换为 0。字符串的处理基本遵循数字常量的相关规则/语法。处理失败时返回 NaN(处理数字常量失败时会产生语法错误)。

对象(包括数组)会首先被转换为相应的基本类型值。首先检查是否有 valueOf() 方法,如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用 toString() 的返回值(如果存在)来进行强制类型转换。如果 valueOf()toString() 都不返回基本类型值,会产生 TypeError 错误。

Object.create(null) 创建的对象 [[Prototype]] 属性为 null,并且没有 valueOf()toString() 方法,因此无法进行强制类型转换。

var a = Object.create(null);

Number(a);

// VM406:1 Uncaught TypeError: Cannot convert  
// object to primitive value
// at Number (<anonymous>)
//  at <anonymous>:1:1

ToBoolean

假值:

  • undefined
  • null
  • false
  • +0、-0 和 NaN
  • ""

除了假值以外的值都是真值。

显示强制类型转换

字符串和数字之间的显示转换

String(..) 和 Number(..)

String(..) 遵循 ToString 规则,Number(..) 遵循 ToNumber 规则。

var a = 42;
var b = String(a);

var c = "3.14";
var d = Number(c);

b; // "42"
d; // 3.14

toString() 、 一元运算符 +~ 运算符(即字位操作 “非“)

var a = 42;
var b = a.toString();

var c = "3.14";
var d = +c;

b; // "42"
d; // 3.14

显示转换为布尔值

Boolean(..) 遵循 ToBoolean 规则。

var a = "0";
var b = [];
var c = {};

var d = "";
var e = 0;
var f = null;
var g;

Boolean(a); // true
Boolean(b); // true
Boolean(c); // true

Boolean(d); // false
Boolean(e); // false
Boolean(f); // false
Boolean(g); // false

一元运算符 !!!

一元运算符 ! 显式地将值强制类型转换为布尔值。但是它同时还将真值反转为假值(或者将假值反转为真值)。所以显式强制类型转换为布尔值最常用的方法是!!

var a = "0";
var b = [];
var c = {};

var d = "";
var e = 0;
var f = null;
var g;

!!a; // true
!!b; // true
!!c; // true

!!d; // false
!!e; // false
!!f; // false
!!g; // false

隐式强制类型转换

字符串和数字之间的隐式转换

+ 运算符即能用于数字加法,也能用于字符串拼接。如果 + 的其中一个操作数是字符串则执行字符串拼接,否则执行数字加法。

var a = "42";
var b = "0";

var c = 42;
var d = 0;

a + b; // "420"
c + d; // 42
a + d; // "420"

-*/ 都只适用于数字,因此会强制类型转换为数字。

var a = "3.14";
var b = a - 0;

b; // 3.14

隐式转换为布尔值

  1. if (..) 语句中的条件判断表达式
  2. for ( .. ; .. ; .. ) 语句中的条件判断表达式(第二个)
  3. while (..) 和 do..while(..) 循环中的条件判断表达式
  4. ? : 中的条件判断表达式
  5. 逻辑运算符 ||(逻辑或)和 &&(逻辑与)左边的操作数(作为条件判断表达式)

以上遵循 ToBoolean 规则。

宽松相等和严格相等

==允许在相等比较中进行强制类型转换,而===不允许。

11.9.3 节中规定, == 在比较两个不同类型的值时会发生隐式强制类型转换,会将其中之一或两者都转换为相同的类型后再进行比较。
11.9.3.1 的最后定义了对象(包括函数和数组)的宽松相等 == 。两个对象指向同一个值时即视为相等,不发生强制类型转换。

字符串和数字之间的相等比较

字符串强制转换为数字比较

如果 Type(x) 是数字,Type(y) 是字符串,则返回 x == ToNumber(y) 的结果。
如果 Type(x) 是字符串,Type(y) 是数字,则返回 ToNumber(x) == y 的结果。

其它类型和布尔值之间的比较相等

布尔值强制类型转换为数字比较

如果 Type(x) 是布尔类型,则返回 ToNumber(x) == y 的结果。
如果 Type(y) 是布尔类型,则返回 x == ToNumber(y) 的结果。

null和undefined之间的相等比较

null 和 undefined 相等(它们也与自身相等),除此之外不与其它任何值相等

对象和非对象之间的相等

对象强制类型转换为基本数据类型值

如果 Type(x) 是字符串或数字,Type(y) 是对象,则返回 x == ToPrimitive(y) 的结果。
如果 Type(x) 是对象,Type(y) 是字符串或数字,则返回 ToPrimitive(x) == y 的结果。

抽象关系比较

“抽象关系比较”,分为两个部分:比较双方都是字符串和其他情况。
比较双方首先调用 ToPrimitive ,如果结果出现非字符串,就根据 ToNumber 规则将双方强制类型转换为数字来进行比较。
比较双方都是字符串,则按字母顺序来进行比较。

安全运用隐式强制类型转换

  • 如果两边的值中有 true 或者 false,千万不要使用 ==
  • 如果两边的值中有 []"" 或者 0,尽量不要使用 ==

迭代器模式

迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露改对象的内部表示。

内部迭代器

内部迭代器的迭代规则被提前规定,外部只需要一次初始调用。

const each = function(array, callback) {
    for (let i=0,length=array.length;i<length;i++) {
    callback.call(array[i],i,array[i]); // 把下标和元素当作参数传给 callback 函数
    }
};
each([1,2,3],function(i,n) {
    console.log("i,n",[i,n]);
});

外部迭代器

外部迭代器必须显式地请求迭代下一个元素。

const Iterator = function(obj) {
    let current = 0;
    const next = function() {
        current += 1;
    };
    const isDone = function() {
        return current >= obj.length;
    };
    const getCurrentItem = function() {
        return obj[current];
    };
    return {
        next,
        isDone,
        getCurrentItem
    }
};
const iterator1 = Iterator([1,2,3]);
iterator1.getCurrentItem(); // 1
iterator1.isDone(); // false
iterator1.next(); 
iterator1.getCurrentItem(); // 2
iterator1.isDone(); // false
iterator1.next(); 
iterator1.getCurrentItem(); // 3
iterator1.isDone(); // false
iterator1.next();
iterator1.getCurrentItem(); // undefined
iterator1.isDone(); // true

倒序迭代器

迭代器模式提供了循环访问一个聚合对象中每个元素的方法,但它没有规定我们以顺序、倒序还是中序来循环遍历聚合对象。

const reverseEach = function(array, callback) {
    for(let i=array.length;i>=0;i--) {
        callback(i,array[i]);
    };
};
reverseEach([0,1,2],function(i,n) {
    console.log(n); // 2,1,0
});

中止迭代器

迭代器可以像普通 for 循环中的 break 一样,提供一种跳出循环的方法。

const each = function(array, callback) {
    for(let i=0,length=array.length;i<length;i++) {
        if (callback(i,array[i]) === false ) { // callback 的执行结果返回false,提前终止迭代
            break;
        }
    }
};

each([1,2,3,4,5],function(i,n){
    if (n>3) { // n 大于 3 的时候终止循环
        return false;
    }
    console.log(n); // 分别输出:1,2,3
});

JavaScript深入系列之作用域链

函数作用域

任何声明在某个作用域内的变量,都将附属于这个作用域。在 JavaScript 语言中只有函数作用域。

隐藏内部实现

function doSomething(a) {
    b = a + doSomethingElse(a * 2);
    console.log(b * 3);
}   

function doSomethingElse(a) {
    return a - 1;
}
var b;
doSomething(2); 

// 15
function doSomething(a) {
    function doSomethingElse(a) {
        return a - 1;
    }
    var b;
    b = a + doSomethingElse(a * 2);
    console.log(b * 3);
}
doSomething(2); 

// 15

bdoSomethingElse(...) 都无法从外部被访问,而只能被 doSomething(...) 所控制。功能性和最终效果都没有受影响,但是设计上将具体内容私有化了。这便是函数作用域的作用。

立即执行函数表达式(IIFE)

var a = 2;
function foo() {
    var a = 3;
    console.log(a); // 3
}
foo();
console.log(a); //2

虽然隐藏内部实现可以解决一些问题,但是并不理想。首先,必须声明一个具名函数foo(),意味着foo这个名称本身“污染”了所在的作用域。其次,必须显式地通过函数名foo()调用这个函数才能运行其中的代码。

做如下改进:

var a = 2;
(function foo() {
    var a = 3;
    console.log(a); // 3
})();
console.log(a); //2 

函数被包含在一对 ()括号内部,因此成为一个表达式,通过在末尾加上另外一个 ()括号可以立即执行这个函数。

IIFE(Immediately Invoked Function Expression)的另外一个非常普遍的进阶用法是把它们当作函数调用并传递参数进去。用于解决如下问题:

undefined = true; // 给其他代码挖了一个大坑!绝对不要这么做!

(function IIFE(undefined) {
    var a;
    if (a === undefined) {
        console.log("Undefined is safe here!")
    }
})();
// Undefined is safe here!

IIFE还可以用于倒置代码的运行顺序,将需要运行的函数放在第二位,在IIFE执行之后当作参数传递进去。这种模式在 UMD(Universal Module Definition)项目中被广泛使用。

var a = 2;
(function IIFE(def) {
    def(window);
})(function def(global) {
    var a = 3;
    console.log(a); //3 
    console.log(global.a); // 2
});

JavaScript深入系列之this

this是一个很特别的关键字,被自动定义在所有函数的作用域中。

this既不指向函数自身也不指向函数的词法作用域。

this是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。

this 绑定规则优先级:new > call(..) 或者 apply(..) > 上下文对象调用 > 默认绑定

调用位置

调用栈:为了到达当前执行位置所调用的所有函数。
调用位置在当前正在执行的函数的前一个调用中。

function baz() {
    // 当前调用栈是:baz
    // 因此,当前调用位置是全局作用域
    console.log("baz");
    bar(); // <-- bar的调用位置
}

function bar() {
    // 当前调用栈是 baz -> bar 
    // 因此,当前调用位置在baz中
    console.log("bar");
    foo(); // <-- foo的调用位置
}

function foo() {
    // 当前调用栈是 baz -> bar -> foo 
    // 因此,当前调用位置在bar中
    console.log("foo");
}

baz(); // <-- baz的调用位置

注意我们是如何(从调用栈中)分析出真正的调用位置的,因为它决定了 this 的绑定。

绑定规则

默认绑定

var a = 2;
function foo() {
    console.log(this.a);   
}
foo(); // 2

独立函数调用,既使用不带任何修饰的函数引用进行调用,无法应用其他规则。非 static mode 下,this 默认绑定全局对象。strict mode 下,this 默认绑定 undefined。

隐式绑定

function foo() {
    console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo
};
obj.foo(); // 2

当函数引用有上下文对象时,隐私绑定规则会把函数调用中的 this 绑定到这个上下文对象。

显示绑定

call(..) apply(..) bing(..)

function foo() {
    console.log(this.a);
}
var obj = {
    a: 2
};
foo.call(obj) // 2

通过 foo.call(..),我们可以在调用foo时强制把它的 this 绑定到 obj 上。

如果传入了一个原始值(字符串类型、布尔类型、数字类型)来当作 this 的绑定对象,这个原始值会被转换成它的对象形式(也就是 new String(..)、new Boolean(..)、new Number(..) )。

从 this 绑定的角度来说,call(..) 和 apply(..) 是一样的,它们区别体现在其他的参数上。

new绑定

使用 new 来调用函数,会自动执行下面的操作

  1. 创建(构造)一个全新的对象。
  2. 这个新对象会被执行[[原型]]连接。
  3. 这个新对象会绑定到函数调用的this。
  4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。
function foo(a) {
    this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); //2

使用 new 来调用 foo(..) 时,我们会构造一个新对象并把它绑定到foo(..)调用中的 this 上。new 是最后一种可以影响函数调用时 this 绑定行为的方法。

绑定例外

被忽略的this

如果你把null 或者 undefined 作为 this 的绑定对象传入 call, apply 或者 bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。

function foo() {
    console.log(this.a);
}
var a = 2;
foo.call( null ); // 2

bing(..) 可以对参数进行柯里化(预先设置一些参数)。

function foo(a,b) { 
    console.log("a:" + a + ",b:" + b);
}

// 把数组“展开”成参数
foo.apply(null,[2,3]); // a:2, b:3

// 使用 bind(..) 进行柯里化
var bar = foo.bind(null, 2);
bar(3); // a:2, b:3

这两种方法都需要传入一个参数当作 this 的绑定对象。如果函数并不关心 this 的话,你仍然需要传入一个占位值,这时 null 可能是一个不错的选择。

总是使用 null 来忽略 this 绑定可能产生一些副作用。如果某个函数确实使用了 this (比如第三方库中的一个函数),那默认绑定规则会把 this 绑定到全局对象(在浏览器中这个对象是window),这将导致不可预计的后果(比如修改全局对象)。

更安全的this

function foo(a,b) {
    console.log("a:" + a + ",b:" + b);
}

// 我们的 DMZ 空对象
var o = Object.create( null );

// 把数组展开成参数
foo.apply(o , [2,3]); // a:2, b:3

// 使用 bind(..) 进行柯里化
var bar = foo.bind(o, 2);
bar(3); // a:2, b:3

JavaScript 中创建一个空对象最简单的方法都是 Object.create(null)Object.create(null) 和 {} 很像,但是并不会创建 Object.prototype 这个委托,所以它比 {} "更空"。

箭头函数

箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决定 this。

function foo() {
    //返回一个箭头函数
    return (a) => {
        // this 继承自 foo()
        console.log(this.a);
    };
}

var obj1 = {
    a: 2
};
var obj2 = {
    a: 3
}

var bar = foo.call( obj1 );
bar.call( obj2 );  // 2,不是 3 ! 

JavaScript深入系列之原型和原型链

构造函数

所有带 new 的函数调用。

function Foo() {
    // ...
}

var a = new Foo();

Foo 和你程序中的其他函数没有任何区别。Foo 函数本身不是构造函数,但是当且仅当使用 new 时,函数调用会变成 “构造函数调用”。

[[prototype]]

JavaScript 中的对象拥有的一个特殊的 [[prototype]] 内置属性。这个属性其实就是对其他对象的引用。

function Foo() {
    // ...
}

var a = new Foo();

Object.getPrototypeOf(a) === Foo.prototype; // true

调用 new Foo() 时会创建 a 这个对象,创建过程的其中一步是给 a 一个内部的 [[prototype]] 链接,关联到 Foo.prototype 指向的那个对象。

原型链

[[prototype]] 的作用是:如果在对象上没有找到需要的属性或者方法引用,引擎就会继续在 [[prototype]] 关联的对象上进行查找。同理,如果在后者中也没有找到需要的引用就会继续查找它的 [[prototype]],以此类推。这一系列对象的链接被称为“原型链”。

一张老图

代理模式

代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象。

虚拟代理

图片预加载

图片预加载是一种常用的技术,如果直接给某个img标签节点设置src属性,由于图片过大或者网络不佳,图片的位置往往有段时间会是一片空白。常见的做法是先用一张 loading 图片占位,然后用异步的方式加载图片,等图片加载好了再把它填充到img节点里,这种场景就很适合使用虚拟代理。

// 伪代码
const myImage = (function(){
    const imgNode = document.createElement("img");
    document.body.appendChild(imgNode);
    return {
        setSrc: function(src) {
            imgNode.src = src;
        }
    }
})();

const proxyImage = (function(){
    const img = new Image;
    img.onload = function() {
        myImage.setSrc(this.src);
    }
    return {
        setSrc: function(src) {
            myImage.setSrc('file:// c/.../gif');
            img.src = src;
        }
    }
})();

proxyImage.setSrc('http://.../.jpg');

合并HTTP请求

// 伪代码
const synchronousFile = function(id) {
      console.log("开始同步文件,id 为" + id);
};

const proxySynchronousFile = (function(){
    const cache = [], // 保存 
    timer; // 定时器
    
    return function(id) {
        cache.push(id);
        if (timer) { // 保证不会覆盖已经启动的定时器
            return;
        }
        timer = setTimeout(function() {
            synchronousFile(cache.join(',')); //2 秒后向
            clearTime(timer); // 清空定时器
            timer = null;
            cache.length = 0; // 清空 ID 集合
        },2000);
    }
})();
const checkbox = document.getElementsByTagName('input');

for (let i=0,c;c = checkbox[i++]) {
    c.onClick = function() {
        if (this.checked) {
            proxySynchronousFile(this.id);
        }
    }
}

缓存代理

缓存代理为一些开销大的运算结构提供暂时的存储,在下次运算时,如果传递进来的参数跟之前一致,则可以直接返回前面存储的运算结果。

// 计算乘积
const mult = function() {
    console.log("开始计算面积");
    let a = 1;
    for (let i=0,length=arguments.length;i<length;i++){
        a = a * arguments[i];
    }
    return a;
};

const proxyMult = (function(){
    const cache = {};
    return function() {
        const args = Array.prototype.join.call(arguments,',');
        if (args in cache) {
            return cache[args];
        }
        return cache[args] = mult.apply(this,arguments);
    }
})();

proxyMult(1,2,3,4); // 24
proxyMult(1,2,3,4); // 24

当我们第二次调用 proxyMult(1,2,3,4)的时候,本体 mult 函数并没有计算,proxyMult 直接返回了之前缓存好的计算结果。
缓存代理还可以用于 ajax 异步请求数据。

保护代理

保护代理用于控制不同权限的对象对目标对象的访问。

防火墙代理

控制网络资源的访问,保护主题不让“坏人”接近。

远程代理

为一个对象在不同的地址空间提供局部代表,在 Java 中,远程代理可以是一个虚拟机中的对象。

智能引用代理

取代了简单的指针,它在访问对象时执行一些附加操作,比如计算一个对象被引用的次数。

写时复制代理

通常用于复制一个庞大对象的情况。

JavaScript深入系列10篇正式完结!

前言

JavaScript 深入系列共计 10 篇已经正式完结,其中包含 ES5 和部分 ES6 的知识。这个旨在记录自己对于 JS 这门语言本质的理解。重点讲解了如作用域、闭包、this、对象原型、类型、语法、异步等 JS 语言中的晦涩难懂的概念。

JavaScript 深入系列自 5 月 10 日开始写作,至 6 月 5 日完结。

为什么写深入系列?

全目录

  • 作用域和闭包
    • JavaScript深入系列之词法作用域和动态作用域
    • JavaScript深入系列之作用域链
    • JavaScript深入系列之闭包
  • this 和对象原型
    • JavaScript深入系列之this
    • JavaScript深入系列之原型和原型链
    • JavaScript深入系列之混合对象“类”
  • 类型和语法
    • JavaScript深入系列之数字
    • JavaScript深入系列之强制类型转换
  • 异步
    • JavaScript深入系列之Promise
    • JavaScrip深入系列之迭代器和生成器

重新修订

初稿完成后,参照阮一峰老师的《中文技术文档的写作规范》对所有文章进行了一次修订。

此外,大家有疑问、指正、鼓励或感谢,请留言回复。

中介者模式

中介者模式的作用就是解除对象与对象之间的紧耦合关系。

中介者模式是迎合迪米特原则(最小知识原则:一个对象应该尽可能少地了解另外一个对象)的一种实现。

中介者模式优点:1、对象之间解耦,以中介者和对象的一对多关系取代对象之间的网状多对多关系。对象之间的交互关系由中介者维护,对象关注自身功能。

中介者模式缺点:1、对象之间的复杂关系转移到中介者对象,中介者对象本身往往就是一个难以维护的对象。2、中介者对象消耗内存。

中介者模式例子

function Player(name,teamColor) {
  this.name = name; // 
  this.teamColor = teamColor;
  this.state = 'alive';
}
Player.prototype.win = function() {
	console.log(this.name + 'win');
}
Player.prototype.lose = function() {
	console.log(this.name + 'lost');
}
Player.prototype.die = function() {
	this.state = 'dead';
	playerDirector.ReceiveMessage('playerDead',this);
}
Player.prototype.remove = function() {
	playerDirector.ReceiveMessage('removePlayer',this);
}
Player.prototype.changeTeam = function() {
	playerDirector.ReceiveMessage('changeTeam',this,color);
}

function playerFactory(name,teamColor) {
	const newPlayer = new Player(name,teamColor);
	playerDirector.ReceiveMessage('addPlayer',newPlayer);
	return newPlayer;
}

const playerDirector = (function(){
	const players = {},operations = {};

	operations.addPlayer = function(player) {
		const teamColor = player.teamColor;
		players[teamColor] = players[teamColor] || [];
		players[teamColor].push(player);
	}

	operations.removePlayer = function(player) {
		const teamColor = player.teamColor;
		const teamPalyers = players[teamColor] || [];
		for(let i=teamPalyers.length-1;i>=0;i--) {
			if (teamPalyers[i] === player) {
				teamPalyers.splice(i,1);
			}
		}
	}

	operations.changeTeam = function(player,newTeamColor) {
		operations.removePlayer(player);
		player.teamColor = newTeamColor;
		operations.addPlayer(player);
	}

	operations.playerDead = function(player) {
		const teamColor = player.teamColor,
		teamPalyers = players[teamColor];

		let all_dead = true;
		for(let i =0,length = teamPalyers.length;i<length ;i++) {
			if (player.state !== 'dead') {
				all_dead = false;
				break;
			}
		}

		if (all_dead) {
			for (let i=0,length=teamPalyers.length;i<length;i++) {
				player.lose();
			}
			for (let color in players) {
				if (color !== teamColor) {
					const teamPalyers = players[color];
					for (let i=0,length=teamPalyers.length;i<length;i++) {
						let player = teamPalyers[i];
						player.win();
					}
				}
			}
		}
	}

	const ReceiveMessage = function() {
		const message = Array.prototype.shift.call(arguments);
		operations[message].apply(this,arguments);
	}
	return {
		ReceiveMessage
	}
})();


const player1 = playerFactory('皮蛋','red');
const player5 = playerFactory('黑妞','blue');

player1.die();

JavaScript深入系列之闭包

闭包

函数在定义时的词法作用域以外的地方被调用,并且函数可以继续访问定义时的词法作用域。

模块

模块模式必要条件:
1.必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。
2.封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。

function CoolModule() {
    var something = "cool";
    var another = [1,2,3];
    
    function doSomething() {
        console.log(something);
    }
    function doAnother() {
        console.log(another.join(" ! "));
    }
    
    return {
        doSomething: doSomething,
        doAnother: doAnother    
    };
}
var foo = CoolModule();

foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3

这个模式在JavaScript中被称为模块。最常见的实现模块模式的方法通常被称为模块暴露,这里展示的是其变体。

AMD的模块机制(requirejs)

大多数模块依赖加载器/管理器本质上都是将这种模块定义封装进一个友好的API。

var myModules = (function Manager(){
    var modules = [];
    
    function define(name,deps,impl) {
        for(var i=0; i<deps.length; i++) {
            deps[i] = modules[deps[i]];
        }
        modules[name] = impl.apply(impl,deps);
    }
    
    function get(name) {
        return modules[name];
    }
    
    return {
        define: define,
        get: get
    };
})();

这段代码的核心是 modules[name] = impl.apply(impl,deps) 为了模块的定义引入了包装函数(可以传入任何依赖),并且将返回值,也就是模块的API,储存在一个根据名字来管理的模块列表中。

使用以上函数来定义模块

MyModules.define("bar",[],function(){
    function hello(who) {
        return "Let me introduce:" + who;
    }
    return {
        hello: hello
    };
});
MyModules.define("foo",["bar"],function(){
    var hungry = "hippo";
    
    function awesome() {
        console.log(bar.hello(hungry).toUpperCase());
    }
    return {
        awesome: awesome
    };
});

var bar = MyModules.get("bar");
var foo = MyModules.get("foo");

console.log(
    bar.hello("hippo");
); // Let me introduce: hippo
foo.awesome(); // LET ME INTRODUCE: HIPPO

CMD 的模块机制(seajs)

CMD 依赖就近,延迟执行。

// CMD
define(function(require, exports, module) {
  var a = require('./a');
  a.doSomething();
  var b = require('./b');
  b.doSomething();
})

UMD 的模块机制

异步加载,异步执行,兼容服务器、浏览器端

CommonJS 的模块机制

  • 一个单独的文件就是一个模块
  • 加载模块采用同步方式,加载完成后才能执行后面的操作
  • 加载模块使用require方法,该方法读取一个文件并执行,最后返回内部的exports对象

ES6的模块机制

ES6将文件当作独立的模块处理。

bar.js

function hello(who) {
    return "Let me introduce:" + who;
}

export hello;

foo.js

// 仅从"bar"模块导入hello()
import hello from "bar";

var hungry = "hippo";

function awesome() {
    console.log(
      hello(hungry).toUpperCase()  
    );
} 
export awesome;

baz.js

module foo from "foo";
module bar from "bar";

console.log(
    bar.hello("rhino");  
); // Let me introduce: rhino
foo.awesome(); // LET ME INTRODUCE: HIPPO

模块文件中的内容会被当作好像包含在作用域闭包中一样来处理,就和前面介绍的函数闭包模块一样。

设计模式

在面向对象软件设计过程中针对特定问题简洁,优雅的解决方式。

设计原则

  • 开闭原则:对扩展开放,对修改关闭(高考试卷不能修改试卷,加附加题)
  • 里氏转换原则:子类继承父类(盗版光盘copy正版光盘内容)
  • 依赖倒转原则:引用一个对象,如果这个对象有底层类型,直接引用底层
  • 接口隔离原则(单一职责原则):每个接口应该是一中角色(汽车中的USB插口),就一个类(对象或函数)而言,应该仅有一个引起它变化的原因。
  • 合成/聚合复用原则:新的对象应使用一些已有对象,使之成为新对象的一部分(汽车零件,组装成汽车)
  • 迪米特法则(最小知识原则):一个对象应对其它对象有尽可能少得了解

创建型

对象实例化的模式,创建型模式用于解耦对象的实例化过程。

结构型

把类和对象结合在一起形成一个更大的结构。

行为型

类和对象如何交互,及划分责任和算法。

并发型

待补充

线程池

待补充

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.