Js 中的数组和对象

示例一:
var x = [1,2];
x.k = 'v';
console.log(x);
console.log(x.length);
结果是:
[1, 2, k: "v"]
2

示例二:
var x = [1, 2];
x.get = function(){
alert('function get');
}
console.log(x);
console.log(x.length);
结果是:
[1,2] --注释1
2
注释1:展开后是:
0:1
1:2
get:function()

示例三:
var x = new Object();
x[0] = 1;
x[1] = 2;
x.get = function(){
alert('function get');
}
console.log(x);
console.log(x.length);
结果是:
Object {0: 1, 1: 2} --注释2
undefined
注释2:展开后是:
0:1
1:2
get:function()

示例四:
var m = [];
var n = new Object();
console.log(m instanceof Array, m instanceof Object);
console.log(n instanceof Array, n instanceof Object);
结果是:
true true
false true

【结论1】:从上面可以看出来,JS中的数组仅仅是一种特殊的对象,对象和数组都可以具有属性和方法。数组具有 length 属性,而对象没有。当数组具有属性或方法时,属性和方法不会让数组的 length 增加。

示例五:
var p = [];
console.log(p.length);
p[100] = 1; --注释3
console.log(p.length);
结果是:
0
101
注释3:或者p['100']

示例六:
var p = [1,2,3,4,5]
console.log(p, p.length);
p.length = 3;
console.log(p, p.length);
p.length = 5;
console.log(p, p.length);

结果是:
[1, 2, 3, 4, 5] 5
[1, 2, 3] 3
[1, 2, 3] 5

【结论2】:当值被分配给大于数组本身长度的索引时,length 会自动增加到新的长度;当 length 变小时,索引超过数组长度的元素将被删除。(恢复length也无法恢复数组元素)

Js小知识点——setTimeout

一:下面看一个腾讯面试题,主要知识是Js的线程问题。(有删减)
var cnt = 0;
for(var i = 0; i < 10; ++i) {
setTimeout(function() {
cnt += i;
}, 0);
}
setTimeout(function(){
console.log(cnt);
}, 0);

由于Js是单线程,遇到循环的时候setTimeout会在循环结束后才执行,所以cnt所加的i总是10,最后cnt的值是100。

var cnt = 0;
for(var i = 0; i < 10; ++i) {
    setTimeout(function() {
        cnt += i;
    }, 0);
}
console.log(cnt);

稍作改动,代码的运行结果是0,很容易理解。

二:向 setTimeout 传递字符串字面量

var s = 1;
function test()
{
    var s = 2;
    setTimeout('console.log(s)', 0);
}
function test2()
{
    var s = 2;
    setTimeout(function(){
        console.log(s);
    }, 0);
}
test();
test2();

传入字符串字面量时,字面量会在全局作用域内被解析,所以上面输出 1 和 2 ;

系统中接口调用的单例化(third_party中的一路财富接口)

场景介绍:
1、以前每次调用接口时,都需要new一个接口对象,再调用接口,这样不仅浪费资源,代码也比较繁琐;
2、理想的调用方法是:接口全静态。这样不仅运行效率高,而且调用方便;
思路整理:
1、首先静态方法无法调用非静态方法,如果把接口静态化,调用日志记录接口时会出现问题(CiLogs);
2、基于第一条,接口全静态的想法不可取。转而求其次,接口全部单例化,用静态类来调用;这样既可以记录日志,调用也非常方便。
改造:
1、首先看third_party代码结构(特指ylcf, xinyi类似):
com ----存放工具类,比如加密、字符串操作函数
entity ----未使用,可以删除
service ----存放了所有接口,按文件分类
config.php ----配置文件,主要是接口的转换
demo.php ----接口测试文件
ylcfClass.php ----引入service并实例化,对外提供统一的接口访问

