CODESHIF 最有灵魂的开发者 2021-11-24T16:42:41+08:00 Typecho https://www.codeshif.com/feed/atom/ <![CDATA[Typecho显示文章阅读次数统计]]> https://www.codeshif.com/archives/61.html 2021-11-24T16:42:41+08:00 2021-11-24T16:42:41+08:00 admin https://www.codeshif.com 控制台 / 外观 / 编辑当前外观 / 在 functions.php 最后面加入以下代码

代码已中加入了cookie验证,让文章浏览次数更具有真实性

function get_post_view($archive)
{
    $cid    = $archive->cid;
    $db     = Typecho_Db::get();
    $prefix = $db->getPrefix();
    if (!array_key_exists('views', $db->fetchRow($db->select()->from('table.contents')))) {
        $db->query('ALTER TABLE `' . $prefix . 'contents` ADD `views` INT(10) DEFAULT 0;');
        echo 0;
        return;
    }
    $row = $db->fetchRow($db->select('views')->from('table.contents')->where('cid = ?', $cid));
    if ($archive->is('single')) {
 $views = Typecho_Cookie::get('extend_contents_views');
        if(empty($views)){
            $views = array();
        }else{
            $views = explode(',', $views);
        }
if(!in_array($cid,$views)){
       $db->query($db->update('table.contents')->rows(array('views' => (int) $row['views'] + 1))->where('cid = ?', $cid));
array_push($views, $cid);
            $views = implode(',', $views);
            Typecho_Cookie::set('extend_contents_views', $views); //记录查看cookie
        }
    }
    echo $row['views'];
}

在需要显示次数的地方 (如 index.php,post.php) 加入下边的代码

阅读 <?php get_post_view($this) ?>

]]>
<![CDATA[ThinkPHP数组分页功能详解]]> https://www.codeshif.com/archives/45.html 2021-11-24T14:50:20+08:00 2021-11-24T14:50:20+08:00 admin https://www.codeshif.com ThinkPHP数组分页功能详解

[TOC]

原理

首先找到随便一段PHP查询代码带有paginate的,例如

$data_list = UserModel::where($map)->order('sort,role,id desc')->paginate();

Ctrl+LeftClick点进去看看:

Model.php

会发现进入到了Model.php文件:
见名思意,该文件是MVC框架的Model实现,不过注意这里:

 * @method Paginator|$this paginate() static 分页

拉到顶部可以看到如下:

/**
 * Class Model
 * @package think
 * @mixin Query
 * @method $this scope(string|array $scope) static 查询范围
 * @method $this where(mixed $field, string $op = null, mixed $condition = null) static 查询条件
 * @method $this whereRaw(string $where, array $bind = [], string $logic = 'AND') static 表达式查询
 * @method $this whereExp(string $field, string $condition, array $bind = [], string $logic = 'AND') static 字段表达式查询
 * @method $this when(mixed $condition, mixed $query, mixed $otherwise = null) static 条件查询
 * @method $this join(mixed $join, mixed $condition = null, string $type = 'INNER', array $bind = []) static JOIN查询
 * @method $this view(mixed $join, mixed $field = null, mixed $on = null, string $type = 'INNER') static 视图查询
 * @method $this with(mixed $with, callable $callback = null) static 关联预载入
 * @method $this count(string $field = '*') static Count统计查询
 * @method $this min(string $field, bool $force = true) static Min统计查询
 * @method $this max(string $field, bool $force = true) static Max统计查询
 * @method $this sum(string $field) static SUM统计查询
 * @method $this avg(string $field) static Avg统计查询
 * @method $this field(mixed $field, boolean $except = false, string $tableName = '', string $prefix = '', string $alias = '') static 指定查询字段
 * @method $this fieldRaw(string $field) static 指定查询字段
 * @method $this union(mixed $union, boolean $all = false) static UNION查询
 * @method $this limit(mixed $offset, integer $length = null) static 查询LIMIT
 * @method $this order(mixed $field, string $order = null) static 查询ORDER
 * @method $this orderRaw(string $field, array $bind = []) static 查询ORDER
 * @method $this cache(mixed $key = null , integer|\DateTime $expire = null, string $tag = null) static 设置查询缓存
 * @method mixed value(string $field, mixed $default = null) static 获取某个字段的值
 * @method array column(string $field, string $key = '') static 获取某个列的值
 * @method $this find(mixed $data = null) static 查询单个记录
 * @method $this findOrFail(mixed $data = null) 查询单个记录
 * @method Collection|$this[] select(mixed $data = null) static 查询多个记录
 * @method $this get(mixed $data = null,mixed $with = [],bool $cache = false, bool $failException = false) static 查询单个记录 支持关联预载入
 * @method $this getOrFail(mixed $data = null,mixed $with = [],bool $cache = false) static 查询单个记录 不存在则抛出异常
 * @method $this findOrEmpty(mixed $data = null) static 查询单个记录  不存在则返回空模型
 * @method Collection|$this[] all(mixed $data = null,mixed $with = [],bool $cache = false) static 查询多个记录 支持关联预载入
 * @method $this withAttr(array $name,\Closure $closure = null) static 动态定义获取器
 * @method $this withJoin(string|array $with, string $joinType = '') static
 * @method $this withCount(string|array $relation, bool $subQuery = true) static 关联统计
 * @method $this withSum(string|array $relation, string $field, bool $subQuery = true) static 关联SUM统计
 * @method $this withMax(string|array $relation, string $field, bool $subQuery = true) static 关联MAX统计
 * @method $this withMin(string|array $relation, string $field, bool $subQuery = true) static 关联Min统计
 * @method $this withAvg(string|array $relation, string $field, bool $subQuery = true) static 关联Avg统计
 * @method Paginator|$this paginate() static 分页
 */

