Giter VIP home page Giter VIP logo

light-tips's Introduction

Light Tips

License

记录Web开发所需要的一些基础知识,主要是PHP、MySQL、Javascript相关内容,还有一些基础的算法和数据结构。

收藏请点star,如发现有错误欢迎PR

数据结构和算法

算法

算法 最快时间复杂度 平均时间复杂度 最坏时间复杂度 空间复杂度 是否稳定
冒泡排序 Ω(n) Θ(n2) O(n2) O(1) 稳定
插入排序 Ω(n) Θ(n2) O(n2) O(1) 稳定
希尔排序 Ω(nlogn) Θ(n(log(n))2) O(n(log(n))2) O(1) 不稳定
选择排序 Ω(n2) Θ(n2) O(n2) O(1) 不稳定
堆排序 Ω(nlogn) Θ(nlogn) O(nlogn) O(1) 不稳定
归并排序 Ω(nlogn) Θ(nlogn) O(nlogn) O(n) 稳定
快速排序 Ω(nlogn) Θ(nlogn) O(n2) O(logn) 不稳定
基数排序 Ω(n+b) Θ(n+b) O(n+b) O(n+k) 稳定

O表示上界(小于等于)Ω表示下界(大于等于)Θ表示即是上界也是下界(等于)

数据结构

  1. 链表

  2. 队列

  3. 最大堆

《剑指Offer》

C语言

教程

  1. PHPer看的C语言入门教程

Go语言

教程

  1. PHPer看的Go语言入门教程

PHP

新特性

  1. PHP新特性之命名空间、性状和生成器
  2. PHP新特性之闭包、匿名函数
  3. PHP新特性之字节码缓存和内置服务器

最佳实践

  1. PHP最佳实践系列之标准
  2. PHP最佳实践之过滤、验证、转义和密码
  3. PHP最佳实践之日期、时间和时区
  4. PHP最佳实践之数据库
  5. PHP最佳实践之多字节字符串、字符编码
  6. PHP最佳实践之异常和错误
  7. PHP最佳实践之上线准备

Javascript

  1. Javascript引擎内部的三种抽象操作
  2. 彻底弄懂Javascript闭包

MYSQL

  1. MySQL索引相关问题总结

light-tips's People

Contributors

xx19941215 avatar

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  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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 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  avatar  avatar  avatar  avatar  avatar  avatar

light-tips's Issues

写一个爬虫类获取url页面上所有的图片链接按后缀分类存储到txt文件中

class Crawler
{
    public function execute($url)
    {
        $arr = $this->getImgUrlArr($url);
        $typeMap = $this->typeSort($arr);

        $path = __DIR__ . '/imgCat.txt';

        return $this->save($typeMap, $path);
    }

    public function getImgUrlArr($url): array
    {
        $content = file_get_contents($url);
        //主要是这个正则没有在纸上一次正确写出来,所以这里记录下
        $pattern = '/<img.*?src=[\'|\"](.*?)[\'|\"].*?[\/]?>/';
        $matches = [];

        preg_match_all($pattern, $content, $matches);

        if (!empty($matches[1])) {
            return $matches[1];
        }

        return [];
    }

    public function typeSort($imgUrlArr): array
    {
        $typeMap = [];
        foreach ($imgUrlArr as $url) {
            $type = substr($url, strripos($url, '.'));
            if (array_key_exists($type, $typeMap)) {
                $typeMap[$type] = ++$typeMap[$type];
            } else {
                $typeMap[$type] = 1;
            }
        }

        return $typeMap;
    }

    public function save($typeMap, $path): bool
    {
        $ok = file_put_contents($path, var_export($typeMap, true));

        if ($ok) {
            return true;
        }

        return false;
    }
}


$crawler = new Crawler();

$res = $crawler->execute("https://www.baidu.com");

var_dump($res);

PHP最佳实践之异常和错误

异常

1).异常是Exception类的对象,在遇到无法修复的状况时抛出,例如远程API没有响应或者数据库查询失败再或者是无法满足程序运行的前置条件。出现问题的时候异常用于主动出击,委托职责;异常还可以用于防守,预测潜在的问题来减轻影响。
2).Exception对象和其他的PHP对象一样,使用new关键字实例化。

<?php
$exception = new Exception('userId cannot be null', 100);

第一个参数是消息,第二个参数是数字代码。数字代码是可选的,用于为指定的异常提供上下文。我们可以使用公开的实例方法getCodegetMessage来获得异常对象的两个属性。
3).假如遇到了异常情况,或者在当前的条件下无法操作,我们需要抛出异常。

<?php
throw new Exception('Something went wrong.Time for lunch!');

4).我们必须抛出Exception类或者他的子类,PHP内置的异常类和其子类如下:

  • Exception
  • ErrorException
    PHP标准库提供了下述额外的Exception子类,扩展了PHP内置的异常类。
  • LogicException
  • BadFunctionCallException
    • BadMethodCallException
  • DomainException
  • InvalidArgumentException
  • LengthException
  • OutOfBoundsException
  • RuntimeException
  • OutOfBoundsException
  • OverflowException
  • RangeException
  • UnderflowException
  • UnexpectedValueException

5).捕获异常。预测和捕获并处理异常是我们自己的责任,因为未捕获的异常可能会导致PHP应用终止运行,显示错误信息。拦截并处理潜在异常的方式是,把可能抛出异常的代码放在在try/catch块中。

try {
    $pdo = new PDO('mysql://host=wrong_host;dbname=wrong_name');
} catch (PDOException $e) {
    $code = $e->getCode();
    $message = $e->getMessage();
    echo 'Something went wrong.Check back soon, please';
    exit;
}

还可以连续抛出多个异常

try {
  throw new Exception('Not a PDO exception');
  $pdo = new PDO('mysql://host=wrong_host;dbname=wrong_name');
} catch (PDOException $e) {
    echo 'Caught PDO exception';
} catch (Exception $e) {
    //处理其他异常
    echo 'Caught generic exception';
} finally {
    //这里的代码始终都会执行
    echo 'Always do this';
}