PS:config.php担负接口管理的职责,但是在service中仍然出现了具体的接口,config没发挥好管理职责。
    更好的做法是,config.php中用键值对(可以是属性名-值,也可以是数组键名-值等等,属性名和键名都算“键”)保存接口,service中只出现config中提供的“键”,不接触接口。

2、把ylcfClass.php所有方法都静态化,用静态属性保存service的实例,保证每个service实例的单一性;

3、把com工具类静态化;

4、以前在 controller 或 model 里面调用接口是这样的:
        include_once("xxxx/ylcfClass.php");
        class A
        {
            ......
            $yc = new YlcfClass();
            $yc->xxx()->yyy();
            ......
        }
    为了美观,我们先创建一个 library: YlcfApiTool.php, 里面使用静态方法,调用 YlcfClass。
    然后在 controller 或 model 里这样调用:
        class A
        {
            public __construst()
            {
                $this->load->library('YlcfApiTool');
            }
            ......
            YlcfApiTool::xxx()->yyy();
            ......
        }
5、日志处理:
    整个过程没有修改日志处理流程,所以不受影响。

总结:
1、静态化可以提高代码效率,特别是在非脚本语言里;
2、用 library 包装了一遍接口,好处是可以用 load library 优雅地引入接口,调用也无需创建实例。

软件测试-大家都知道的

  1. 为什么要做测试(必答题)
    测试不能提高代码质量;
    自己写的代码自己一般检查不出来BUG;
    流程中复杂的数据结构,第三方模块和方法,内部细节不够详细(类似于黑盒),必须要测试才能发现潜在的bug;
    bug可能因为两块正常的代码耦合在一起而产生,所以通畅地测试整个流程很有必要;

  2. web开发的测试包括哪些
    宏观的测试内容包括:
    用户感受:页面样式正确、文案通顺、交互正常、流程逻辑合理;
    运营需求:流程(样式、文案、引导等等)和目的一致,数据能随时统计;
    开发人员:代码重用率、需求扩展是否容易、文档是否齐全;

  3. 测试需要遵循哪些规则

  4. 有哪些常规的测试方法
    代码评审
    黑盒测试

  5. 有哪些测试工具,自动化测试工具
    Chrome DevTools
    postman(模拟请求)
    fiddler(抓包)
    Selenium(模拟访问 & 结果存储分析)

  6. 如何编写测试文档(范例)
    用例:
    描述:
    前置条件:
    后置条件:
    预期结果:
    是否通过:
    附链接参考:http://www.ibm.com/support/knowledgecenter/zh/SSYMRC_4.0.0/com.ibm.rational.test.qm.doc/topics/t_testcase_template_ref.html

  7. 在以往项目开发过程中,测试的时候遇到的问题(必答题)
    调用api错误(调了别人的分支、调用了测试分支);
    数据解析错误(传参不一样数据结构不一样的接口);
    sql错误:表缺少字段,或者存储了一个null到非null字段;

spl_autoload_register 入门级讲解

标签(空格分隔): PHP自动加载


__autoload()

了解 spl_autoload_register() 前先了解一下 __autoload(),或者直接 跳过此节

function __autoload($c)
{
    var_dump($c);
}
new Say();
//输出
string(3) "Say"
Fatal error: Class 'Say' not found in E:\Dropbox\liubole\htdocs\test\index.php on line xx

当Say类不存在的时候,自动执行了 __autoload() 函数,并将类名当做参数传入了 __autoload()。

如果在 __autoload() 里面 include 一下包含 Say 类的文件,可以“亡羊补牢”,代码继续正常执行:

function __autoload($c)
{
    echo "not fund class \"{$c}\", auto loading...\n";

    //libs下的say.php,大小写无影响
    include_once __DIR__ . "/libs/{$c}.php";
}

$say = new Say();
$say->hello();

包含 Say 类(libs/say.php)的文件内容:

class Say
{
    function __construct()
    {
        echo "init class \"Say\"...\n";
    }

    function hello()
    {
        echo "say: hello~\n";
    }
}

再次执行,结果如下:

not fund class "Say", auto loading...
init class "Say"...
say: hello~

至此我们大概了解了 __autoload() 的实际执行情况。


spl_autoload_register()

PS:前缀 SPL 的全称是 Standard PHP Library。

当PHP找不到类文件会调用 __autoload() 方法,当注册了自己的函数或方法时,PHP不会调用 __autoload() 函数,而会调用自定义的函数。

如果在你的程序中已经实现了__autoload函数,它必须显式注册到__autoload栈中。因为spl_autoload_register()函数会将Zend Engine中的__autoload函数取代为spl_autoload()或spl_autoload_call()。

上面的话来自 php.net。OK,那怎样算 显式注册到__autoload栈中 呢?

◔ ‸◔ ... ◔ ‸◔ ..... ◔ ‸◔ ...... ◔ ‸◔ ........◔ ‸◔

答案是:spl_autoload_register("__autoload") 。以下是我找来的一点摘要,描述了为何需要这样做:

  • autoload机制的主要执行过程为:
    (1) 检查执行器全局变量函数指针autoload_func是否为NULL。
    (2) 如果autoload_func==NULL,则查找系统中是否定义有__autoload()函数,如果没有,则报告错误并退出。
    (3) 如果定义了__autoload()函数,则执行__autoload()尝试加载类,并返回加载结果。
    (4) 如果autoload_func不为NULL,则直接执行autoload_func指针指向的函数用来加载类。注意此时并不检查__autoload()函数是否定义。

  • 如果既实现了__autoload()函数,又实现了autoload_func(将autoload_func指向某一PHP函数),那么只执行autoload_func函数。

  • SPL autoload机制的实现是通过将函数指针autoload_func指向自己实现的具有自动装载功能的函数来实现的。SPL有两个不同的函数spl_autoload, spl_autoload_call,通过将autoload_func指向这两个不同的函数地址来实现不同的自动加载机制。spl_autoload的功能比较简单,而且它是在SPL扩展中实现的,我们无法扩充它的功能。所以我们需要spl_autoload_register将autoload_func指向spl_autoload_call的这个功能。

  • 在SPL模块内部,有一个全局变量autoload_functions,它本质上是一个HashTable,不过我们可以将其简单的看作一个链表,链表中的每一个元素都是一个函数指针,指向一个具有自动加载类功能的函数。spl_autoload_call按顺序执行这个链表中每个函数,在每个函数执行完成后都判断一次需要的类是否已经加载,如果加载成功就直接返回,不再继续执行链表中的其它函数。

  • 自动加载函数链表autoload_functions由spl_autoload_register函数维护,它可以将用户定义的自动加载函数注册到这个链表中,并将autoload_func函数指针指向spl_autoload_call函数。

欲知详情请移步 __autoload机制详解以及与spl_autoload_register的区别, 文章把原因讲的很详细,时间匆忙我仅囫囵吞枣瞄了眼。


本文涉及资料:
PHP: spl_autoload_register - Manual
__autoload机制详解以及与spl_autoload_register的区别


判断点是否在矩形内的算法


声明:想获取更多更详细的知识请移步:计算几何算法概览

需求描述:
有一个 4*4 的方格,用户可以选择起点和落点形成矩形,每次形成的矩形不能重叠,起点可以和落点坐标一样。现已知起点、未选点集合和已选点集合,求落点的集合。

算法重点:转换为判断点P是否在一个矩形内。


方法一:建立平面直角坐标系,根据横坐标和纵坐标判断点是否在矩形内。


/** * 判断点是否在矩形内(含边)。p1、p2是矩形的两个对角点。 * 该算法和平面直角坐标系怎么建立无关 * @param $rc * @param $p * @return bool */ function isInRect($rc, $p) { $xi = ($p->x - $rc->p1->x) * ($p->x - $rc->p2->x); $yi = ($p->y - $rc->p1->y) * ($p->y - $rc->p2->y); return ($xi <= 0) && ($yi <= 0); }