也就是说啊,这个Model.php的方法是从Query.php这里搞过来的,那我们来看一下Query.php里面是什么

Query.php

Query.php文件主要是针对数据库链式操作的类,不过其中含有这么一个方法:

/**
     * 分页查询
     * @access public
     * @param  int|array $listRows 每页数量 数组表示配置参数
     * @param  int|bool  $simple   是否简洁模式或者总记录数
     * @param  array     $config   配置参数
     *                            page:当前页,
     *                            path:url路径,
     *                            query:url额外参数,
     *                            fragment:url锚点,
     *                            var_page:分页变量,
     *                            list_rows:每页数量
     *                            type:分页类名
     * @return $this[]|\think\Paginator
     * @throws DbException
     */
    public function paginate($listRows = null, $simple = false, $config = [])

那么这也就是说,我们之前的代码:

$data_list = UserModel::where($map)->order('sort,role,id desc')->paginate();

其方法->paginate();是调用的Query.php中的paginate方法。

DIY实现分页

解释

如果我们想自己查询数据库,例如使用->select();方法:

$data_list = UserModel::where($map)->order('sort,role,id desc')->select();

没错,得到的$data_list是数组类型,数组类型并不返回Model本身的实例,则无法继续像这样调用paginate方法了:

$data_list = UserModel::where($map)->order('sort,role,id desc')->select();
$data_list->paginate();    //    这里是错误的,因为数组没有paginate方法可调用

怎么办?虽然无法调用了,但是我们可以自己创造实例去呀?
但是我们需要先了解paginate方法的类在哪对吧?

Paginator.php

根据查询,弄到了paginate的方法原类对象是Paginator,所在位置thinkphp/library/think/Paginator.php

它是一个abstract抽象类,抽象类是不能实例化的。

abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable

不过我们可以提前看看它所提供的方法:

public function __construct($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = [])
public static function make($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = [])

这两个方法,一个是用于构造对象用的构造函数__construct,另外一个是静态方法make,看参数应该知道,这就是我们想要找的类。

Bootstrap.php

再来看看Bootstrap,这个类继承自Paginator抽象类,也就是Paginator抽象类的实现类,

class Bootstrap extends Paginator

所在位置:thinkphp/library/think/paginator/driver/Bootstrap.php

在Query.php中是这么实例化它的:

$config = Container::get('config')->pull('paginate');

/** @var Paginator $class */
$class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\paginator\\driver\\' . ucwords($config['type']);

return $class::make($results, $listRows, $page, $total, $simple, $config);

$config['type']来自于thinkphp/convention.php配置文件的:

//分页配置
'paginate'   => [
    'type'      => 'bootstrap',
    'var_page'  => 'page',
    'list_rows' => 15,
],

所以,我们接下来可以自己去创造实例去了。

自己创造实例

根据以上知识,我们只需要自行实例化Bootstrap类,并将查询的数组数据注入至Bootstrap实例,最后将Bootstrap实例交给ThinkPHP来处理就好了,非常简单。

$data_list = UserModel::where($map)->order('sort,role,id desc')->select();
$config = Container::get('config')->pull('paginate');
/*

$listRows 每页数量 数组表示配置参数
$simple   是否简洁模式或者总记录数
$config 配置参数
    page:当前页,
    path:url路径,
    query:url额外参数,
    fragment:url锚点,
    var_page:分页变量,
    list_rows:每页数量
    type:分页类名
*/

// 原型:
// public static function make($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = [])

$data_list = Bootstrap::make($data_list,$config['list_rows'],1,15,false,$config);

此时,$data_list就拥有了原数组就有了Bootstrap实例的外衣,交给ThinkPHP去处理时,就可以分页了。

]]>
<![CDATA[Typecho自定义首页文章数量]]> https://www.codeshif.com/archives/44.html 2021-11-24T14:47:23+08:00 2021-11-24T14:47:23+08:00 admin https://www.codeshif.com 修改typecho首页显示文章的数量:
编辑文件 functions.php
在末尾添加:

/* 自定义首页文章分布数量,如 10 */
function themeInit($archive) {
    if ($archive->is('index')) {
        $archive->parameter->pageSize = 10;
    }
}
]]>
<![CDATA[Typecho导航栏输出分类]]> https://www.codeshif.com/archives/43.html 2021-11-24T14:08:47+08:00 2021-11-24T14:08:47+08:00 admin https://www.codeshif.com 编辑外观文件header.php
搜索

<?php _e('首页'); ?></a>

在空行后面添加