捕获某种异常的时候只会允许其中一个catch块,如果PHP没有找到适用的catch块,异常会向上冒泡,直到PHP脚本由于致命的错误而终止。
6).异常处理程序。我们可以使用一个全局的异常处理程序,来捕获所有未被捕获的异常。异常捕获程序都必须接受一个了类型为Exception的参数,异常捕获程序使用set_exception_handler()函数注册。

<?php
set_exception_handler(function (Exception $e) {
    //处理并记录异常
});

//你的代码
...

//还原成之前的异常处理程序
restore_exception_handler();
错误

1).我们可以使用error_reporting()函数或者在php.ini文件中使用error_reporting指令告诉PHP报告或者忽略那些错误。这两种都是使用E_*常量来确定。
2)错误报告方式四原则:

  • 一定要让PHP报告错误
  • 在开发环境中要显示错误
  • 再生产环境中不能显示错误
  • 在开发和生产环境中都要记录错误

3)一种php.ini配置的例子:
开发环境:

;显示错误
display_startup_errors = On
display_errors = On
;报告所有错误
error_reporting = -1
; 记录错误
log_errors = On

生产环境:

;不显示错误
display_startup_errors = Off
display_errors = Off
;除了注意事项外,报告所有错误
error_reporting = E_ALL & ~E_NOTICE
; 记录错误
log_errors = On

4).注册全局的错误处理程序:set_error_handler()函数。

<?php
set_error_handler(function($errno, $errstr, $errfile, $errline) {
    //处理错误
    //$errno表示错误等级对应E_*常量
    //$errcontext是一个省略的参数,高级调试才用到
});

5.一个简单的全局错误处理程序的例子:

set_error_handler(function($errno, $errstr, $errfile, $errline) {
   if (!(error_reporting() & $errno)) {
    //error_reporting指令没有设置这个错误,所以忽略
    return;  
  }
  throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
});

//其他代码

//还原成之前的错误处理程序
restore_error_handler();
相关处理组件
  • 开发环境: filp/whoops
  • 生产环境: monolog/monolog

PHP最佳实践之数据库

PDO扩展

PHP原生提供了PDO扩展,意思是PHP数据对象。

数据库链接和DSN

DSN是指数据源名称,提供数据库链接的详细信息。一般包含以下信息:

  • 主机名或者IP地址
  • 端口号
  • 数据库名
  • 字符集

以上信息构成的DSN可以用于PDO类构造函数的第一个参数,第二个和第三个参数分别是数据库的用户名和密码。如果数据库需要认证,则需要提供这两个 参数。

<?php
try {
    $pdo = new PDO(
        'mysql:host = 127.0.0.1;dbname=books;port=3306;charset=utf8',
        'USERNAME',
        'PASSWORD'
    );
} catch(PDOException $e) {
    echo "Database connection failed";
    exit;
}
保证密码凭证的安全

把数据库凭证保存在一个位于文档根目录之外的配置文件中,然后在需要的文件中导入这个文件。

预处理语句

在SQL中使用用户的输入时,一定要过滤。因此需要使用PDO扩展的预处理语句和参数绑定,这项操作非常简单。预处理语句是PDO对象的实例,不过我们很少直接去实例化这个类,而是通过PDO实例的prepare方法获得预处理语句的对象。这个方法得第一个参数是一个sql语句字符串,返回值是一个PDOStatement实例:

<?php
$sql = 'SELECT id FROM users WHERE email = :email';
$statement = $pdo->prepare($sql);

在这个SQL语句中,email这个值可以安全的绑定任何值。

<?php
$sql = 'SELECT id FROM users WHERE email = :email';
$statement = $pdo->prepare($sql);
$email =  filter_input(INPUT_GET, 'email');
$statement->bindValue(':email', $email);

预处理语句会自动过滤$email的值。PDOStatement的第三个参数可以制定绑定值的类型,不填的话默认就是字符串类型。可选的常量类型如下

  • PDO::PARAM_BOOL
  • PDO::PARAM_NULL
  • PDO::PARAM_INT
  • PDO::PARAM_STR(默认值)
查询结果

如果执行的是select方法,返回的数据我们需要使用fetch()、fetchAll()、fetchColumn()和fetchObject()方法获取查询结果。

//把预处理语句获得的结果当成关联数组处理
$sql = 'SELECT id, email FROM users WHERE email = :email';
$statement = $pdo->prepare($sql);
$email = filter_input(INPUT_GET, 'email');
$statement->bindValue(':email', $email);
$stament->execute();
//迭代结果
while(($result = $statement->fetch(PDO::FETCH_ASSOC)) !== false) {
    echo $result['email'];
}

fetch、fetchAll方法的参数可选

  • PDO::FETCH_ASSOC:返回一个关联数组
  • PDO::FETCH_NUM:返回一个键为数字的数组,数组的键是数据库列在查询结果中的索引
  • PDO::FETCH_BOTH:返回一个既有键为列名又有键为数字的数组。
  • PDO::FETCH_OBJ:让fetch()和fetchAll()返回一个对象,对象的属性是数据库的列名。
    不推荐使用fetchAll(),除非你可以十分确定可用内存放得下整个查询结果。如果只关心查询结果中的一列,可以使用fetchColumn()方法,这个方法的作用和fetch()方法类似,返回查询结果中下一行的某一列,该方法只有一个参数用于制定所需要的索引。
// 构建并执行SQL查询
$sql = 'SELECT id, name FROM users WHERE email = :email';
$statement = $pdo->prepare($sql);
$email = filter_input(INPUT_GET, 'email');
$statement->bindValue(':email',  $email);
$statement->execute();

while(($emal = $statament->fetchCoulmn(1)) !== false) {
    echo $email;
}

因为在SQL语句中,email出现在了第二个字段的位置,所以这里使用索引1取出来。我们还可以使用fetchObj()方法来获取查询结果中的行,这个方法把行行当成对象,对象的属性是查询结果中的列。

// 构建并执行SQL查询
$sql = 'SELECT id, name FROM users WHERE email = :email';
$statement = $pdo->prepare($sql);
$email = filter_input(INPUT_GET, 'email');
$statement->bindValue(':email',  $email);
$statement->execute();