方法二:假设矩形左、右、上、下分别在直线 L1、L2、L3、L4 上,只需要证明点 P 同时在上下、左右直线之间即可。

过P作一条直线相交于 L1、L2 的 C1 、C2 ,向量PC1 、PC2 同向则P在 L1、L2 之外,反向则在 L1、L2 之内。同理可判断P是否在 L3、L4 之间。

$$ PC_1 = (C_1x - P_x , C_1y - P_y); PC_2 = (C_2x - P_x , C_2y - P_y);$$
$$ \frac{C_2x - P_x}{C_1x - P_x } > 0 或 \frac{C_2y - P_y}{C_1y - P_y } > 0 => PC_1、PC_2 同向 $$
$$(C_1x-P_x)^2 + (C_1y-P_y)^2 = 0 => PC_1为零向量 => PC_1、PC_2 同向$$
$$(C_2x-P_x)^2 + (C_2y-P_y)^2 = 0 => PC_2为零向量 => PC_1、PC_2 同向$$

如果:
$$ L_1: y = mx + t_1; $$
$$ L_2: y = mx + t_2; $$
$$ C_1C_2: y = (m+1)x; $$
$$ P_1:(P_1x, P_1y); P_2:(P_3x, P_3y) 是L_1上的两个矩形顶点;$$
$$ P_3:(P_3x, P_3y); P_4:(P_4x, P_4y) 是L_2上的两个矩形顶点;$$

则:

$$ 若点P和{P_1,P_2,P_3,P_4}中的某一点重合,则P在矩形内; $$

$$ 若 (t_2 - P_x)*(t_1 - P_x)>0, t_1=\frac{P_1yP_2x-P_1xP_2y}{P_2x-P_1x},t_2=\frac{P_3yP_4x-P_3xP_4y}{P_4x-P_3x} 则P在矩形内;$$

$$ 若 (t_2 - P_x)*(t_1 - P_x)>0, t_1=\frac{P_1yP_2x-P_1xP_2y}{P_2x-P_1x},t_2=\frac{P_3yP_4x-P_3xP_4y}{P_4x-P_3x} 则P在矩形内;$$

/**
 * 此处省略n行代码
 */

方法三:以点P为起点,向左作射线,如果射线和矩形的焦点个数是奇数,则P在矩形内。

PS:对以下特例不做考虑:1、对于矩形的水平边不作考虑;2、对于矩形的顶点和L相交的情况。

/**
 * 此处省略m行代码
 */

总结:方法一简单实用,方法三可以推广到判断点是否在任意多边形内。


RabbitMQ在项目中的使用方式+简略介绍

声明:文章写于2016-11-10,项目中RabbitMQ的使用方式在后面可能会发生变化。

消息队列(MessageQueue)的好处:

  • 简化业务流程
  • 异步处理消息
  • 减少组件耦合
  • 消减数据峰值

消息队列的应用场景:

  • 场景1:
    支付成功以后,给用户发送优惠券or红包;
  • 场景2:
    用户注册成功后发送通知邮件or短信;

  • 场景3:
    活动里面的站内信发送;
    . . . . . .
    总结起来就是,把单个业务简洁化,通过消息系统把不相关的业务连接起来,实现更复杂的业务链,降低整个系统的复杂度。

安装

安装方法自行google,如果要在生产环境中安装请先联系运维,涉及到安全问题。

项目中使用

//发布消息
use CI123\Shop\Tool\RabbitMq\Publisher;
Publisher::publish('routing_key', $message);

//监听消息
use Api\Tool\RabbitMq\Subscriber;
Subscriber::subscribe('routing_key', function ($msgObj)    {
            $message = $msgObj->body;
            Subscriber::ack($msgObj);
});

以上是对RabbitMQ极其简陋的介绍,下面是重点:

一、我们项目中怎么设置RabbitMQ的:

1. 持久化