<?php $this->widget('Widget_Metas_Category_List')->to($categories); ?>

                    <?php while($categories->next()): ?>
                    <a<?php if($this->is('category', $categories->slug)): ?> class="current"<?php endif; ?> href="<?php $categories->permalink(); ?>" title="<?php $categories->name(); ?>"><?php $categories->name(); ?></a>
                    <?php endwhile; ?>
]]>
<![CDATA[Vuex我的教学文档]]> https://www.codeshif.com/archives/58.html 2020-08-05T12:15:00+08:00 2020-08-05T12:15:00+08:00 admin https://www.codeshif.com Vuex.png

]]>
<![CDATA[Vue-Router我的教学文档]]> https://www.codeshif.com/archives/53.html 2020-08-05T12:15:00+08:00 2020-08-05T12:15:00+08:00 admin https://www.codeshif.com Vue-Router.png

]]>
<![CDATA[Vue实现购物车]]> https://www.codeshif.com/archives/50.html 2020-08-05T12:15:00+08:00 2020-08-05T12:15:00+08:00 admin https://www.codeshif.com 购物车的数据:[

]

localstorage用来持久化数据

存数据

​ 组件去存,就是把当前页面的data、state保存到localstorage

取数据

​ 在actions里面取数据

加、减:

​ 加:

​ 从actions传递过来一个购物车数据,然后传递到mutations,在mutation里面去判断有没有这个购物车数据,如果有的话,就+1,如果没有的话,push到state保存购物车的数组里。

​ 减:

​ 从actions传递过来一个购物车数据,然后传递到mutations,在mutation里面去判断有没有这个购物车数据,如果有的话呢,就-1,如果只有1份,就删除。

方法:

​ 1、判断购物车有没有某项数据

​ 2、购物车增加数据

​ 3、购物车减少数据

​ 4、清空购物车

​ 5、能够获取到购物车的项目的数量

Actions功能:

​ 1、是需要从保存在localstorage里面的数据,用在页面上。

​ 2、接受增加、减少、清空的功能

Mutations的功能:

​ 1、对state的修改

​ 2、Mutations有增加和删除两个功能,增加时对state里的购物车数组增加,减少时对state的购物车数组内减少或删除。如果需要用到exist的话用[].findIndex来查找自定义的数据。

组件的功能:

​ 1、控制显示的内容

​ 2、调用Actions的方法实现增加,减少。

​ 3、把处理好的state的购物车数组,保存到localstorage里面。

Vuex购物车.zip

]]>
<![CDATA[JS特殊语法]]> https://www.codeshif.com/archives/49.html 2020-08-05T12:15:00+08:00 2020-08-05T12:15:00+08:00 admin https://www.codeshif.com JS特殊语法

[TOC]

event.preventDefault()方法

//    $0.onclick = (e)=>{e.preventDefault();alert(1);}
//    html A tag id : #aaa
var a = document.getElementById('aaa');
//    这里的参数e代表当前所触发的鼠标事件内的,鼠标信息,比如说当前鼠标所在屏幕、页面位置、点击的左键还是右键等等
a.onclick = function(e){
  //    取消一些DOM元素的默认事件,比如说A标签的链接跳转功能
  e.preventDefault();
}

event.stopPropagation()方法

//    假设box是个框框,当它点击的时候输出1111
var box = document.getElementById('box');
box.onclick = ()=>{
  console.log(1111)
}
//    给body一个点击事件,输出2222
document.querySelector('body').onclick = ()=>{console.log(2222)}

//    当点击了#box之后,实际上会输出两个内容:1111,2222
//    因为,当儿子元素响应事件之后,还会传递到它的父亲,所以body也会输出内容。
//    当使用event.stopPropagation()方法之后,则不会传递到上一层内容之中。

box.onclick = (e)=>{
  e.stopPropagation();
  console.log(1111)
}

event.target什么意思

比如我们有两个按钮

按钮A 按钮B

当你给按钮A制作一个click事件,并点击它的时候,如你所见,target就是self,因为是自己的事件内出发的该事件。

可是呢,当你在按钮A的click事件内,主动去调用按钮B的点击事件,此时,按钮B的click也会被出发,不过,target就不是自己啦。

a.onclick = function(e){
//  这里如果我主动点击,那么e.target就是我自己。
    console.log(111);
//    是不是就变了?所以b的click内的e:event的target就不是自己了,会显示是a元素调用的
  b.click();
}
b.onclick = function(e){
  //    但是a调用了b内的点击事件,那么e.target就不是b自己了,而是a触发的。
  console.log(222);
}

oncontextmenu

这个是鼠标右键的默认菜单,想要屏蔽它,需要将当前元素取消默认的oncontextmenu返回值就OK了

box.oncontextmenu = function(e){
  return false;
}

box.oncontextmenu = e => false

onselectstart

可以不让别人选择文本

box.onselectstart = e => false

oncopy

可以禁止复制文本

box.oncopy = e => false

::selection

可以控制选择的文本背景色和被选中的文字颜色

::selection
{
    color:#ff0000;
    background: #088;
}

如何制作鼠标右键的菜单