while(($result = $statament->fetchObj()) !== false) {
    echo $result->email;
}
事务

事务是指把一系列数据库语句当成单个的逻辑执行单元执行,也就是说事务中的一系列SQL查询要么都成功,要么不执行。事务的原子性能保证数据的一致性、安全性和持久性。事务还有一个很好的副作用就是提升性能,因为事务是把多个查询排成队列,一次性全部执行。

PHP新特性之字节码缓存和内置服务器

Zend OPcache

1).从PHP5.5开始,内置了字节码缓存功能,名为Zend OPcache。因为PHP是解释性语言,PHP解释器执行PHP脚本时会解析PHP脚本代码,生成一系列的Zend操作码,然后执行字节码,每次的HTTP请求都是这样,会消耗很多资源,使用字节码缓存可以缓存预先编译的字节码,减少响应时间,降低系统资源的压力。

启用Zend OPcache

默认情况之下,Zend OPcache是没有启动的。如果是自己编译PHP,执行的时候命令必须包含以下选项:

--enable-opcache

编译好PHP之后,还必须在php.ini文件中指定Zend OPcache的扩展路径,如下所示:

zend_extension=/path/to/opcache.so

PHP编译成功之后会立即显示Zend OPcache扩展的文件路径。可以使用下面的命令找到这个PHP扩展的路径

php-config --extension-dir

然后使用下面的代码可以确认该扩展运行正常

<?php
phpinfo();
配置Zend OPcache

推荐配置

opcache.validate_timestamps = 1 //在生产环境中设为'0'
opcache.revalidate_freq = 0
opcache.memory_comsumption = 64
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 4000
opcache.fast_shutdown = 1

可以访问PHP官方网站查看详细设置。

使用Zend OPcache

1).生产环境可以设置opcache.validate_timestamps=0。在开发环境中需要设置为1

内置的HTTP服务器

1).启动php -S localhost:4000 -c app/config/php.ini
2).不支持.htaccess文件。意味着不支持控制器模式。前端控制器用来转发所有的HTTP请求,需要通过.htaccess文件或重写规则实现。
3).可以使用路由器脚本实现以上功能。但是只支持少量的URL重写规则php -S localhost:8000 router.php
4).判断使用的是哪个服务器:

<?php
if (php_sapi_name() === 'cli-server') {
    //php内置服务器
} else {
    // 其他Web服务器
}

PHP新特性之命名空间、性状和生成器

1.命名空间

命名空间是什么?

1).命名空间在PHP 5.3中被引入,类似于文件夹的功能。例如Symfony框架中的Request和Response,位于Symfony的命名空间下。
2).命名空间始终应该在<?php标签的下面一行。
3).PHP文件的命名空间和操作系统的物理文件系统不同,这是一个虚拟的概念,没有必要和文件系统的目录结构完全对应。虽然如此,绝大多数PHP组件为了兼容广泛使用的PSR4自动加载标准,会把子命名空间放到文件系统的子目录中去。
4).命名空间只是PHP语言的一种记号,PHP解释器会将这种记号作为前缀添加到类、接口、函数和常量的名称前面。

为什么需要命名空间?

1).命名空间使得程序可以像沙盒一样运行,可以和其他开发者编写的代码一起使用。确保了自己的代码和项目可以和项目的第三方依赖一起使用。

声明命名空间

1).顶层命名空间经常用于设定顶层厂商名。
2).厂商的命名空间必须具有全局唯一性,子命名空间就没有那么重要,但有助于组织项目的代码。

导入和别名

1).从PHP5.3开始可以导入PHP类、接口和其他命名空间,并为其创建别名。从PHP5.6开始可以导入PHP函数和常量,并为其创建别名。
2).使用use关键字导入代码时无须在开头加上\符号,因为PHP假定导入的是完全限定命名空间。use关键字必须出现在全局作用域中即不能出现在类或者函数中,因为这个关键字是在编译的时候使用的,不过,use关键字可以在命名空间声明语句后使用,导入其他命名空间的代码。
从PHP5.6开始我们可以导入函数和常量。

<?php
use func Namespace\functionName;

functionName();

也可以导入常量,

use constant Namespace\CONS_NAME;
echo CONS_NAME;

函数和常量的别名与类名的创建方式一样。

最佳实践

1).PHP允许在一个PHP文件中定义多个命名空间。但是这么做容易让人困惑,违背了一个文件一个类的良好实践。
2).在一个命名空间中引用全局的命名空间的代码时,需要加上\前缀,告诉PHP需要在全局中查找该类,例如PHP原生的异常类。

自动加载

1).命名空间为PHP-FIG制定的PSR4自动加载器奠定了坚实的基础。

2.使用接口

1).就像我可以选择开不一样的车。因为他们都有方向盘、油门和刹车,并且燃料都是汽油。

3.性状

1).形状是类的部分实现(常量、属性和方法),可以混入一个或者多个现有的PHP类中,性状有两个作用,表明类可以做什么(类似接口),提供模块化实践(类似类)。
2).性状使得两个无关的类可以使用相同属性和方法。
3).PHP解释器会把性状复制粘贴到类的定义体中。

4.创建生成器

1)在普通函数中一次或者多次使用yield关键字,不返回值,只生成值,这个函数就是一个生成器。例如:

<?php
function myGenerator() {
    yield 'value1';
    yield 'value2';
}

调用生成器函数的时候,PHP会返回一个属于Generator类的对象,这个对象可以使用foreach()函数迭代,每次迭代,PHP会要求这个对象的实例计算并提供下一个要迭代的值,生成器的优雅之处就是在每产出一个值之后,生成器内部状态会一直停顿和恢复之间切换,直到抵达定义体的末尾或者遇到空的return;语句为止,例如:

<?php
foreach (myGenerator() as $yieldedValue) {
    echo $yieldedValue, PHP_EOL;
}

以上例子会输出

value1
value2

2).生成器是如何节约内存的?生成一个范围内的数值(错误方式)

function makeRange($length) {
    $dataset = [];
    for ($i=0; $i < $length; $i++) {
      $dataset[] = $i;
	}
    return $dataset;
}