真正的持久保存消息,需要设置三个地方:
1.1 将交换机设置成durable;
1.2 将队列设置成durable;
1.3 将消息的delivery_mode设置成2;

PS:
缺少任何一个设置,都无法真正持久化;
持久化会消耗额外的性能;

2. 自动删除

2.1 消息在队列里存在时间是5分钟;
2.2 队列本身存在时间是30分钟;
2.3 过期的消息会被移出到“死信队列”;
2.4 死信队列里的消息存在7天;
2.5 死信队列本身存在10天;

3. 消费者应答机制(ack/nack机制)

3.1 如果消息已经被正确消费了,你可以通过ack方法告诉rabbitmq,rabbitmq会删除这条消息;
3.2 反之,你可以调用nack告诉RabbitMQ消费失败,rabbitmq会立马将消息移到“死信队列”;
3.3 或者什么都不做,等待消息过期后被移动到“死信队列”。

4. 生产者确认机制(Confirm机制)

4.1 发布消息后,发送者可以通过异步的回调知道消息是否发送成功;

PS:目前项目中基本没有使用。

二、初步理解RabbitMQ

1. 四个比较重要的概念

  • 虚拟主机(vhost):每个vhost都有单独一套交换器、队列、绑定规则
  • 交换器(exchange):将消息发送到队列
    常见相关名词:
    + 名称(exchange):交换器名字,不能重复
    + 类型(type):fanout/direct/topic/headers
    + 持久化(durable):服务重启后是否要恢复
    + 自动删除(auto-delete):没有队列使用交换器时自动删除
  • 队列(queue):存取消息
    常见相关名词:
    + 名称(queue):队列名字,不能重复
    + 持久化(durable):服务重启后是否要恢复
    + 自动删除(auto-delete):没有消费者时自动删除
    + 声明(arguments): 消息存在时间(x-message-ttl)、队列存在时间(x-expires)
  • 绑定(binding):将队列和交换机绑定,以通过routing_key来收发消息;

2. 四种Exchange

  • fanout :广播
  • direct:routing_key全部匹配
  • topic:可以理解为在direct的基础上给routing_key增加了正则匹配,当然肯定不是正则;
  • headers:消息里包含headers,如果消息的headers和队列能匹配上就将消息投递到该headers,可以指定是完全匹配(all)还是只需要匹配单个(one);

3. 其他概念和结论

  • 消息的获取有主动拉取、被动接收,发布/订阅模式是被动接收;
  • 交换器、队列的属性一旦声明就不可更改,但是绑定是可以解绑的(unbind);
  • 当一个 message 被路由到多个 queue 中时,在某个 queue 中的某个 message 的“死亡”不会对相同 message 在其他 queue 中的生存状况;
  • 同时设置了队列生存时间T1和消息生存时间T2后,消息在队列中的存在时间是T1/T2之中的最小值;
  • 如果没有设置死信队列,即便你设置了durable,消息到期后也会被丢弃;

4.目前的使用状况

  • 按照普通的消息队列来使用:佣金、理财、保险;
  • 改造为延时队列:微信模板消息,通过延时队列来重发上次发送失败的消息;
  • 可以改造为任务队列使用:待定;

深入理解CI框架:框架如何运转起来(原理篇)

标签(空格分隔): codeigniter 源码

一、核心

index.php:入口文件

入口文件主要定义路径和环境,包括BASEPATHFCPATHAPPPATH等常量表示的路径和applicationsystem等变量表示的路径;

index.php通过$_SERVER['CI_ENV']来分辨当前的执行环境,目前支持developmenttestingproduction三种(默认development),如果你在apache里配置CI_ENV为其他值(比如mytest),项目会输出错误信息并停止执行;

core/CodeIgniter.php:分派请求