1、建立HTML,弹出的鼠标菜单选项(position:absolute;z-index:9999;background:#000)
2、你需要屏蔽掉用于弹出菜单的层的鼠标右键:oncontextmenu = e => false
3、在用于弹出菜单的层,右键事件中,对准备好的弹出菜单显示,位置用鼠标事件中的e.pageXe.pageY来决定弹出菜单的top和left的样式。

a6 => <div id="a6" style="display:none; position: absolute; background: rgb(0, 0, 0); z-index: 9999;"><ul><li>我们的祖国</li><li></li><li>我们的祖国</li><li></li><li>我们的祖国</li><li></li></ul></div>

a6 = document.getElementById('a6');
box.onmousedown = e =>{ if(e.button == 2){ a6.style.left = e.pageX + 'px'; a6.style.top = e.pageY + 'px';a6.style.display:block }}

其他内容


1、闭包

闭包就是前置执行,在方法体前和方法体后,加入一组小括号,代表成为一个整体,该整体就叫做闭包,用于分离开变量的作用域。

(function(){});
//    如何调用呢?

(function(){})();

var abc = (function(){ return 123;})();

闭包下的作用域,返回JSON/Object类型

var obj = (function(a,b,c){
  
  //    private members
  
  return {
    //    public members;
  }
})(1,2,3);

返回Function类型,返回的一个基于Function类型的对象(可以理解为类)

var Book = (function(a,b,c){
  
  var age = 16;
  this.name = 111;
  
  return function(){
    
      //    private members
    var a1,a2,a3;
    
    //    public members;
    this.isbn = "N12309128398";
  }
})(1,2,3);

var b = new Book();
b.isbn; //    N12309128398
var Dog = function(){
    
      //    private members
    var a1,a2,a3;
    
    //    public members;
      
      this.getAge = function(){
      return 123;
    }
  
}

var d = new Dog();
d.getAge();    //    123

2、JS变态写法

||或者运算:

var b = undefined;
var a = b || 123;
//    现在a的值是123

为什么呢?注意,在JS中,或者||可以判断左侧的变量是否为null或者undefined,如果是的话,就取右侧的值,赋值给左侧变量。

例子:

function abc(a,b){
    a = a || 123;
    console.log(a);
}

abc();
//    123

&&与运算:

var b = true;
b && console.log(123);

//    结果:123

如果&&与符号前面表达式为真,那么后面就会被执行。相反如果前面为假,后面就不会被执行。

连写:

var username="admin", password = "admin";
if(username == "admin" && password == "admin"){
    alert("登录成功")
}

注意:var声明变量的这一行,必须用分号结尾之后才可以执行自定义语句,不能写在一行。

使用三元运算符,可以区分逻辑再执行

var username="admin", password = "admin";
(username == "admin" && password == "admin") ? alert("登录成功") : alert("登录失败");

注意:括号内的逗号,可以隔开多条待执行语句

var username="admin", password = "admin";
(username == "admin" && password == "admin") ? (alert("登录成功"), location.href = "http://www.baidu.com") : alert("登录失败");

forEach遍历

注意:forEach在JS中,是异步的,不是同步,for循环才是同步的。
也就是说,forEach的后面的语句,不会等待forEach执行完毕后再去执行。

var arr = [1,2,3,4,5];
//    for的做法
for(var i = 0; i < arr.length; i++){
  arr[i]++;    // arr[i] += 1;
}
//    这个会等待for语句执行完毕后,会打印111
console.log(1111);


//    forEach的做法,它是不能有返回值的
arr.forEach((item, index) => {
  //    在这里修改item的值,只能在当前方法大括号内被改变,不会影响到arr数组内的值
  item += 100000;
})

//    这里的1111,不一定在forEach执行完毕后才被打印,所以forEach是异步的,非同步。
console.log(1111);

Map

它是数组的拓展方法,意思是将数组遍历一次,并将遍历时,元素计算后的结果返回。

var arr = [1,2,3,4,5];
for(var i = 0; i < arr.length; i++){
  arr[i]++;    // arr[i] += 1;
}

//    Map
//    Map最需要注意的是返回值,必须要return
//    要求:它不是过滤用的,必须每一个元素都需要返回。
var arr2 = arr.map((item, index) => {
    item += 100;
  return item    // return当前元素你要干什么之后改变的值
})

//    arr2 => [101,102,103,104,105]

Filter过滤器

它是数组的过滤器,如果你感觉某个元素不符合你的要求,可以将它排除。

var arr = [1,2,3,4,5];

for(var i = 0; i < arr.length; i++){
  if(i < 3){
    arr.splice(i,1)
  }
}

//    filter的做法
var arr2 = arr.filter((item, index) => {
    
  //    你这里需要返回,你认为达到标准的元素,当表达式为真则返回该元素。
  return item    > 3;
})

3、ES6

难度较大:

1、数组解构、对象解构
2、函数的rest参数
3、数组的拓展运算符
4、对象的拓展运算符

比较简单:

5、Set 和 Map 数据结构
6、Iterator 和 for...of 循环

难度较大:

7、Promise 对象(工作常用)
8、async 函数(工作常用)
9、Class 的基本语法
10、Class 的继承

Promise

非同步,异步,异步的东西转变为同步才使用await/async。
网络请求的地方使用await,只要你使用了await,就必须在当前的方法外侧声明为async。

只有返回值为promise对象的方法才可以使用await

所以,必须会Promise。

//    取得用户详情信息
function getProfile(){
  
  //    这里去请求网络...
  //    在返回值的时候,你也不知道网络请求成功了没有,所以你就返回了一个带有没有的状态。这个状态就是promise。
  //    return new Promise(这里啊是个回调函数)
  return new Promise((resolve,reject) => {
    //    resolve是执行成功了,reject是失败了
    resolve(data);    //    比如说data就是网络请求的数据。
  });
  
  //    所以promise其实就是一个等待数据来的函数,但是数据很可能还没来呢。
}
//    不使用await需要使用then,来获取数据到达
getProfile().then( res => {
    //    res就相当于返回的data数据,但是注意,这里是resolve来的地方
}, error => {
  //    这里是reject来的地方。所以就发生了错误
})

//    这里啊还可以使用catch方法截获错误,那就不用使用reject的回调函数了
getProfile().then( res => {
  
}).catch(err => {
  
})
//    既然使用await,就必须加上async,才能用,不然报错
async function wrapProfile(){
  //    你的data肯定是resolve来的数据,但是请问reject在哪?
  let data = await getProfile();
  
  //    最好的办法是使用try-catch语法来解决这样的错误和问题
  try{
    let data = await getProfile();
    //    所以我们在这里进行判断data是不是null就可以了
        if(data != null){
        //  就行了
    }
  }catch(error){
    //    一旦出现错误就会来到这里
    //    在这里可以处理错误信息
  }
}

Promise.all()

就是当多个含有promise的对象都处理完成后才会进入回调函数。

Promise.any()

就是数组内多个含有promise的对象,只要有一个处理完成,就会进入回调函数。

Set

set就相当于一个没有重复元素的数组,只不过添加数据的方式是使用add,而不是push。

let s = new Set();

s.add(1).add(2).add(2);
// 注意2被加入了两次

s.size // 2

s.has(1) // true
s.has(2) // true
s.has(3) // false

s.delete(2);
s.has(2) // false

Map

键值对。

key => value,相当于PHP中的array。

const m = new Map();
const o = {p: 'Hello World'};

m.set(o, 'content')
m.get(o) // "content"

m.has(o) // true
m.delete(o) // true
m.has(o) // false

4、JS基础语法拓展

call、apply、bind的作用于区别

方法内的this默认指向为Window

call是改变方法内的this指向的目标,且立即执行。
apply是改变方法内的this指向的目标,且立即执行,和call的区别是第二个参数为一个数组。
bind是改变方法内的this指向的目标,但是不会立即执行,需要自己调用才能执行。

function get(){
    //  this => Window
  //    当被改变之后 this => set
  this.setTimeout(function(){console.log(111);},1500);
}

function set(){
  this.setTimeout = function(){
    console.log("小强")
  }
}

var s = new set();

//    默认
get();

//    call和apply区别:
get.call(s,1,2,3,4);
get.apply(s,[1,2,3,4]);

//    委托/代理
var proxy = get.bind(s,1,2,3,4);
proxy();

//    打印的结果:

//    小强
//    小强
//    小强
//    undefined
//    111

prototype是什么

//    我们定义一个Dog类,想要让它有个叫做say的成员方法

//    第一种实现方法
function Dog(){
    this.say = function(){
      console.log("汪汪汪")
    }
}

//    new Dog().say();

//    第二种实现方法
function Dog(){
  
}

//    可以使用prototype,在类的原型链中动态添加新方法
//    也被可以称之为:拓展方法
Dog.prototype.say = function(){
  console.log("汪汪汪")
}

//    new Dog().say();

for..in和for..of的区别

for..in会取出对象、类中的原型链上的内容,而for..of则不会。
for..in可以遍历任何类、对象,但是for..of只能遍历实现了iterator遍历器实现的类、对象。

for...in 循环:只能获得对象的键名,不能获得键值,而for...of 循环:允许遍历获得键值

//    arr为数组,则是Array的对象
let arr = [1, 2, 3];

for(let x in arr){
    console.log(x);
}

//    1
//    2
//    3

//    在原有的Array类上添加一个方法,就是在Array的原型链上进行拓展
Array.prototype.something = function(){
  
}

for(let x in arr){
    console.log(x);
}

//    1
//    2
//    3
//    something


//    注意,for..of方法,它不会取得对象原型链上的内容
for(let x of arr){
    console.log(x);
}

//    1
//    2
//    3

//    所以我们认为,for..of是相对于for..in来说较为安全的。
//    因为for..in会打印出来一些我们并不需要的内容,
//    但是for..of需要对象实现了iterator接口。

总之,for...in 循环主要是为了遍历对象而生,不适用于遍历数组
for...of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象

浅拷贝:值传递和引用传递

var a = 1,b = a;
a = 2;

//    b = ?
b = 1
//    为什么呢?因为你要赋值的操作是值类型的(数字,字符串,布尔),是直接拷贝(复制)过来。

var a = [1,2,3,4,5], b = a;
b[0] = 99;

//    a = ?
//    a = [99,2,3,4,5]

//    为什么呢?因为你要赋值的操作是引用类型的(基于对象),所以是取得它的地址而交给赋值左侧的变量。
]]>
<![CDATA[我的ES6总结教程]]> https://www.codeshif.com/archives/48.html 2020-08-05T12:15:00+08:00 2020-08-05T12:15:00+08:00 admin https://www.codeshif.com [TOC]