$customRange = makeRange(1000000);
foreach ($customeRange as $i) {
    echo $i, PHP_EOL;
}

预先创建了一个包含很大整数组成的数组,再看使用生成器的例子。

function makeRange($length) {
    for ($i = 0; $i < $length; $i++) {
        yield $i;
    }
}

foreach(makeRange(1000000) as $i) {
    echo $i, PHP_EOL;
}

在实际的例如迭代一个4GB大小的文件中功能中,迭代器大展身手。

function getRows($file) {
    $handle = fopen($file, 'rb');
    if ($handle === false) {
        throw new Exception();
    }
    //feof()函数检测是否到达文件末尾
    while (feof($handle) === false) {
        //fgetcsv()一次读取csv文件的一行
        yield fgetcsv($handle);
    }
    fclose($handle)
}

foreach (getRows('data.csv') as $row) {
    print_r($row);
}

3).生成器没有为PHP添加新功能,需要实现在数据集中执行快进、快退和查找,最好自己编写类实现Iterator接口,或者使用PHP标准库中的某个原生迭代器。
原生迭代器链接

有如下结构表,请写出符合条件的SQL

product表
product_id
product_status
product_price

category表
category_id
category_name

product_category表
product_id
category_id

找出品类为Shoes的产品,运行sql得出如下结构的数据 第一行为价格范围

状态 [0, 10] [10,20] [20,30] [30,40]
上架 10 10 10 10
下架 10 10 10 10
其他 10 10 10 10

PHP最佳实践之多字节字符串、字符编码

多字节字符串

1).PHP假设字符串中的每一个字符都是八位字符,占用一个字节的内存。但是,你有可能会遇到多字节字符串。
2).这里所说的多字节字符串是指不在传统的128个ASCII字符集中的字符。如果使用PHP原生的字符串函数处理这些多字节Unicode字符串,会得到意外的错误。
3).安装mbstring扩展来处理这些问题。例如使用处理多字节的字符串函数mb_strlen()函数来替代原生的strlen()函数。

字符编码

1).一定要知道数据的字符编码。
2).使用UTF-8字符编码存储数据。
3).使用UTF-8字符编码输出数据。
4).mbstring扩展不仅可以处理Unicode字符串,还可以在不同的字符编码之间转换多字节字符串。

输出UTF-8数据

1).在php.ini中作如下配置

default_charset = "UTF-8";

2)例如header函数明确指定字符集,在PHP返回的响应中,Content-Type首部默认也使用了这个默认值

<?php
header('Content-Type: application/json;charset=utf-8');

3).只要PHP已经明确返回了输出,就不能使用header()函数了
4).建议在HTML文档的头部也加入响应的meta标签

<meta charset="UTF-8"/>

PHP最佳实践之日期、时间和时区

1).自己处理很容易出错,建议使用从PHP5.2之后新增的DateTime、DateInterval和DateTimeZone类。
2).使用date_default_timezone_set('Asia/Shanghai');设置默认时区为**时区,或者你也可以在php.ini文件中配置。
3).使用DateTime管理时间和日期:

<?php
//没有传入参数返回当前日期和时间的实例
$datetime = new DateTime();
//传入符合规范的时间格式
$datetime = new DateTime('2017-07-14 9:19 AM')
//有时我们必须处理那些不符合规范的时间格式t
$datetime = DateTime::createFromFormat('M j, y H:i:s', 'Jul 14, 2017 09:19:20');

4).DateTime::createFromFormate()静态方法使用的日期格式与date()一样。可以的日期和格式可以参见http://php.net/manual/zh/datetime.createfromformat.php
5).使用DateInterval偏移时间:

<?php
$datetime = new DateTime('2017-07-14 14:00:00');
$interval = new DateInterval('P2W');
$datetime->add($interval);
echo $datetime->format('Y-m-d H:i:s');

有效的周期标志如下:

  • Y(年)
  • M(月)
  • D
  • W
  • H
  • M(分)
  • S
    间隔的周期中M即表示月,又表示分。所以怎么区分呢?前3个表示日期,后面的表示时间,这就需要用字母T来分隔。可以使用T2M表示间隔两秒。
$dateStart = new \DateTime();
$dateInterval = DateInterval::createFromDateString('-1 day');
$datePeriod = new DatePeriod($dateStart, $dateInterval, 3);
foreach ($datePeriod as $date) {
    echo $date->format('Y-m-d'), PHP_EOL;
}

5).DateTimeZone类:

<?php
$timezone = new DateTimeZone('Asia/Shanghai');
$datetime = new DateTime('2017-07-14', $timezone);
//使用setTimeZone()方法修改DateTime实例的时区
$dateTIme->setTimezone(new DateTimeZone('Asia/Hongkong'));

最好是一直使用UTC时间。服务器使用,自己开发默认也是,然后存入数据库也是,这样的话把数据显示给用户看的话转换为适当时区的日期和时间就行了。
6).上面说到的DatePeriod类适合在迭代处理一段时间内反复出现的一系列时期和时间,重复在日程表中记事就是一个很好的例子。
7).nesbot/carbon组件是一个不错的时间组件

PHP最佳实践之上线准备

配置

1).如果你使用的不是Paas,那么先配置VPS或者专用的服务器才能运行PHP应用。本文假设你会使用vim或者nano编辑器。
2).我们选择nginx服务器来作为我们的web服务器。

首次登录

注意要把ip换成你自己的VPS的IP。
13.png

我这里使用的是vultr的VPS,这个VPS服务商是按小时收费的。这意味着,架设一台VPS的成本几乎为零。你可以点击这里注册

升级软件

apt-get update
apt-get upgrade

这一步很重要,因为这能保证系统中默认的软件安装了最新的更新和安全修补。

14.png

15.png

输入y然后敲回车继续

非根用户

现在你的新服务器还是不太安全,因为我们必须要避免使用根用户。我们可以创建一个叫xiao的非根用户。密码必填,其他信息不是必填项,敲回车继续。

adduser xiao

16.png

接下来把他加入sudo用户组,使他拥有sudo权限。

usermod -G sudo xiao

17.png

SSH 密钥对认证

