0%

花无人戴,酒无人劝,醉也无人管。

引起问题

在Laravel下, 我们可以通过两种方式访问模型中的属性

  • 对象的方式
    1
    2
    $user = \App\Models\User::query()->find(1);
    dump($user->name);
  • 数组的方式
    1
    2
    $user = \App\Models\User::query()->find(1);
    dump($user['name']);

同样我们也可以通过这两种方式达到修改模型属性值的效果

1
2
3
4
$user = \App\Models\User::query()->find(1);

$user['name'] = '杨国奇';
$user->save();

这是什么原理呢?

一探究竟

我们通过查看User模型的源码, 发现了其中的奥秘

1
2
3
4
5
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
...

进一步查看Authenticatable类的源码

1
2
3
4
5
6
7
class User extends Model implements
AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract
{
use Authenticatable, Authorizable, CanResetPassword;
}

再查看Model类的源码

1
2
3
4
5
6
7
8
abstract class Model implements ArrayAccess, Arrayable, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable
{
use Concerns\HasAttributes,
Concerns\HasEvents,
Concerns\HasGlobalScopes,
Concerns\HasRelationships,

...

发现了其中的奥秘, Model类实现了ArrayAccess接口

什么是ArrayAccess接口

ArrayAccess(数组式访问)接口:提供像访问数组一样访问对象的能力的接口。

实现改接口需要实现以下几个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
ArrayAccess {
//检查一个偏移位置是否存在
abstract public boolean offsetExists ( mixed $offset );

//获取一个偏移位置的值
abstract public mixed offsetGet ( mixed $offset );

//设置一个偏移位置的值
abstract public void offsetSet ( mixed $offset , mixed $value );

//复位一个偏移位置的值
abstract public void offsetUnset ( mixed $offset );
}

我们Model类的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public function offsetExists($offset)
{
return ! is_null($this->getAttribute($offset));
}

/**
* Get the value for a given offset.
*
* @param mixed $offset
* @return mixed
*/
public function offsetGet($offset)
{
return $this->getAttribute($offset);
}

/**
* Set the value for a given offset.
*
* @param mixed $offset
* @param mixed $value
* @return void
*/
public function offsetSet($offset, $value)
{
$this->setAttribute($offset, $value);
}

/**
* Unset the value for a given offset.
*
* @param mixed $offset
* @return void
*/
public function offsetUnset($offset)
{
unset($this->attributes[$offset], $this->relations[$offset]);
}

你看了一千个正确答案,还是要一秒一秒的等时间

Supervisor 是一个 Python 写的进程管理工具,有时一个进程需要在后台运行,并且意外挂掉后能够自动重启,就需要这么一个管理进程的工具。在 Laravel 开发中,也经常使用到队列监听,可以配合 Supervisor 来管理 Laravel 队列进程。

Supervisor的安装