解构

数组解构

//  赋值
let [a, b, c] = [1, 2, 3];
//  还可以解构基于Iterator的数据结构
let [x, y, z] = new Set(['a', 'b', 'c']);
// 默认值 x='a', y='b
let [x, y = 'b'] = ['a', undefined]; 

对象解构

//  变量必须与属性同名,才能取到正确的值
let { bar, foo } = { foo: 'aaa', bar: 'bbb' };

const { log } = console;
log('hello') // hello

字符串解构

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

函数参数解构

[[1, 2], [3, 4]].map(([a, b]) => a + b);
// [ 3, 7 ]

function move({x = 0, y = 0} = {}) {
  return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]

圆括号

作用:

// 错误的写法
let x;
{x} = {x: 1};

// 正确的写法
let x;
({x} = {x: 1});

解构唯一可以使用圆括号:赋值语句的非模式部分(左侧部分),可以使用圆括号。

只有在赋值,并且没有声明语句时可用。否则全部不可用!

[(b)] = [3]; // 正确
({ p: (d) } = {}); // 正确
[(parseInt.prop)] = [3]; // 正确

函数的扩展

rest 参数

function add(...values) {
  let sum = 0;

  for (var val of values) {
    sum += val;
  }

  return sum;
}

add(2, 5, 3) // 10
//  注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
// 报错
function f(a, ...b, c) {
  // ...
}