你在本地想访问VPS的时候推荐你使用密钥认证这种方式。简单来说,就是我们创建一对密钥,其中一个是私钥保存在本地设备中,另一个是公钥,传到你的VPS中,之所以叫做密钥对,是因为使用公钥加密的 消息只能使用对应的私钥解密。
使用SSH连接远程设备的时候,远程设备会随机创建一个消息,使用公钥加密之后把密文发送给本地设备,本地设备收到密文之后使用私钥解密,然后把解密后的消息发送给远程的服务器,远程的服务器验证了解密之后的消息,赋予你访问的权限。

ssh-keygen
scp ~/.ssh/id_rsa.pub [email protected]:

第一个命令执行成功你的~/.ssh 目录会有如下文件
18.png
然后使用scp上传到远程服务器
19.png

然后登陆你的VPS确认这个~/.ssh/目录是否存在。

20.png

如果不存在则执行下面的命令

mkdir ~/.ssh
touch ~/.ssh/authorized_keys

这个文件是一系列允许登陆这台VPS的公钥。然后将上传的公钥复制到这个文件中

cat ~/id_rsa.pub >> ~/.ssh/authorized_keys

现在使用下面的命令修改一下目录权限。

chown -R xiao:xiao ~/.ssh
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

21.png

禁止密码和根用户登陆

修改/etc/ssh/sshd_config文件。将PasswordAuthentication设置为 no,然后将PermitRootLogin设置为no。
我是用的是vim,你可以使用你会的编辑器。

23.png

24.png

25.png

sudo service ssh restart

重启ssh使修改生效。
26.png

至此为止,我们的PHP部署服务器已经准备好,下一篇将会介绍PHP-FPM安装和配置,包括全局配置和进程池配置。

专题系列

PHP专题系列目录地址:https://github.com/xx19941215/webBlog
PHP专题系列预计写二十篇左右,主要总结我们日常PHP开发中容易忽略的基础知识和现代PHP开发中关于规范、部署、优化的一些实战性建议,同时还有对Javascript语言特点的深入研究。

彻底弄懂Javascript闭包

基础概念

什么是闭包

简单来说,闭包是指可以访问另一个函数作用域变量的函数,一般是定义在外层函数中的内层函数。

为什么需要闭包呢

局部变量无法共享和长久的保存,而全局变量可能造成变量污染,所以我们希望有一种机制既可以长久的保存变量又不会造成全局污染。

特点
  • 占用更多内存
  • 不容易被释放
何时使用

既想反复使用,又想避免全局污染

如何使用

1.定义外层函数,封装被保护的局部变量。
2.定义内层函数,执行对外部函数变量的操作。
3.外层函数返回内层函数的对象,并且外层函数被调用,结果保存在一个全局的变量中。

函数生命周期

直接上图,点击图片方法查看。要记住函数对象、作用域链对象、执行环境(EC)和活动对象(AO)这几个东西都啥时候出现,啥时候消失。

函数生命周期

例子实战

看下面这个函数,函数对象的地址仅作标识,不代表真实的地址。

var getNum;//------------------------1
function getCounter() { // ----------2
    var n = 1; 
    var inner = function () { return n++; }
    return inner;
}

getNum = getCounter();//------------3
console.log(getNum()); //1 ---------4
console.log(getNum()); //2 ---------5

程序运行到2的时候:

运行到2

程序运行到3的时候:

运行到3

运行到4的时候,外层函数调用结束,AO对象释放,图中红线断了:

4处的代码执行前

4处的代码执行时:

执行4处的代码

4处的代码执行完:

4处代码执行完

总结:可以看到内层函数对象被全局的变量getNum引用,所以内层函数相关的AO对象,作用域链对象在函数调用完都无法被内存回收,因此占用了更多的内存空间,但是这样持久的保存了需要的n

Javascript引擎内部的三种抽象操作

很久之前我在知乎上问过一道很无聊的Js题目,链接在这里JS中{}+[]和[]+{}的返回值情况是怎样的?,这篇文章要说的和这个题目有一些关系。下面介绍一下Js引擎内部的三种抽象操作。

1.通过ToPrimitive()将值转换为原始值。
JavaScript引擎内部的抽象操作ToPrimitive()有着这样的签名:
ToPrimitive(input, PreferredType)

可选参数PreferredType可以是Number或者String,它只代表了一个转换的偏好,转换结果不一定必须是这个参数所指的类型,但转换结果一定是一个原始值.如果PreferredType被标志为Number,则会进行下面的操作来转换输入的值:

①如果输入的值已经是个原始值,则直接返回它.否则,如果输入的值是一个对象.则调用该对象的valueOf()方法.如果valueOf()方法的返回值是一个原始值,则返回这个原始值.否则,调用这个对象的toString()方法.如果toString()方法的返回值是一个原始值,则返回这个原始值.否则,抛出TypeError异常.

②如果PreferredType被标志为String,则转换操作的第二步和第三步的顺序会调换.如果没有PreferredType这个参数,则PreferredType的值会按照这样的规则来自动设置:Date类型的对象会被设置为String,其它类型的值会被设置为Number.

2.通过ToNumber()将值转换为数字
下面的表格解释了ToNumber()是如何将原始值转换成数字的

ToNumber()转换规则
如果输入的值是一个对象,则会首先会调用ToPrimitive(obj, Number)将该对象转换为原始值,然后在调用ToNumber()将这个原始值转换为数字.转型函数Number()会根据此规则执行

3.通过ToString()将值转换为字符串
下面的表格解释了ToString()是如何将原始值转换成字符串

ToString()转换规则

如果输入的值是一个对象,则会首先会调用ToPrimitive(obj, String)将该对象转换为原始值,然后再调用ToString()将这个原始值转换为字符串.转型函数String()会根据该规则执行

理解了Javascript引擎内部的三种抽象操作之后可以看下面的例子:

小例子

这里的转型函数Number会根据2的规定来执行类型转换,由于重写了对象的valueOf和toString函数(之前toString会返回"[object Object]",valueOf会返回该对象),所以Number()根据规则首先调用对象的valueOf方法,没有获得原始值,继续调用toString方法,任然没有获得原始值,这时就会抛出TypeError。