  • 使用 pip 工具进行安装:
    1
    2
    3
    sudo yum install epel-release
    sudo yum -y install python-pip
    sudo pip install supervisor
  • Ubuntu 系统使用 apt-get
    1
    sudo apt-get install supervisor
  • 还有其他的安装方式,请见官网(http://supervisord.org/)

Supervisor的配置

一般配置文件在/etc/supervisor/conf.d 目录下
也可以运行这个命令可以生成一个默认的配置文件:

1
echo_supervisord_conf > /etc/supervisor/supervisord.conf

生成成功后,打开编辑这个文件,把最后的 include 块的注释打开,并修改如下:

1
2
[include]
files = /etc/supervisor/*.conf

新增的 Supervisor 配置文件放在 /etc/supervisor 目录下,并且以 conf 结尾。

这时我们使用新的配置文件来启动 Supervisor:

1
supervisord -c /etc/supervisord.conf

如果提示已经有进程在运行,那么先 kill 掉它。

使用Supervisor管理Laravel队列进程

我们使用 Laravel 队列,会用到 php artisan queue:work 命令,让它监听队列,我们可以通过 nohup 方式让它在后台运行,但是进程如果意外中断是不会自动重启的,所以使用 Supervisor 来监控进程是个很好的方式。

首先在 /etc/supervisor 目录下新增一个 Supervisor 的配置文件,如下:
文件名erp2-worker.conf,和配置文件中指定的program保持一致

1
2
3
4
5
6
7
8
9
[program:erp2-worker]
process_name=%(program_name)s_%(process_num)02d
command=/usr/bin/php7 /home/vagrant/code/ccerp-v2/artisan queue:work --tries=3
autostart=true
autorestart=true
user=vagrant
numprocs=8
redirect_stderr=true
stdout_logfile=/var/log/supervisor/laravel-queue.log

这里 user 填写网站运行进程的用户,如 vagrant,numprocs 表示启动多少个进程来监听 Laravel 队列。
一切就绪后,我们使用如下命令就可以启动队列进程的监听了:

注意: 修改了配置文件以后都要进行 reload 和 update

1
2
3
4
5
6
7
8
9
10
supervisord # 先执行

sudo supervisorctl reload

sudo supervisorctl update

sudo supervisorctl start erp2-worker:*

// 或者
sudo supervisorctl start all

但是在这一步,发生了错误,提示如下:

laravel-worker:laravel-worker_00: ERROR (spawn error)
laravel-worker:laravel-worker_01: ERROR (spawn error)
laravel-worker:laravel-worker_02: ERROR (spawn error)
laravel-worker:laravel-worker_03: ERROR (spawn error)
laravel-worker:laravel-worker_04: ERROR (spawn error)
laravel-worker:laravel-worker_05: ERROR (spawn error)
laravel-worker:laravel-worker_06: ERROR (spawn error)
laravel-worker:laravel-worker_07: ERROR (spawn error)

经过一番折腾,解决方法是,把 Supervisor 的日志文件,和新增的队列配置文件中的日志文件,用 chown 把用户和组设置为正确的,如本例是 chown vagrant:vagrant file_name,另外把日志文件权限设置为 777.

再次经过上述步骤,成功开启进程管理:

laravel-worker:laravel-worker_00: started
laravel-worker:laravel-worker_01: started
laravel-worker:laravel-worker_02: started
laravel-worker:laravel-worker_03: started
laravel-worker:laravel-worker_04: started
laravel-worker:laravel-worker_05: started
laravel-worker:laravel-worker_06: started
laravel-worker:laravel-worker_07: started

可以看到 Laravel 队列开始正常运行了,这里值得注意的是,如果 Laravel 处理队列的代码更改了,需要重启 Supervisor 的队列管理才能生效。

查看运行情况:

1
ps -ef|grep 'queue:work'

结果

1
2
3
4
5
6
7
8
9
vagrant   4899  1407  0 03:10 ?        00:00:00 /usr/bin/php /home/vagrant/code/ccerp-v2/artisan queue:work --tries=3
vagrant 4900 1407 0 03:10 ? 00:00:00 /usr/bin/php /home/vagrant/code/ccerp-v2/artisan queue:work --tries=3
vagrant 4901 1407 0 03:10 ? 00:00:00 /usr/bin/php /home/vagrant/code/ccerp-v2/artisan queue:work --tries=3
vagrant 4902 1407 0 03:10 ? 00:00:00 /usr/bin/php /home/vagrant/code/ccerp-v2/artisan queue:work --tries=3
vagrant 4903 1407 0 03:10 ? 00:00:00 /usr/bin/php /home/vagrant/code/ccerp-v2/artisan queue:work --tries=3
vagrant 4904 1407 0 03:10 ? 00:00:00 /usr/bin/php /home/vagrant/code/ccerp-v2/artisan queue:work --tries=3
vagrant 4905 1407 0 03:10 ? 00:00:00 /usr/bin/php /home/vagrant/code/ccerp-v2/artisan queue:work --tries=3
vagrant 4906 1407 0 03:10 ? 00:00:00 /usr/bin/php /home/vagrant/code/ccerp-v2/artisan queue:work --tries=3
vagrant 4978 2575 0 03:11 pts/0 00:00:00 grep --color=auto queue:work

一个错误

使用supervisorctl reload却得到报错提示:

1
error: <class 'socket.error'>, [Errno 2] No such file or directory: file: /usr/lib/python2.7/socket.py line: 228  

在运行 reload 命令前,先运行如下两个命令

1
2
sudo supervisord -c /etc/supervisor/supervisord.conf  
sudo supervisorctl -c /etc/supervisor/supervisord.conf

或者直接

1
sudo supervisord

即可解决

Horizon

Horizon为 Laravel 官方出品的 Redis 队列提供了一个可以通过代码进行配置、并且非常漂亮的仪表盘,并且能够轻松监控队列的任务吞吐量、执行时间以及任务失败情况等关键指标。

队列执行者的所有配置项都存放在一个简单的配置文件中,所以团队可以通过版本控制进行协作维护。

生产环境中,我们需要配置一个进程管理工具来监控 php artisan horizon 命令的执行,以便在其意外退出时自动重启。当服务器部署新代码时,需要终止当前 Horizon 主进程,然后通过进程管理工具来重启,从而使用最新的代码。

使用 Artisan 命令 horizon:terminate 来正常停止系统中的 Horizon 主进程,此命令执行时,Horizon 当前执行中的任务会被正常完成,然后 Horizon 执行结束:

1
php artisan horizon:terminate

Supervisor 配置
可以使用进程管理工具 Supervisor 来管理 horizon 进程,下面配置文件就已够用:

1
2
3
4
5
6
7
8
[program:horizon]
process_name=%(program_name)
command=php /home/forge/app.com/artisan horizon
autostart=true
autorestart=true
user=forge
redirect_stderr=true
stdout_logfile=/home/forge/app.com/horizon.log

注意Horizon仅仅支持redis, 不支持database等其他队列引擎;

其他

配置说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
; 设置进程的名称,使用 supervisorctl 来管理进程时需要使用该进程名
[program:foo]
; 可以在 command 这里用 python 表达式传递不同的参数给每个进程
command=python server.py --port=90%(process_num)02d
directory=/home/python/tornado_server ; 执行 command 之前,先切换到工作目录
; 若 numprocs 不为1,process_name 的表达式中一定要包含 process_num 来区分不同的进程
numprocs=2
process_name=%(program_name)s_%(process_num)02d;
user=oxygen ; 使用 oxygen 用户来启动该进程
autorestart=true ; 程序崩溃时自动重启
redirect_stderr=true ; 重定向输出的日志
stdout_logfile = /var/log/supervisor/tornado_server.log
loglevel=info

上面这个例子会启动两个进程,process_name 分别为 foo:foo_01 和 foo:foo_02。通过这样一种方式,就可以用一个 [program:x] 配置项,来启动一组非常类似的进程。

https://laravel-china.org/topics/3592/using-supervisor-to-manage-laravel-queue-processes
https://laravel-china.org/docs/laravel/5.5/horizon
https://www.restran.net/2015/10/04/supervisord-tutorial/
http://weidwonder.leanote.com/post/Untitled-55faa25638f4111167000067-6
https://linuxize.com/post/how-to-install-pip-on-centos-7/

今夜月明人尽望,不知秋思落谁家

自定义异常类

app\Exceptions\ExportException.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php

namespace App\Exceptions;

use Throwable;

class ExportException extends \Exception
{

protected $message;

public function __construct(string $message = "", int $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);

$this->message = $message;
}

public function report()
{
// 这里自定义发生异常发生时要额外做的事情
// 比如发邮件通知管理员
//
}


public function render()
{
// 这里需要给浏览器或者API返回必要的通知信息
// 可以是json 结构, 一般是针对API调用的
// 也可以渲染一个网页, 一般是针对浏览器访问的页面
// 也可以直接重定向到其他网页

return response()->json(['status' => 200, 'message' => $this->message], 503);
}
}

说明:
如果在文件 app\Exceptions\Handler.php 中申明了以下代码, 针对该异常类的report方法将不会在被执行;

1
2
3
4
5
6
7
8
9
10
11
12
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* @var array
*/
protected $dontReport = [

ExportException::class
];

抛出并捕获异常

首先尝试抛出异常:

1
2
3
4
5
try {
$res = 1/0;
} catch (\App\Exceptions\ExportException $e) {
throw new \App\Exceptions\ExportException($e->getMessage());
}

上面这段代码并不会在我们自定义的ExportException中捕获, 而会在全局异常中捕获,原因我们打印出这个异常类型看看
app\Exceptions\Handler.php

1
2
3
4
5
6
public function render($request, Exception $exception)
{
dd(get_class($exception)); // ErrorException

return parent::render($request, $exception);
}

该异常类型属于ErrorException, 并不是我们自定的ExportException, 所以并不会被我们捕获;
我们可以这样, 既然我们已经知道这个异常类似是ErrorException, 那么

1
2
3
4
5
try {
$res = 1/0;
} catch ( ErrorException $e) {
throw new \App\Exceptions\ExportException($e->getMessage());
}

当然也可以直接这样

1
2
3
4
5
try {
$res = 1/0;
} catch ( Exception $e) {
throw new \App\Exceptions\ExportException($e->getMessage());
}

这样就可以用我们自定义的异常类来捕捉从而进一步处理了

1
2
3
4
{
"status": 200,
"message": "Division by zero"
}

关于异常的理解

  • 由程序员设计不足所导致的错误,需要用异常来捕捉和处理
  • 程序在运行过程中, 有可能会发生一些不可预知的错误, 无法用if…else这样的语句来处理的时候.
  • 函数无法满足调用方的期望的时候使用异常, 比如用户要请求一个API获得最新的商品信息, 但是查询结果为空的时候

所以我们自定义的异常可以是在不符合实现给定的情况下抛出的, 而不是真正程序在运行过程中发生了错误, 在Laravel的源码中我们可以找到很多一样的例子
vendor\laravel\framework\src\Illuminate\Foundation\Auth\AuthenticatesUsers.php

1
2
3
4
5
6
protected function sendFailedLoginResponse(Request $request)
{
throw ValidationException::withMessages([
$this->username() => [trans('auth.failed')],
]);
}

vendor\guzzlehttp\psr7\src\functions.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function str(MessageInterface $message)
{
if ($message instanceof RequestInterface) {
$msg = trim($message->getMethod() . ' '
. $message->getRequestTarget())
. ' HTTP/' . $message->getProtocolVersion();
if (!$message->hasHeader('host')) {
$msg .= "\r\nHost: " . $message->getUri()->getHost();
}
} elseif ($message instanceof ResponseInterface) {
$msg = 'HTTP/' . $message->getProtocolVersion() . ' '
. $message->getStatusCode() . ' '
. $message->getReasonPhrase();
} else {
throw new \InvalidArgumentException('Unknown message type');
}

foreach ($message->getHeaders() as $name => $values) {
$msg .= "\r\n{$name}: " . implode(', ', $values);
}

return "{$msg}\r\n\r\n" . $message->getBody();
}

只要想起一生中后悔的事 梅花便落满了南山

什么是 “yield”

生成器函数看上去就像一个普通函数, 除了不是返回一个值之外, 生成器会根据需求产生更多的值。

来看以下的例子:

1
2
3
4
5
function getValues() {
yield 'value';
}
// 输出字符串 "value"
echo getValues();

当然, 这不是他生效的方式, 前面的例子会给你一个致命的错误,类生成器的对象不能被转换成字符串, 让我们清楚的说明

“yield” & “return” 的区别

前面的错误意味着 getValues() 方法不会如预期返回一个字符串,让我们检查一下他的类型:

1
2
3
4
5
6
7
8
9
function getValues() {
return 'value';
}
var_dump(getValues()); // string(5) "value"

function getValues() {
yield 'value';
}
var_dump(getValues()); // class Generator#1 (0) {}

生成器 类实现了 生成器 接口, 这意味着你必须遍历 getValue() 方法来取值:

1
2
3
4
5
6
7
8
9
foreach (getValues() as $value) {
echo $value;
}

// 使用变量也是好的
$values = getValues();
foreach ($values as $value) {
echo $value;
}

但这不是唯一的不同!

一个生成器运行你写使用循环来迭代一维数组的代码,而不需要在内存中创建是一个数组,这可能会导致你超出内存限制。

在下面的例子里我们创建一个有 800,000 元素的数字同时从 getValues() 方法中返回他,同时在此期间,我们将使用函数 memory_get_usage() 来获取分配给次脚本的内存, 我们将会每增加 200,000 个元素来获取一下内存使用量,这意味着我们将会提出四个检查点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
function getValues() {
$valuesArray = [];
// 获取初始内存使用量
echo round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;
for ($i = 1; $i < 800000; $i++) {
$valuesArray[] = $i;
// 为了让我们能进行分析,所以我们测量一下内存使用量
if (($i % 200000) == 0) {
// 来 MB 为单位获取内存使用量
echo round(memory_get_usage() / 1024 / 1024, 2) . ' MB'. PHP_EOL;
}
}
return $valuesArray;
}
$myValues = getValues(); // 一旦我们调用函数将会在这里创建数组
foreach ($myValues as $value) {}

前面例子发生的情况是这个脚本的内存消耗和输出:

0.34 MB
8.35 MB
16.35 MB
32.35 MB

这意味着我们的几行脚本消耗了超过 30 MB 的内存, 每次你你添加一个元素到 $valuesArray 数组中, 都会增加他在内存中的大小。

让我们使用 yield 同样的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
function getValues() {
// 获取内存使用数据
echo round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;
for ($i = 1; $i < 800000; $i++) {
yield $i;
// 做性能分析,因此可测量内存使用率
if (($i % 200000) == 0) {
// 内存使用以 MB 为单位
echo round(memory_get_usage() / 1024 / 1024, 2) . ' MB'. PHP_EOL;
}
}
}
$myValues = getValues(); // 在循环之前都不会有动作
foreach ($myValues as $value) {} // 开始生成数据

这个脚本的输出令人惊讶:

0.34 MB
0.34 MB
0.34 MB
0.34 MB

这不意味着你从 return 表达式迁移到 yield,但如果你在应用中创建会导致服务器上内存出问题的巨大数组,则 yield 更加适合你的情况。

https://laravel-china.org/topics/8704/using-yield-to-do-memory-optimization-in-php?utm_source=coffeephp.com

枕上诗书闲处好,门前风景雨来佳

参数类型

  • 可执行的(PHP 5.4+)

    1
    2
    3
    function foo(callable $callback) {

    }
  • 可迭代的(PHP 7.1+)

    1
    2
    3
    4
    5
    6
    7
    function foo(iterable $data) {
    foreach ($data as $item) {
    dump($item);
    }
    }

    foo([1,2,3]);
  • 接受某一个类的实例

    1
    2
    3
    function getReuestParams(Request $request){
    return $request->only('name', 'email');
    }
  • 数组, 整形, 字符串
    整形

    1
    2
    3
    function foo(int $number) {

    }

    数组

    1
    2
    3
    function foo(array $number) {

    }

    字符串

    1
    2
    3
    function foo(string $number) {

    }

可选参数

通过给定默认值实现

1
2
3
function foo(int $number = 1) {

}

可空参数

1
2
3
4
5
6
7
8
9
<?php
function foo(?string $name)
{
return $name;
}
echo foo(null); // 不会返回任何东西
echo foo('Nauman'); // Nauman
echo foo(); // 致命错误

可空参数不是可选参数,你必须传递一个值或者是 null

指定返回值类型

1
2
3
4
5
6
7
8
9
10
11
12
13
function foo(?string $name) : string
{
return $name;
}
echo foo(null); // 致命错误,必须返回 string 类型
echo foo('Nauman'); // Nauman


// 返回 void 类型
$attribute = 2;
function changeAttribute(string &$param, $value) : void {
$param = $value;
}

他说你任何为人称道的美丽 不及他第一次遇见你
在Laravel 中, 如果要实现用户的单点登录, 即系统中同一个用户仅仅允许一个会话(session)存在, 实现起来相当简单, 下面的代码是我在Laravel 5.5.13中试验的, 并不保证在其他版本的Laravel中可以使用, 但是思想同样可以借鉴;

修改user表

User模型对应的数据库表增加 session_id 字段, string类型;

修改登录控制器

/app/Http/Controllers/Auth/LoginController.php

1
2
3
4
5
6
7
8
9
10
public function authenticated(Request $request,User $user){
$previous_session = $user->session_id;

if ($previous_session) {
\Session::getHandler()->destroy($previous_session);
}
\Auth::user()->session_id = \Session::getId();
\Auth::user()->save();
return redirect()->intended($this->redirectPath());
}

验证

我们分别在不同的浏览器去用同一帐号尝试去登录我们的应用, 发现, 如果帐号在一个浏览器登录, 那么另外一个浏览器的帐号就会自动退出;

https://tiicle.com/items/327/laravel-applications-a-user-is-only-allowed-a-single-session

若是眼睛不喜欢,心也会拒绝。

现在我们就以我们的home页面为例, 说一下使用面向对象的方式如何开发微信小程序;

文件目录结构

1
2
3
4
5
6
7
- pages
-- home
--- home-model.js
--- home.js
--- home.wxml
--- home.wxss
--- home.json

我们新建一个model类, 用来处理一些复杂的逻辑, 以使我们的代码变得整洁;

home-model.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Home {

constructor(){
// 构造函数
}

// 根据banner获取id
getBannerData(id){
wx.request({
url: 'http://tp5.me/api/v1/banner/'+id,
method: 'GET',
success: function(res){
console.log(res);
return res;
}
})
}
}

export {Home}

home.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { Home } from 'home-model.js';

var home = new Home();

// pages/home.js
Page({

data: {

},

onLoad: function () {
console.log('home on load');
this._loadData();
},

// 定义一个私有方法
_loadData: function () {
var id = 1;
var data = home.getBannerData(id)
return data;
}

})

接收异步返回值

因为API返回的数据是异步的, 所以我们无法通过调用直接拿到返回值, 但是我们可以通过设置一个回调函数拿到他的返回值
home.js

1
2
3
4
5
6
7
8
9
10
// 定义一个私有方法
_loadData: function () {
var id = 1;
home.getBannerData(id, this.callback);
},

callback: function(res){
console.log('回调返回');
console.log(res)
}

home-model.js

1
2
3
4
5
6
7
8
9
getBannerData(id, callback){
wx.request({
url: 'http://tp5.me/api/v1/banner/'+id,
method: 'GET',
success: function(res){
callback(res);
}
})
}

构造基类

目录结构

1
2
3
4
5
6
7
8
- pages
- utils
-- base.js
-- util.js
-- config.js
- app.js
- app.json
- app.wxss

构造配置类

config.js

1
2
3
4
5
6
7
8
9
class Config {
constructor(){

}
}

Config.restUrl = 'http://tp5.me/api/v1/';

export {Config};

构造基类

base.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import { Config } from '../utils/config.js';

class Base {
constructor(){
this.baseRequestUrl = Config.restUrl;
}

request(params){
var url = this.baseRequestUrl + params.url;

if (!params.type) {
params.type = 'GET';
}

wx.request({
url: url,
data: params.type,
header: {
'content-type': 'application/json',
'token': wx.getStorageInfoSync('token')
},
method: params.type,
success: function(res) {
params.callback && params.callback(res.data);
},
fail: function(error) {
console.log(error);
},
complete: function(res) {

},
})
}
}

export {Base};

类的继承

home-model.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { Base } from '../../utils/base.js'

class Home extends Base {

constructor(){
// 如果继承了某个类, 则必须要调用 super
super();
}

getBannerData(id, callback){

var params = {
url: 'banner/'+id,
callback:function(res){
callback && callback(res.items)
}
};

this.request(params);
}
}

export {Home}

月黑见渔灯,孤光一点萤。 微微风簇浪,散作满河星。

方案一

  • 余额提现、转出等
    一般会向下取数,比如10.1234;那实际可提现金额为10.12。

  • 分期相关
    如银行额度总共为1000元,然后刚好买了一样东西,全花了,在操作分期。分3期;
    按正常思维是1000/3=333.3333333;在四舍五入一下就成了333.33;等你三期都还完了,发现只还了999.99。这就坑了
    一般做法是:前2期按四舍五入计算。最后一期,按减法算:1000-333.33-333.33=333.34;

  • 其他说明
    根据不同业务,保留位数和取舍都不一样。如基金的净值。小数点的长度影响的资金量还是很大的。这块是越精准越好。具体需求跟产品沟通吧!

https://segmentfault.com/q/1010000007519197

心中事,眼中泪,意中人

简介

一款微信小程序商城,后端API基于ThinkPHP 5 开发, 实现了商品展示, 用户登录, 加入购物车, 下单购买等基本流程;

首页

商品详情

购物车

专题列表

微信用户地址获取

授权登录

项目源代码

富润屋,德润身,心广体胖。

在JS中,这三者都是用来改变函数的this对象的指向的,他们有什么样的区别呢。在说区别之前还是先总结一下三者的相似之处:

  • 都是用来改变函数的this对象的指向的。
  • 第一个参数是前面函数在执行时this要指向的对象(即后面的这个对象暂时拥有了执行这个函数的权限)。
  • 都可以利用后续参数传参。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var xw={
name: "小王",
gender: "男",
age: 24,
say: function(){
alert(this.name+" , "+this.gender+" ,今年"+this.age);
}
}
var xh={
name: "小红",
gender: "女",
age: 18
}
xw.say();

本身没什么好说的,显示的肯定是小王 , 男 , 今年24。那么如何用xw的say方法来显示xh的数据呢。
对于call可以这样:

1
xw.say.call(xh);

对于apply可以这样:

1
xw.say.apply(xh);

而对于bind来说需要这样:

1
xw.say.bind(xh)();

如果直接写xw.say.bind(xh)是不会有任何结果的,看到区别了吗?call和apply都是对函数的直接调用,而bind方法返回的仍然是一个函数,因此后面还需要()来进行调用才可以。那么call和apply有什么区别呢?我们把例子稍微改写一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
var xw={
name: "小王",
gender: "男",
age: 24,
say: function(school,grade){
alert(this.name+" , "+this.gender+" ,今年"+this.age+" ,在"+school+"上"+grade);
}
}
var xh={
name: "小红",
gender: "女",
age: 18
}

可以看到say方法多了两个参数,我们通过call/apply的参数进行传参。对于call来说是这样的

1
xw.say.call(xh,"实验小学","六年级");

而对于apply来说是这样的

1
xw.say.apply(xh,["实验小学","六年级"]);

看到区别了吗,call后面的参数与say方法中是一一对应的,而apply的第二个参数是一个数组,数组中的元素是和say方法中一一对应的,这就是两者最大的区别。那么bind怎么传参呢?它可以像call那样传参。

1
xw.say.bind(xh,"实验小学","六年级")();

但是由于bind返回的仍然是一个函数,所以我们还可以在调用的时候再进行传参。

1
xw.say.bind(xh)("实验小学","六年级");

分析

获取数组中的最大值和最小值

1
2
3
var  numbers = [5, 458 , 120 , -215 ]; 
var maxInNumbers = Math.max.apply(Math, numbers), //458
var maxInNumbers = Math.max.call(Math,5, 458 , 120 , -215); //458

https://www.jianshu.com/p/56a9c2d11adc