箭头函数

var f = v => v;

// 等同于
var f = function (v) {
  return v;
};


//  如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。

var f = () => 5;
// 等同于
var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
  return num1 + num2;
};

//  如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了。

let fn = () => void doesNotReturn();

箭头函数有几个使用注意点。

(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。

注意:由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向。

数组的扩展

拓展运算符

作用:扩展运算符是三个点(...),将一个数组转为用逗号分隔的参数序列。

Math.max(...[14, 3, 77])

//  复制数组
const a1 = [1, 2];
// 写法一 解开并重新成组
const a2 = [...a1];
// 写法二
const [...a2] = a1;


//  合并数组

const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];

// ES5 的合并数组
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]

// ES6 的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]

//  转换Iterator结构为数组
let nodeList = document.querySelectorAll('div');
let array = [...nodeList];


//  转换Map Set为数组
let map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

let arr = [...map.keys()]; // [1, 2, 3]



对象的拓展

属性表达式

let lastWord = 'last word';

const a = {
  'first word': 'hello',
  [lastWord]: 'world'
};

a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"

扩展运算符


//  拷贝作用
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }


//  分解数组为对象
let foo = { ...['a', 'b', 'c'] };
foo
// {0: "a", 1: "b", 2: "c"}

链判断运算符

a?.b
// 等同于
a == null ? undefined : a.b

a?.[x]
// 等同于
a == null ? undefined : a[x]

a?.b()
// 等同于
a == null ? undefined : a.b()

a?.()
// 等同于
a == null ? undefined : a()

null判断运算符

??行为类似||,但是只有运算符左侧的值为null或undefined时,才会返回右侧的值。

const headerText = response.settings.headerText ?? 'Hello, world!';
const animationDuration = response.settings.animationDuration ?? 300;
const showSplashScreen = response.settings.showSplashScreen ?? true;

??和||的区别

a ?? b
a !== undefined && a !== null ? a : b

对于undefined和null, ??操作符的工作原理与||操作符相同

> undefined ?? 'default'
'default'
> null ?? 'default'
'default'

除了 undefined 和 null的其它虚值,?? 不会返回默认值。

> false ?? 'default'
false
> '' ?? 'default'
''
> 0 ?? 'default'
0

参考文档:传送门

**注意:
双竖线判断的是逻辑性的(相当于两个等号==,而不是三个等号===),
双问号的判断是为空判断的(相当于三个等号===,会进行类型判断)。**

Symbol

概念:

Symbol是新的数据类型,表示独一无二的值。它是JavaScript语言的第七种数据类型。
凡是属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。



let s1 = Symbol('foo');
let s2 = Symbol('bar');

s1 // Symbol(foo)
s2 // Symbol(bar)

s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"

定义属性名

在对象里面定义属性时,要使用[]的方式进行定义,不能使用.操作符定义,因为会被js把.操作符理解为字符串类型。

let mySymbol = Symbol();

// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';

// 第二种写法
let a = {
  [mySymbol]: 'Hello!'
};

// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });

// 以上写法都得到同样结果
a[mySymbol] // "Hello!"

//  不可使用.操作符

const mySymbol = Symbol();
const a = {};

//  使用.操作符会让js理解为是字符串
a.mySymbol = 'Hello!';
a[mySymbol] // undefined
a['mySymbol'] // "Hello!"

属性名的遍历

使用Object.getOwnPropertySymbols()方法,才可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。(返回Symbol属性数组)

Symbol 作为属性名,遍历对象的时候,该属性不会出现在for...infor...of循环中,也不会被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回。
const obj = {};
let a = Symbol('a');
let b = Symbol('b');

obj[a] = 'Hello';
obj[b] = 'World';

const objectSymbols = Object.getOwnPropertySymbols(obj);

objectSymbols
// [Symbol(a), Symbol(b)]

其它方式:

Reflect.ownKeys()方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。

let obj = {
  [Symbol('my_key')]: 1,
  enum: 2,
  nonEnum: 3
};

Reflect.ownKeys(obj)
//  ["enum", "nonEnum", Symbol(my_key)]

Symbol.for(),Symbol.keyFor()

Symbol.for和Symbol的区别:

Symbol的参数为该对象的描述而已,所以即便参数的字符串相同,但是对象是不一样的。

// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();

s1 === s2 // false

// 有参数的情况
let s1 = Symbol('foo');
let s2 = Symbol('foo');

s1 === s2 // false

Symbol.for()的参数会搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值

这里可以用用户名来理解:注册一个用户名,比如叫admin,用户名是唯一性的。Symbol.for()就是用于获取有没有注册过该用户名,如果有就提取出来使用,如果没有则新建这个用户名并保存。

let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');

s1 === s2 // true

Symbol.keyFor()

Symbol.keyFor()方法返回一个已登记的 Symbol 类型值的key。只支持由Symbol.for()注册的对象。