再来看一个例子:即{}+[]和[]+{}的返回值是怎么样?
对于{}+[] 我们将{}理解为一个空的代码块,所以问题就变成了 +[] 的结果,这里的加号并不是代表加法的二元运算符,而是一个一元运算符,作用是将它后面的操作数转换成数字,和Number()函数作用完全一样,即求Number([])的结果是多少?[]继承自Array,Array对象的的valueOf方法直接返回该数组并不是原始值,继续调用toString方法,得到""即一个空的字符串。那么Number("")的结果就是0,所以{}+[]的结果就是0。而[]+{}的结果呢?你可以参考《Javascript高级程序设计》的3.5.5加性操作符一章和本文的相关说明获得答案是"[object Object]"。

文章的最后总结一些类型分别调用valueOf、toString和toLocaleString得到的结果:
相关总结

buildHeap直接生成即可,严谨的$i =floor($length /2) -1

require_once DIR . '/common.php';

function heapSort(&$arr)
{
$length = count($arr);
$heapSize = $length - 1;
for ($i = floor($length / 2) - 1; $i >=0; $i--) {
heapify($i, $heapSize, $arr);
}
for ($i = $heapSize; $i >= 0; $i--) {
list($arr[0], $arr[$heapSize]) = [$arr[$heapSize], $arr[0]];
$heapSize--;
//再把剩下的元素调整成最大堆 ,-1就是求下一个节点
heapify(0, $heapSize, $arr);
//$heapSize 可以用$i替代, heapify (0, $heapSize, $arr); 可用$i-1 替代
}
return $arr;
}

function heapify(int $k, int $heapSize, array &$arr)
{
$largest = $k;
$left = 2 * $k + 1;
$right = 2 * $k + 2;
if ($left <= $heapSize && $arr[$k] < $arr[$left]) {
//跟左侧节点比,顶点比左侧小,则换
$largest = $left;
}
if ($right <= $heapSize && $arr[$largest] < $arr[$right]) {
//跟右侧节点比,顶点比右侧小,则换
$largest = $right;
}
if ($largest != $k) {
//如果最大点不是顶点,则交互并继续排序
list($arr[$largest], $arr[$k]) = [$arr[$k], $arr[$largest]];
heapify($largest, $heapSize, $arr);
}
}

PHP新特性之闭包、匿名函数

闭包

闭包是什么?

1).闭包和匿名函数在PHP5.3中被引入。
2).闭包是指在创建时封装函数周围状态的函数,即使闭包所在的环境不存在了,闭包封装的状态依然存在,这一点和Javascript的闭包特性很相似。
3).匿名函数就是没有名称的函数,匿名函数可以赋值给变量,还可以像其他任何PHP对象一样传递。可以将匿名函数和闭包视作相同的概念。
4).需要注意的是闭包使用的语法和普通函数相同,但是他其实是伪装成函数的对象,是Closure类的实例。闭包和字符串或整数一样,是一等值类型。

创建闭包
$closure = function ($name) {
  return sprintf('hello %s', $name);
};

echo $closure('Josh');

我们之所以可以调用$closure变量,是因为这个变量的值是一个闭包,而且闭包对象实现了__invoke()魔术方法,只要后面跟着(),PHP就会查找__invoke()方法。这里简单解释下这个魔术方法:

class testClass
{
    public function __invoke
    {
        print "hello world";
    }
}
$n = new testClass;
$n();

PHP自从5.3版以来就新增了一个叫做__invoke()的魔术方法,使用该方法就可以在创建实例后,直接调用对象。

何时使用?

我们通常把PHP闭包当做函数和方法的回调使用。很多PHP函数都会用到回调函数,例如array_map()preg_replace_callback()

$numbersPlusOne = array_map(function($number) {
    return $number + 1;
}, [1, 2, 3]);
如何理解附加状态?

1).注意PHP闭包不会真的像JS一样自动封装应用的状态,在PHP中必须调用闭包对象的bindTo方法或者使用use关键字,把状态附加到PHP闭包上。来看一个例子

function enclosePerson($name)
{
    return function ($doCommand) use ($name) {
        return sprintf('%s , %s', $name, $doCommand);
   };
}
//把字符串“Clay”封装在闭包中
$clay = enclosePerson('Clay');
//传入参数,调用闭包
echo $clay('get me sweat tea!'); // Clay, get me sweat tea!

在这个例子中,函数enclosePerson()有一个$name参数,这个函数返回一个闭包对象,这个闭包封装了$name参数,即便返回的对象跳出了enclosePerson()函数的作用域,它也会记住$name参数的值,因为$name变量仍然在闭包中。
2).使用use关键字可以把多个关键字传入闭包,此时要想像PHP函数或方法的参数一样,使用逗号分割多个参数。
3).PHP闭包仍然是对象,可以使用$this关键字获取闭包的内部状态。闭包的默认状态里面有一个__invoke()魔术方法和bindTo()方法。
4).bindTo()方法为闭包增加了一些有趣的东西。我们可以使用这个方法把Closure对象内部状态绑定到其他对象上。bindTo()方法的第二个参数可以指定绑定闭包的那个对象所属的PHP类,这样我们就可以访问这个类的受保护和私有的成员变量。看下面的代码示例:

class App
{
    protected $route = array();
    protected $responseStatus = '200 OK';
    protected $responseContentType = 'text/html';
    protected $responseBody = 'Hello world';

    public function addRoute($routePath, $routeCallback)
    {
        $this->routes[$routePath] = $routeCallback->bindTo($this, __CLASS__);
    }

    public function dispatch($currentPath)
    {
        foreach($this->routes as $routePath => $callback) {
            if ($routePath === $currentPath) {
                 $callback();
            }
        }
        header('HTTP/1.1' . $this->responseStatus);
        header('Content-type: ' . $this->responseContentType);
        header('Content-length: ' . mb_strlen($this->responseBody));
        echo $this->responseBody;
    }
}

我们把路由回调绑定到了当前的App实例上,这样就可以在回调函数中处理App实例的状态了。