CodeIgniter.php加载了各种各样的类和方法,它解析用户的请求(URI类),判断请求对应哪个控制器的哪个方法(Router类),创建控制器对象,执行方法,得到结果并输出(Output类),期间还会加载并执行预先设置好的各种钩子(Hooks类)。

  1. 很显然,CodeIgniter.php是CI框架最重要的文件之一,它根据用户请求调用不同类处理并输出结果。
  2. CI框架解析用户请求时,主要依据$_SERVER这个变量($_SERVER['REQUEST_METHOD']
  3. 除了各种类,CodeIgniter.php还加载了很多公共方法,详见core/Common.phpcore/compat

二、提取核心代码

$RTR =& load_class('Router', 'core');
$class = ucfirst($RTR->class);
require_once BASEPATH.'core/Controller.php';
$CI = new $class();

三、细枝末节

core/Common.php:公共方法

Common Functions——这是Common.php定位,用来存放各种公共方法,比如is_phpis_httpsis_clishow_404function_usable等等。

core/compat目录下也存放有很多公共方法文件,比如hash.phpmbstring.phppassword.phpstandard.php,我们经常用的array_column函数就是在standard.php里定义的。

四、黑科技

extract:从数组中将变量导入到当前的符号表

//index.php
$vars = array('name' => 'jane', 'age' => 20);
extract($vars);
var_dump($name);

ob_xxx函数:把输出放到缓冲区、从缓冲区获取内容

ob_start:打开输出控制缓冲
ob_end_flush:冲刷出(送出)输出缓冲区内容并关闭缓冲
ob_end_clean:清空(擦除)缓冲区并关闭输出缓冲

//index2.php
//将一个php文件保存到文本文件里
//load->view 如何实现的
ob_start();
include('./index.php');
$buffer = ob_get_contents();
file_put_contents('index.php.txt', $buffer);
@ob_end_clean();
ob_end_flush();

ReflectionMethod:反射类

//检查方法是否可以访问
//如何在访问一个private方法或不存在的方法的时候显示404
$reflection = new ReflectionMethod($class, $method);
if ( ! $reflection->isPublic() OR $reflection->isConstructor())
{
    $e404 = TRUE;
}

http到https的过渡

最近https有点火,搜索一下“运商商 流劫持持”,应该能看到好多运营商向网页中强制插入广告的新闻,运营商能这做一是因为流量都要从运营商过路,二是因为http传输的是明文。如果换成https传输数据,数据的安全性可以提高一个档次。

看下文之前请先看《https的使用注意事项》

http到https的切换,道理是很简单的,主要是修改对网址的解析(包括主域名和资源文件的CDN),项目里把“http://”改为“https://”或“//”(自适应协议)。下面是比较详细的改造过程:

1、改配置文件。
配置文件一般存放了项目域名、资源文件域名,所以第一步从这里下手改。
如果开启了https,$_SERVER['HTTPS']非空,一般为on(非https时,在IIS上使用ISAPI方式,该值为off)。
所以,判断是否是https访问的判断条件是 $_SERVER['HTTPS']=='on',我们用 $protocal 表示当前协议,然后把配置里的 http:// 改成 $protocal 。

2、修改项目中的绝对路径。
主要关注几个,jquery路径,二维码路径,微信js地址,轮播图地址,轮播图的点击地址,登录页面的地址。

3、外网资源文件。
如果资源文件很少,可以抓取过来放到自己网站。
如果资源文件很多,可以强制跳转到http页面。(百度图片就是强制跳转到http)

4、统计JS。
百度统计、腾讯统计均支持https,CNZZ现在也支持https(以前的cnzz统计代码不支持https)。
量子统计不支持https。

5、切换到https后可能出现的问题。
a.访问出现502错误;
b.网页打不开,加载时间非常长;
首先看是否是cdn解析问题,如果不能快速解决就切换回http。

总结:
刚始始切换后问题很多,主要特点就是“打不开”、“变慢”。多和运维做些沟通,持续优化https的体验。
https不是绝对安全,如果项目牵扯很多,改造起来难度也很大,量力而行。
记得要保证可以随时切回http。