let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"

//    没有使用Symbol.for注册的所以获取不到
let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined

Symbol.hasInstance


//    狗类
//    function Dog(){
  
//    }

//    狗类
class Dog{
  //    用中括号,包括起来之后,它就形成了一个方法,可以进行控制instanceof操作符的返回结果
  //    重写了Dog类被使用instanceof的方法,可以操纵返回值。
  //    参数就是instanceof操作符前面的对象 
  //    dog instanceof Dog 里面的dog
  [Symbol.hasInstance](obj){
    //    返回一个bool类型的值,代表instanceof的返回值(逻辑型)
    return obj instanceof Array;
  }
}

var dog = new Dog();
//    判断当前的对象是否属于Dog类
if (dog instanceof Dog){
  //    true
}

//    True
[1,2,3] instanceof Dog

Symbol.iterator

使当前的类,或对象(Object)支持遍历,类似于数组,支持for..of。


let obj = {
  //    用来实现该对象,支持遍历迭代。
  *[Symbol.iterator](){
    yield 1;
    yield 2;
  }
}

for(let a of obj){
    console.log(a);
}

//    1
//    2

yield类似于return,但是不会终止当前方法的执行,它只是为了迭代使用时,返回当前所属的一个值。

Iterator(遍历器,迭代器)

Iterator是一种接口,任何数据结构只要实现 Iterator 接口,就可以使用遍历、迭代。

Iterator里含有一个next方法,每次调用它,都会将游标向下移动一次(可以理解为数组下标+1)。
next方法每次调用会有返回值,返回一个对象,其中有value和done的属性。value是值,done是bool代表是否遍历完成。

let obj = next();
obj: {
  value:1,
  done: true/false
}

next方法中返回的done和value缺一不可:

class RangeIterator {
  constructor(start, stop) {
    this.value = start;
    this.stop = stop;
  }

  [Symbol.iterator]() { return this; }

  next() {
    var value = this.value;
    if (value < this.stop) {
      this.value++;
      return {done: false, value: value};
    }
    return {done: true, value: undefined};
  }
}

function range(start, stop) {
  return new RangeIterator(start, stop);
}

//    使用range方法可以返回一个RangeIterator对象,该对象支持迭代。
for (var value of range(0, 3)) {
  console.log(value); // 0, 1, 2
}

原生具备 Iterator 接口的数据结构如下。

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

注意:解构和拓展运算符,都需要支持Iterator接口

实现Iterator的重要代码 generator和iterator+yield

yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。

我们要实现一个支持遍历的方法或对象,先要实现它的Iterator接口,并使用generator和yield提供的简写方式,这样会变得更简单。

//    当你使用yield的实现时,即无需实现next方法,它将自动实现,
//    但是yield必须要在generator里才能使用

let generator = function* () {
  yield 1;
  //    yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
  yield* [2,3,4];
  yield 5;
};

var iterator = generator();

iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }

for(let x of iterator){
    console.log(x);
}

遍历器对象的 return(),throw()

next()方法是必须部署的,return()方法和throw()方法是否部署是可选的。

如果for...of循环提前退出(通常是因为出错,或者有break语句),就会调用return()方法。

function readLinesSync(file) {
  return {
    [Symbol.iterator]() {
      return {
        next() {
          return { done: false };
        },
        return() {
          file.close();
          return { done: true };
        }
      };
    },
  };
}

Generator

Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态。

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

yield的返回值

yield可以返回值,这个值,就是从next方法传递进去的参数。

function* f() {
  for(var i = 0; true; i++) {
    //    在这里我们可以看到yield返回了一个bool类型的值,
    //    这个值来自于下面next的参数。
    //    g.next(true)
    var reset = yield i;
    if(reset) { i = -1; }
  }
}

var g = f();

g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }

对任意对象Objectclass实现迭代,务必要实现Symbol.iterator接口方法,才可以实现迭代
#号代表私有成员属性

class Cat{
    constructor(){
        this.#index = 0;
    }
    add(item){
        this[this.#index] = item;
        this.#index++;
    }
    *[Symbol.iterator](){
        for(let i = 0; i < this.index; i++){
          yield this[i]
        }
    }
}

使用private

class Cat{
      #index = 0
    add(item){
        this[this.#index] = item;
        this.#index++;
    }
    *[Symbol.iterator](){
        for(let i = 0; i < this.#index; i++){
          yield this[i]
        }
    }
}
]]>
<![CDATA[Web前端登录的检测方式]]> https://www.codeshif.com/archives/47.html 2020-08-05T12:15:00+08:00 2020-08-05T12:15:00+08:00 admin https://www.codeshif.com 登录的检测方式
1、组件内的检测

检测登录第一种方式,最垃圾,是从组件内部的created方法进行检测,每个需要登录的组件都需要写一遍,很烦

created() {
const openid = localStorage.getItem("openid");
if (!openid) {
  this.$router.push("/login");
}
},
2、使用组件的继承

使用组件的继承,从父组件实现检测登录,当前组件继承父组件,将会自动调用登录检测

父组件:IsLogin.vue

<script>
export default {
  data() {
    //    当前组件会被子组件继承,那么该data内的openid依然也会被继承,则在子组件内可直接使用this.openid来获取登录用户状态
    return { 
      openid: null 
    };
  },
  created() {
    const openid = localStorage.getItem("openid");
    //    将openid赋值到组件数据data内,子组件继承后可直接使用
    this.openid = openid;
    if (!openid) {
      this.$router.push("/login");
    }
  },
};
</script>