$app = new App();
$app->addRoute('/users/xiaoxiao', function () {
    $this->responseContentType = 'application/json;charset=utf8';
    $this->responseBody = '{"name" : "xiaoxiao"}';
});
$app->dispatch('/users/xiaoxiao');

PHP最佳实践系列之标准

PHP-FIG

1).即PHP Framework Interop Group。这个组织发布推荐的规范,而不是强制规范。
2).PHP-FIG的使命是实现框架的互操作性。框架的互操作性指的是通过自动加载机制、接口和标准的风格,让框架互相合作。
3).框架之间通过接口、自动加载和标准的编码风格进行合作。

PSR

1).即PHP推荐标准。这是上面的组织制定的推荐规范。例如下面的规范:

  • PSR-1:基本的代码风格
    • 必须把PHP代码放在<?php ?><??>标签中。
    • 编码字符集必须是UTF-8
    • 一个PHP文件可以定义类或者处理数据,但是不能同时做这两件事
    • 遵循自动加载
    • 类命名遵循驼峰式命名
    • 常量大写
    • 方法名称使用第一个字母小写的驼峰式命名
  • PSR-2:严格的代码风格
    • 贯彻执行PSR-1
    • 使用四个空格缩进
    • 使用UNIX风格换行符
    • 不能使用关闭标签
    • 每一行不能超过120个字符
    • PHP关键字一律小写
    • 命名空间之后必须跟一个空行
    • 类的定义体起始括号应该在类命后新起一行,类的结束括号也必须新起一行
    • 方法名的括号换行方式和类一直,参数除了第一个参数前面要有空格。
    • 必须声明属性和方法的可见性
    • 控制结构关键字后面的起始括号应该和控制结构的关键字写在同一行,结束括号另起一行。例子如下
<?php
$gorilla = new \Animals\Gorilla;
$libs = new \Animals\StrawNeckedIbis;
if ($gorilla->isAwake() === true) {
         do {
             $gorilla->beatChest();
         } while ($libs->isAsleep() === true);
         $libs->flyAway();
}
- 可以使用php code sniffer检查代码格式 
  • PSR-3:日志记录接口
    • 该规范不是一系列方针,而是一个接口,规定了PHP日志记录器组件可以实现的方法。
  • PSR-4:自动加载
    • 这个规范描述了一个标准的自动加载策略。自动加载策略是指在运行时按需查找PHP类、接口或者形状,并将其载入PHP解析器,支持PSR-4自动加载器标准的PHP组件和框架使用同一个自动加载器就能找到相关代码,然后将其载入PHP解释器。
    • 该规范的精髓是把命名空间的前缀和系统中的目录对应起来。
    • 自己实现psr-4自动加载器
<?php
spl_autoload_register(function ($class) {
    //这个项目的命名空间前缀
    $prefix = 'Foo\\Bar\\';
    //这个项目命名空间前缀对应的基目录
    $base_dir = __DIR__ . '/src/';
    //参数传入的类使用这个命名空间前缀吗?
    $len = strlen($prefix);
    if (strncmp($prefix, $class, $len) != 0) {
        //不使用,交给下一个自动加载器处理
        return;
    }
    //去掉前缀的类名
    $relative_class = substr($class, $len);
    //把命名空间前换成基目录
    //去掉前缀中的类名,把命名空间分隔符换成目录分隔符
    //然后在后面加上.php
    $file = $base_dir . str_replace('\\' , '/', $relative_class) . '.php';

    if (file_exists($file)) {
        require $file;
    }
});

这样的话当我们使用 \Foo\Bar\Baz\Qux这个类的时候,使用SPL注册了上面这个函数之后,PHP就会尝试从/path/to/project/src/Baz/Qux.php 加载这个类。

MySQL索引相关问题总结

1.简单描述MySQL中,索引,主键,唯一索引,联合索引的区别?

1.索引类似于书籍的目录。存储引擎中的索引也是如此,查询时会先去索引中找到对应的值,然后根据匹配的值找到对应的行。
2.主键指字段唯一 可以唯一指定一行的字段。
3.唯一索引 指索引列的值必须唯一,但是可以有空值。主键是唯一索引,但是唯一索引不是主键。
4.联合索引 将多个键放在一块创建联合索引,可以覆盖多个列。

2.索引对数据库的影响?

1.大大减少服务器需要扫描的数据量
2.改变随机IO为顺序IO
3.帮助服务器避免排序和临时表

3.索引的类型有哪些?

1.组合索引,即上面的联合索引。
2.外键索引,只有InnoDB类型的表才可以使用外键索引,保证数据的一致性、完整性和实现级连操作。
3.全文索引,MySQL MyISAM引擎中支持,且只支持英文搜索。
4.Mysql 5.6版本之后InnoDB存储引擎开始支持全文索引,5.7版本之后通过使用ngram插件开始支持中文。

4.MySQL索引的注意事项?

1.联合索引遵循从前缀原则。
例如下面的key(a,b,c) 会在SQL例如:
Where a = 1 and b = 2 and c =3
Where a = 1 and b = 2
Where a = 1
Where a = 1 and c = 3

生效 但是下面的SQL不会生效
Where b = 2 and c = 3

2.like查询%不能在前面,这样会全表搜索。
3.column is null 可以使用到索引。
4.如果or前的条件中的列有索引,后面的没有,索引也不会用
5.列类型是字符串,查询的时候一定要加引号,否则索引失效

5.如何优化MySQL查询?
分析查询速度慢的方法

1.记录慢查询日志。可以使用pt-query-digest工具分析。
2.使用show profile
set profiling = 1;服务器上执行的所有语句会检测消耗时间,存放到临时表中。
3.使用show status
show status 会返回一些计数器,show global status 查看服务器级别的所有计数器
有些时候,可以根据这些计数猜测出那些操作代价较高或者消耗的时间多
4.使用show processlist 观察是否有大量的线程处于不正常的状态或者特征
5.使用explaindesc分析单条语句。

优化查询过程中的数据访问

1.访问数据太多导致查询性能低下
2.确定应用程序是否在检索大量超过需要的数据,可能是太多行或者太多的列
3.确认Mysql服务器是否存在分析大量不必要的数据行

避免使用如下的SQL语句