子组件:

//    导入父组件
import IsLogin from "@/components/IsLogin";

export default {
  // 组件的继承关系,并不是父子组件的包含关系,而是父子继承后是一体的,父子组件包含关系不是一体的。
  // 组件的继承就像类的继承一样,而父子组件包含并不是继承。
  extends: IsLogin
}
3、路由检测

在路由检测登录状态,但是缺陷很明显,路由会中断跳转,不会进入到组件内部,所以不能实现跳转返回
例如:从A页面跳转到B页面,B页面又需要登录状态,会自动跳转到Login页面,此时Login登录后返回时,将无法获取到B页面的链接
不能实现B页面的登录后跳转返回(原因是根本就没有进入到B页面组件内,在路由处就掐死了)

router/index.js

const router = new VueRouter({
    mode: 'history',
    base: process.env.BASE_URL,
    routes
})

router.beforeEach((to, from, next) => {


    // 不管多少个页面,只要你需要用户登录状态的页面,你要跳转到需要用户登录的页面之前
    // 进行判断,是不是登录了呢?登录的条件源自于:有没有OpenId,如果有的话,那么正常放行
    // 否则呢,你就需要让他先登录

    let paths = [
        '/mine',
        '/order'
    ];

    // paths.includes(to.path)
    // 相当于:
    // if (to.path == '/mine' || to.path == '/order')
    if (paths.includes(to.path)) {
        let openid = localStorage.getItem('openid');
        // 判断有无登录状态,如果有,放行;如果没有,就让用户去登录
        if (openid) {
            next();
        } else {
            // next({ path: '/login' });
            next('/login')
        }
    } else {
        // 如果不是你要检测的URL,那么直接放行
        next();
    }

})
以上三种登录检测的通用页面(路由内守卫)

login.vue

// 获取即将跳转至login页的 链接
// 跳转之login页的上一个页面
// 路由内守卫
beforeRouteEnter(to, from, next) {
  // 我们想要在这里得到上个页面的链接地址,并在登录成功之后,跳转回去。↓
  // 这里的VM指的是,当前组件的this实例
  next((vm) => {
    vm.returnURL = from.path;
  });
},
  methods: {
    onSubmit(values) {
      console.log(this.returnURL);
      axios({
        url: "/login.php",
        method: "GET",
        params: {
          username: values.username,
          password: values.password,
        },
      }).then((res) => {
        let data = res.data;
        if (data.status == false) {
          // Toast("密码错误,请重试");
          Notify({ type: "danger", message: "密码错误,请重试" });
        } else {
          Notify({ type: "success", message: "登录成功" });
          let openid = data.openid;
          localStorage.setItem("openid", openid);

          setTimeout(() => {
            // 这里的returnURL是从当前组件的路由守卫获取的
            this.$router.replace(this.returnURL);
          }, 3000);
        }
      });
    },
  },

登录的过程


1、制作登录页面

需要做好登录页面、并且处理好登录逻辑,包括登录后的返回处理(登录之后需要返回、跳转到登录之前的页面)

2、登录状态的检测

构思哪些页面需要用到登录后的状态,在这些页面内进行登录检测,如果没有登录状态(没登录),那么就让用户去登录页面先登录;

对于登录的检测,我们推荐使用第二种方式:父子继承判断登录状态,获取登录信息。

3、根据令牌获取用户数据

已经有登录的令牌了,根据令牌,去获取用户的数据(拿登录的令牌去获取已登录的用户信息和已登录的用户数据)

登录后的跳转


因为登录成功之后需要跳转到登录前的页面,所以我们需要在登录页面获取到从哪里跳转来的链接,然后返回回去。
路由内的守卫就可以获取到登录前的页面,我们先将该链接保存到组件实例内的data:

第一种方式:
// 获取即将跳转至login页的 链接
// 跳转之login页的上一个页面
// 路由内守卫
beforeRouteEnter(to, from, next) {
  // 我们想要在这里得到上个页面的链接地址,并在登录成功之后,跳转回去。↓
  // 这里的VM指的是,当前组件的this实例
  next((vm) => {
    vm.returnURL = from.path;
    //    跳转回去的路径,有可能带query参数如:/order?id=1111,如果你不处理query,那么直接根据path跳转回去是:/order而不是/order?id=1111,所以我们需要加入query
    //    第一种方式,可以记录from.query
    vm.returnQuery = from.query;
  });
},

当登录成功后,可以根据组件实例this内的data保存的链接地址,进行返回。返回该链接时:

setTimeout(() => {
  // 这里的returnURL是从当前组件的路由守卫获取的
  this.$router.replace({ path: this.returnURL, query:this.returnQuery });
}, 3000);
第二种方式:

beforeRouteEnter(to, from, next) {
  next((vm) => {
    //    跳转回去的路径,有可能带query参数如:/order?id=1111,如果你不处理query,那么直接根据path跳转回去是:/order而不是/order?id=1111,所以我们需要加入query
    
    //    第二种方式,直接使用fullPath,就不用写path和query了
    vm.returnURL = from.fullPath;
  });
},
    

返回链接:

setTimeout(() => {
  // 这里的returnURL是从当前组件的路由守卫获取的
  this.$router.replace(this.returnURL);
}, 3000);

推荐使用第二种方式

]]>