1.查询不需要的记录 使用 limit
2. 多表关联返回全部列,指定A.id A.name B.age
3.总是取出全部列 SELECT * 会让优化器无法完成索引覆盖扫描的优化
4.重复查询想用的数据,可以使用缓存数据。下次读取直接使用缓存
5.查看是否在扫描额外的记录 使用explain的时候,如果发现查询需要扫描大量的数据但只是返回少量的行 k可以使用索引覆盖扫描,把所有的列都放到索引中,这样存储引擎不需要获取对应行就可以把结果返回。
6.修改数据库和表结构,修改数据库符范式,以空间换时间
7.重写SQL语句。让优化器可以更加优化的方式执行查询

优化长难的查询语句

1.切分查询,将一个大的查询分为多个小的查询,一次性删除1000万的数据比一次性删除1万,暂停一会的方案更加损耗服务器开销。
2.分解关联查询,可以将一条管理语句分解成多条SQL语句来执行,让缓存的效率更高,执行单个查询可以,减少锁的竞争,在应用层做关联可以更加容易对数据库进行拆分。

优化特定类型的查询语句

1.优化count查询,count(*)中的*会直接忽略所有的列直接统计所有的列数,因此不要使用count(列名)
MyISAM中,没有任何WHERE条件的count()非常快,当有了WHERE条件,MyISAM的count统计不一定比其他的表引擎快。可以使用explain查询近似值,用近似值代替count()。增加汇总表,增加缓存。

PHP最佳实践之过滤、验证、转义和密码

过滤、验证和转义

1).不要相信任何来自不受自己直接控制的数据源中的数据。包括但不限于:

  • $_GET
  • $_POST
  • $_REQUEST
  • $_COOKIE
  • $argv
  • php://stdin
  • php://input
  • file_get_contents()
  • 远程数据库
  • 远程API
  • 来自客户端的数据

2).解决办法:过滤输入。删除不安全的字符,在数据到达应用的存储层之前,必须过滤数据。需要过滤的数据包括不限于:HTML、SQL查询和用户资料信息。

  • HTML:使用htmlentities()函数过滤HTML成对应的实体。这个函数会转义制定字符的HTML字符,以便在存储层安全的渲染。正确的使用方式是使用htmlentities($input, ENT_QUOTES, 'UTF-8')过滤输入。或者使用HTML Purifier。缺点是慢
  • SQL查询: 有时必须根据数据构建SQL查询。这时要要使用PDO预处理语句过滤外部数据。
  • 用户资料信息:使用filter_var()filter_input()过滤用户资料信息

3).验证数据:也可以使用filter_var(),验证成功返回要验证的值,失败返回false。但是这个函数无法验证所有数据,所以可以使用一些验证功能组件。例如aura/filter或者symfony/validator
4)转义输出:任然可以使用htmlentities这个函数,一些模板引擎也自带了转义功能。

密码

1).绝对不能知道用户的密码。
2).绝对不要约束用户的密码,要限制的话只限制最小长度。
3).绝对不能使用电子邮件发送用户的密码。你可以发送一个修改密码的链接,上面带一个token验证是用户本人就行了。
4).使用bcrypt计算用户密码的哈希值。加密和哈希不是一回事,加密是双向算法,加密的数据可以被解密。但是哈希是单项算法,哈希之后的数据无法被还原,想同的数据哈希之后得到的数据始终是相同的。使用数据库存储通过bcrypt哈希密码之后的值。
5).使用密码哈希API简化计算密码哈希和验证密码的操作。下面的注册用户的一般操作

POST /register.php HTTP/1.1
Content-Length43
Content-type: application/x-www-form-urlencoded

[email protected]&password=nihao

下面是接受这个请求的PHP文件

<?php
try {
    $email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
    if (!$email) {
        throw new Exception('Invalid email');
    }
    $password = filter_iput(INPUT_POST, 'password');
    if (!$password || mb_strlen($password) < 8) {
        throw new Exception('Password must contain 8+ characters');
    }
    //创建密码的哈希值
    $passwordHash = password_hash(
        $password,
        PASSWORD_DEFAULT,
        ['cost' => 12]
     );

    if ($passwordHash === false) {
        throw new Exception('Password hash failed');
    }

    //创建用户账户,这里是虚构的代码
    $user = new User();
    $user->email = $email;
    $user->password_hash = $passwordHash;
    $user->save();
    header('HTTP/1.1 302 Redirect');
    header('Location: /login.php');
} catch (Exception $e) {
    header('HTTP1.1 400 Bad Request');
    echo $e->getMessage();
}

6).根据机器的具体计算能力修改password_hash()的第三个值。计算哈希值一般需要0.1s-0.5s。
7).密码的哈希值存储在varchar(255)类型的数据库列中。
8).登录用户的一般流程

POST /login.php HTTP1.1
Content-length: 43
Content-Type: application/x-www-form-urlencoded

[email protected]&pasword=nihao
session_start();
try {
    $email = filter_input(INPUT_POST, 'email');
    $password = filter_iinput(INPUT_POST, 'password');

    $user = User::findByEmail($email);

    if (password_verify($password, $user->password_hash) === false) {
        throw new Exception(''Invalid password);
    }

    //如果需要的话,重新计算密码的哈希值
    $currentHasAlgorithm = PASSWORD_DEFAULT;
    $currentHashOptions = array('cost' => 15);
    $passwordNeedsRehash = password_needs_rehash(
        $user->password_hash,
        $currentHasAlgorithm,
        $currentHasOptions
    );
    if ($passwordNeedsRehash === true) {
        $user->password_hash = password_hash(
            $password,
            $currentHasAlgorithm,
            $currentHasOptions
        );

        $user->save();
    }

    $_SESSION['user_logged_in'] = 'yes';
    $_SESSION['user_email'] = $email;

    header('HTTP/1.1 302 Redirect');
    header('Location: /user-profile.php');
} catch (Exception) {
    header('HTTP/1.1 401 Unauthorized');
    echo $e->getMessage();
}

9).PHP5.5.0版本之前的密码哈希API无法使用,推荐使用ircmaxell/password-compat组件。

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.