小马甲

小马甲 查看完整档案

深圳编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

生活不止眼前的苟且

个人动态

小马甲 回答了问题 · 11月21日

解决vue3 props typescript 报错

本人尝试一下方式不会报错了:

const { currentPage } = toRefs<any>(props);
console.log(currentPage);

或者给 props 参数指定类型:

setup(props: Props) {
  console.log(props.currentPage);
}

关于 eslint 错误,可以参考文档:
https://eslint.vuejs.org/rule...

关注 1 回答 1

小马甲 提出了问题 · 11月21日

解决vue3 props typescript 报错

export default defineComponent({
  props: {
    list: {
      type: Array,
      default: function() {
        return [];
      },
    },
    currentPage: {
      type: Number,
      default: 1,
    },
    lastPage: {
      type: Number,
      default: 1,
    },
  },
  setup(props) {
    console.log(props.currentPage);
    return {};
  },
});

image

关注 1 回答 1

小马甲 收藏了文章 · 11月8日

js 记一个牛逼的一维数组转二维数组的方法, 不知道还有更牛逼的吗?

function getArr(arr, number) {
    let spliceArr = arr.slice(0);
    return Array.from({length: Math.ceil(arr.length/number)}, () => {
        return spliceArr.splice(0, number)
    })
}
查看原文

小马甲 收藏了问题 · 11月7日

socket.io不支持中文发送吗?

服务端向客户端发送一个中文但是客户端接收不到为空,改为英文字母就ok了是怎么回事?
server.js

var io = require('socket.io')(server);
io.on('connection', function (socket) {
    socket.emit('news', { hello: 'wold' });
    socket.on('my other event', function (data) {
       console.log(data);
    });
    
    //这里的val不能用中文
    // socket.emit('news', { hello: '欢迎你' });

    // socket.on('my other event', function (data) {
    //     console.log(data)
    // });
});

小马甲 收藏了文章 · 11月7日

typescript 使用的几种情况

接口的创建

可以使用 type 和 interface 来创建类型

type 特有的优点:

  1. 声明基本类型别名,联合类型,元组等类型

    type S = string;
    
    type IFoo = IBar | string;
  2. 可使用 typeof 获取实例的类型赋值

    const a:number = 1;
    const IA = typeof a;
    // IA 被 ts 识别为 number

interface 特有的优点

interface 能够声明合并

interface IFoo {  
  name:string  
}  
interface IFoo {  
  age:number  
}
// 等于
type IFoo = {
    name:string 
    age:number
}

关于对象

获取对象

以IFoo作为例子

interface IFoo {  
  name:string  
  age:number  
  gender:string  
}  

获取接口的单个属性的类型

type IBar = IFoo['name']
// IBar = string

获取接口中一或多个属性,并将其合并为一个接口

type IBar = Pick<IFoo, 'name'>
// IBar = {name: string}
type IBar = Pick<IFoo, 'name' | 'age'>
// IBar = {name: string, age: number}

忽略接口中的某些属性,将剩余属性作为一个接口

type IBar = Omit<IFoo, 'name'>
// IBar = {age: number, gender: string}

获取接口中所有键

type IBar = keyof IFoo
// IBar = "name" | "age" | "gender"

获取接口中所有键对应的值

type IBar = IFoo[keyof IFoo]
// IBar = string | number

创建对象

创建多个重复值的对象

type IBar = Record<'name' | 'age', string>
// IBar = {name: string, age: string}

使用例子

interface IFoo {  
  name: string  
  age: string  
  gender: string  
  
  getSkill(): void  
  
  setSkill: (skill: string[]) => void  
}
// 生成一个新类型,将 age 和 gender 的类型修改为 number,其他的类型不变
// 使用上述知识 声明一个新的高级类型IBar:
type IBar<K extends string,T = number> = (Record<K, T> & Omit<IFoo, K>)

type IBaz = IBar<'age' | 'gender'>
// 生成新的类型 IBaz ,符合上述描述
// 并且使用 Ibar 可将 age 和 gender (或其他)更改为任意其他类型 如:
type IBax = IBar<'age' | 'gender' | 'name', string[]>

关于函数

函数类型创建

创建函数类型的两种方式

interface IFoo {  
  name: string  
  age: number  
  gender: string  
  
  getSkill(): void  // type 不支持此种声明
  
  setSkill: (skill: string[]) => void  
}

函数类型中参数的获取

以此为例子:

type IFoo = (name: string, age: number) => { name: string, age: number, gender: string }

获取函数的参数类型:

type IBar = Parameters<IFoo>  
  
// IBar = [string, number]

获取函数的返回类型:

type IBar = ReturnType<IFoo>  
  
// IBar = {name: string, age: number, gender: string}
查看原文

小马甲 赞了文章 · 9月22日

⚡️前端多线程大文件下载实践,提速10倍,拿捏百度云盘

背景

没错,你没有看错,是前端多线程,而不是Node。这一次的探索起源于最近开发中,有遇到视频流相关的开发需求发现了一个特殊的状态码,他的名字叫做 206~

屏幕快照 2020-09-21 23.21.05

为了防止本文的枯燥,先上效果图镇文。(以一张3.7M 大小的图片为例)。

动画效果对比(单线程-左 VS 10个线程-右)

single-vs-multiple-donwload

时间对比(单线程 VS 10个线程)

image-20200915235421355

看到这里是不是有点心动,那么请你继续听我道来,那我们先抓个包来看看整个过程是怎么发生的。

`GET /360_0388.jpg HTTP/1.1
Host: limit.qiufeng.com
Connection: keep-alive
...
Range: bytes=0-102399

HTTP/1.1 206 Partial Content
Server: openresty/1.13.6.2
Date: Sat, 19 Sep 2020 06:31:11 GMT
Content-Type: image/jpeg
Content-Length: 102400
....
Content-Range: bytes 0-102399/3670627

...(这里是文件流)` 

可以看到请求这里多出一个字段 Range: bytes=0-102399 ,服务端也多出一个字段Content-Range: bytes 0-102399/3670627,以及返回的 状态码为 206.

那么Range是什么呢?还记得前几天写过一篇文章,是关于文件下载的,其中有提到大文件的下载方式,有个叫 Range的东西,但是上一篇作为系统性地介绍文件下载的概览,因此没有对range 进行详细介绍。

以下所有代码均在 https://github.com/hua1995116/node-demo/tree/master/file-download/example/download-multiple

Range 基本介绍

Range的起源

Range是在 HTTP/1.1 中新增的一个字段,这个特性也是我们使用的迅雷等支持多线程下载以及断点下载的核心机制。(介绍性的文案,摘录了一下)

首先客户端会发起一个带有Range: bytes=0-xxx的请求,如果服务端支持 Range,则会在响应头中添加Accept-Ranges: bytes来表示支持 Range 的请求,之后客户端才可能发起带 Range 的请求。

服务端通过请求头中的Range: bytes=0-xxx 来判断是否是进行 Range 处理,如果这个值存在而且有效,则只发回请求的那部分文件内容,响应的状态码变成206,表示Partial Content,并设置Content-Range。如果无效,则返回416状态码,表明Request Range Not Satisfiable。如果请求头中不带 Range,那么服务端则正常响应,也不会设置 Content-Range 等。

image.png

Range的格式为:

Range:(unit=first byte pos)-[last byte pos]

Range: 单位(如bytes)= 开始字节位置-结束字节位置

我们来举个例子,假设我们开启了多线程下载,需要把一个5000byte的文件分为4个线程进行下载。

  • Range: bytes=0-1199 头1200个字节
  • Range: bytes=1200-2399 第二个1200字节
  • Range: bytes=2400-3599 第三个1200字节
  • Range: bytes=3600-5000 最后的1400字节

服务器给出响应:

第1个响应

  • Content-Length:1200
  • Content-Range:bytes 0-1199/5000

第2个响应

  • Content-Length:1200
  • Content-Range:bytes 1200-2399/5000

第3个响应

  • Content-Length:1200
  • Content-Range:bytes 2400-3599/5000

第4个响应

  • Content-Length:1400
  • Content-Range:bytes 3600-5000/5000

如果每个请求都成功了,服务端返回的response头中有一个 Content-Range 的字段域,Content-Range 用于响应头,告诉了客户端发送了多少数据,它描述了响应覆盖的范围和整个实体长度。一般格式:

Content-Range: bytes (unit first byte pos) - [last byte pos]/[entity length]Content-Range:字节 开始字节位置-结束字节位置/文件大小

浏览器支持情况

主流浏览器目前都支持这个特性。

image-20200916002624861

服务器支持

Nginx

在版本nginx版本 1.9.8 后,(加上 ngx_http_slice_module)默认自动支持,可以将 max_ranges 设置为 0的来取消这个设置。

Node

Node 默认不提供 对 Range 方法的处理,需要自己写代码进行处理。

router.get('/api/rangeFile', async(ctx) => {
    const { filename } = ctx.query;
    const { size } = fs.statSync(path.join(__dirname, './static/', filename));
    const range = ctx.headers['range'];
    if (!range) {
        ctx.set('Accept-Ranges', 'bytes');
        ctx.body = fs.readFileSync(path.join(__dirname, './static/', filename));
        return;
    }
    const { start, end } = getRange(range);
    if (start >= size || end >= size) {
        ctx.response.status = 416;
        ctx.body = '';
        return;
    }
    ctx.response.status = 206;
    ctx.set('Accept-Ranges', 'bytes');
    ctx.set('Content-Range', `bytes ${start}-${end ? end : size - 1}/${size}`);
    ctx.body = fs.createReadStream(path.join(__dirname, './static/', filename), { start, end });
}) 

或者你可以使用 koa-send 这个库。

https://github.com/pillarjs/send/blob/0.17.1/index.js#L680

Range实践

架构总览

我们先来看下流程架构图总览。单线程很简单,正常下载就可以了,不懂的可以参看我上一篇文章。多线程的话,会比较麻烦一些,需要按片去下载,下载好后,需要进行合并再进行下载。(关于blob等下载方式依旧可以参看上一篇

1600705973008

服务端代码

很简单,就是对Range做了兼容。

router.get('/api/rangeFile', async(ctx) => {
    const { filename } = ctx.query;
    const { size } = fs.statSync(path.join(__dirname, './static/', filename));
    const range = ctx.headers['range'];
    if (!range) {
        ctx.set('Accept-Ranges', 'bytes');
        ctx.body = fs.readFileSync(path.join(__dirname, './static/', filename));
        return;
    }
    const { start, end } = getRange(range);
    if (start >= size || end >= size) {
        ctx.response.status = 416;
        ctx.body = '';
        return;
    }
    ctx.response.status = 206;
    ctx.set('Accept-Ranges', 'bytes');
    ctx.set('Content-Range', `bytes ${start}-${end ? end : size - 1}/${size}`);
    ctx.body = fs.createReadStream(path.join(__dirname, './static/', filename), { start, end });
}) 

html

然后来编写 html ,这没有什么好说的,写两个按钮来展示。

<!-- html -->
<button id="download1">串行下载</button>
<button id="download2">多线程下载</button>
<script data-original="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script> 

js公共参数

const m = 1024 * 520;  // 分片的大小
const url = 'http://localhost:8888/api/rangeFile?filename=360_0388.jpg'; // 要下载的地址 

单线程部分

单线程下载代码,直接去请求以blob方式获取,然后用blobURL 的方式下载。

download1.onclick = () => {
    console.time("直接下载");
    function download(url) {
        const req = new XMLHttpRequest();
        req.open("GET", url, true);
        req.responseType = "blob";
        req.onload = function (oEvent) {
            const content = req.response;
            const aTag = document.createElement('a');
            aTag.download = '360_0388.jpg';
            const blob = new Blob([content])
            const blobUrl = URL.createObjectURL(blob);
            aTag.href = blobUrl;
            aTag.click();
            URL.revokeObjectURL(blob);
            console.timeEnd("直接下载");
        };
        req.send();
    }
    download(url);
} 

多线程部分

首先发送一个 head 请求,来获取文件的大小,然后根据 length 以及设置的分片大小,来计算每个分片是滑动距离。通过Promise.all的回调中,用concatenate函数对分片 buffer 进行一个合并成一个 blob,然后用blobURL 的方式下载。

// script
function downloadRange(url, start, end, i) {
    return new Promise((resolve, reject) => {
        const req = new XMLHttpRequest();
        req.open("GET", url, true);
        req.setRequestHeader('range', `bytes=${start}-${end}`)
        req.responseType = "blob";
        req.onload = function (oEvent) {
            req.response.arrayBuffer().then(res => {
                resolve({
                    i,
                    buffer: res
                });
            })
        };
        req.send();
    })
}
// 合并buffer
function concatenate(resultConstructor, arrays) {
    let totalLength = 0;
    for (let arr of arrays) {
        totalLength += arr.length;
    }
    let result = new resultConstructor(totalLength);
    let offset = 0;
    for (let arr of arrays) {
        result.set(arr, offset);
        offset += arr.length;
    }
    return result;
}
download2.onclick = () => {
    axios({
        url,
        method: 'head',
    }).then((res) => {
        // 获取长度来进行分割块
        console.time("并发下载");
        const size = Number(res.headers['content-length']);
        const length = parseInt(size / m);
        const arr = []
        for (let i = 0; i < length; i++) {
            let start = i * m;
            let end = (i == length - 1) ?  size - 1  : (i + 1) * m - 1;
            arr.push(downloadRange(url, start, end, i))
        }
        Promise.all(arr).then(res => {
            const arrBufferList = res.sort(item => item.i - item.i).map(item => new Uint8Array(item.buffer));
            const allBuffer = concatenate(Uint8Array, arrBufferList);
            const blob = new Blob([allBuffer], {type: 'image/jpeg'});
            const blobUrl = URL.createObjectURL(blob);
            const aTag = document.createElement('a');
            aTag.download = '360_0388.jpg';
            aTag.href = blobUrl;
            aTag.click();
            URL.revokeObjectURL(blob);
            console.timeEnd("并发下载");
        })
    })
} 

完整示例

https://github.com/hua1995116/node-demo
`// 进入目录
cd file-download
// 启动
node server.js
// 打开 
http://localhost:8888/example/download-multiple/index.html` 

由于谷歌浏览器在 HTTP/1.1 对于单个域名有所限制,单个域名最大的并发量是 6.

这一点可以在源码以及官方人员的讨论中体现。

讨论地址

https://bugs.chromium.org/p/chromium/issues/detail?id=12066

Chromium 源码

// https://source.chromium.org/chromium/chromium/src/+/refs/tags/87.0.4268.1:net/socket/client_socket_pool_manager.cc;l=47
// Default to allow up to 6 connections per host. Experiment and tuning may
// try other values (greater than 0).  Too large may cause many problems, such
// as home routers blocking the connections!?!?  See http://crbug.com/12066.
//
// WebSocket connections are long-lived, and should be treated differently
// than normal other connections. Use a limit of 255, so the limit for wss will
// be the same as the limit for ws. Also note that Firefox uses a limit of 200.
// See http://crbug.com/486800
int g_max_sockets_per_group[] = {
    6,   // NORMAL_SOCKET_POOL
    255  // WEBSOCKET_SOCKET_POOL
}; 

因此为了配合这个特性我将文件分成6个片段,每个片段为520kb (没错,写个代码都要搞个爱你的数字),即开启6个线程进行下载。

我用单个线程和多个线程进行分别下载了6次,看上去速度是差不多的。那么为什么和我们预期的不一样呢?

image-20200919165242745

探索失败的原因

我开始仔细对比两个请求,观察这两个请求的速度。

6个线程并发

image-20200919170313455

单个线程

image-20200919170512650

我们按照3.7M 82ms 的速度来算的话,大约为 1ms 下载 46kb,而实际情况可以看到,533kb ,平均就要下载 20ms 左右(已经刨去了连接时间,纯 content 下载时间)。

我就去查找了一些资料,明白了有个叫做下行速度和上行速度的东西。

网络的实际传输速度要分上行速度和下行速度,上行速率就是发送出去数据的速度,下行就是收到数据的速度。ADSL是根据我们平时上网,发出数据的要求相对下载数据的较小这种习惯来实现的一种传输方式。我们说对于4M的宽带,那么我们的l理论最高下载速度就是512K/S,这就是所说的下行速度。 --百度百科

那我们现在的情况是怎么样的呢?

把服务器比作一根大水管,我来用图模拟一下我们单个线程和多个线程下载的情况。左侧为服务器端,右侧为客户端。(以下所有情况都是考虑理想情况下,只是为了模拟过程,不考虑其他一些程序的竞态影响。)

单线程

IMG_01

多线程

IMG_02

没错,由于我们的服务器是一根大水管,流速是一定的,并且我们客户端没有限制。如果是单线程跑的话,那么会跑满用户的最大的速度。如果是多线程呢,以3个线程为例子的话,相当于每个线程都跑了原先线程三分之一的速度。合起来的速度和单个线程是没有差别的。

下面我就分几种情况来讲解一下,什么样的情况才我们的多线程才会生效呢?

服务器带宽大于用户带宽,不做任何限制

这种情况其实我们遇到的情况差不多的。

服务器带宽远大于用户带宽,限制单连接网速

IMG_03

如果服务器限制了单个宽带的下载速度,大部分也是这种情况,例如百度云就是这样,例如明明你是 10M 的宽带,但是实际下载速度只有 100kb/s ,这种情况下,我们就可以开启多线程去下载,因为它往往限制的是单个TCP的下载,当然在线上环境不是说可以让用户开启无限多个线程,还是会有限制的,会限制你当前IP的最大TCP。这种情况下下载的上限往往是你的用户最大速度。按照上面的例子,如果你开10个线程已经达到了最大速度,因为再大,你的入口已经被限制死了,那么各个线程之间就会抢占速度,再多开线程也没有用了。

改进方案

由于 Node 我暂时没有找到比较简单地控制下载速度的方法,因此我就引入了 Nginx。

我们将每个TCP连接的速度控制在 1M/s。

加入配置 limit_rate 1M;

准备工作

1.nginx_conf

server {
    listen 80;
    server_name limit.qiufeng.com;
    access_log  /opt/logs/wwwlogs/limitqiufeng.access.log;
    error_log  /opt/logs/wwwlogs/limitqiufeng.error.log;

    add_header Cache-Control max-age=60;
    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Methods 'GET, OPTIONS';
    add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,range,If-Range';
    if ($request_method = 'OPTIONS') {
        return 204;
    }
    limit_rate 1M;
    location / {
        root 你的静态目录;
        index index.html;
    }
} 

2.配置本地 host

`127.0.0.1 limit.qiufeng.com` 

查看效果,这下基本上速度已经是正常了,多线程下载比单线程快了速度。基本是 5-6 : 1 的速度,但是发现如果下载过程中快速点击数次后,使用Range下载会越来越快(此处怀疑是 Nginx 做了什么缓存,暂时没有深入研究)。

修改代码中的下载地址
const url = 'http://localhost:8888/api/rangeFile?filename=360_0388.jpg';
变成
const url = 'http://limit.qiufeng.com/360_0388.jpg'; 

测试下载速度

image-20200919201613507

还记得上面说的吗,关于 HTTP/1.1 同一站点只能并发 6 个请求,多余的请求会放到下一个批次。但是 HTTP/2.0 不受这个限制,多路复用代替了 HTTP/1.x序列和阻塞机制。让我们来升级 HTTP/2.0 来测试一下。

需要本地生成一个证书。(生成证书方法: https://juejin.im/post/6844903556722475021)

server {
    listen 443 ssl http2;
    ssl on;
    ssl_certificate /usr/local/openresty/nginx/conf/ssl/server.crt;
    ssl_certificate_key /usr/local/openresty/nginx/conf/ssl/server.key;
    ssl_session_cache shared:le_nginx_SSL:1m;
    ssl_session_timeout 1440m;

    ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers RC4:HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    server_name limit.qiufeng.com;
 
    access_log  /opt/logs/wwwlogs/limitqiufeng2.access.log;
    error_log  /opt/logs/wwwlogs/limitqiufeng2.error.log;

    add_header Cache-Control max-age=60;
    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Methods 'GET, OPTIONS';
    add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,range,If-Range';
    if ($request_method = 'OPTIONS') {
        return 204;
    }
    limit_rate 1M;
    location / {
        root 你存放项目的前缀路径/node-demo/file-download/;
        index index.html;
    }
} 

10个线程

`将单个下载大小进行修改
const m = 1024 * 400;` 

image-20200919200203877

12个线程

image-20200919202302096

24个线程

image-20200919202138838

当然线程不是越多越好,经过测试,发现线程达到一定数量的时候,反而速度会更加缓慢。以下是 36个并发请求的效果图。

image-20200919202427985

实际应用探索

那么多进程下载到底有啥用呢?没错,开头也说了,这个分片机制是迅雷等下载软件的核心机制。

网易云课堂

https://study.163.com/course/courseLearn.htm?courseId=1004500008#/learn/video?lessonId=1048954063&courseId=1004500008

我们打开控制台,很容易地发现这个下载 url,直接一个裸奔的 mp4 下载地址。

image-20200920222053726

把我们的测试脚本从控制台输入进行。

// 测试脚本,由于太长了,而且如果仔细看了上面的文章也应该能写出代码。实在写不出可以看以下代码。
https://github.com/hua1995116/node-demo/blob/master/file-download/example/download-multiple/script.js 

直接下载

image-20200920221657541

多线程下载

image-20200920221853959

可以看到由于网易云课堂对单个TCP的下载速度并没有什么限制没有那么严格,提升的速度不是那么明显。

百度云

我们就来测试一下网页版的百度云。

image-20200919210106839

以一个 16.6M的文件为例。

打开网页版百度云盘的界面,点击下载

image-20200920222309345

这个时候点击暂停, 打开 chrome -> 更多 -> 下载内容 -> 右键复制下载链接

image-20200922004619680

依旧用上述的网易云课程下载课程的脚本。只不过你需要改一下参数。

`url 改成对应百度云下载链接
m 改成 1024 * 1024 * 2 合适的分片大小~` 

直接下载

百度云多单个TCP连接的限速,真的是惨无人道,足足花了217秒!!!就一个17M的文件,平时我们饱受了它多少的折磨。(除了VIP玩家)

image-20200919211105023

多线程下载

image-20200919210516632

由于是HTTP/1.1 因此我们只要开启6个以及以上的线程下载就好了。以下是多线程下载的速度,约用时 46 秒。

image-20200919210550840

我们通过这个图再来切身感受一下速度差异。

image-20200922010911389

真香,免费且只靠我们前端自己实现了这个功能,太tm香了,你还不赶紧来试试??

方案缺陷

1.对于大文件的上限有一定的限制

由于 blob 在 各大浏览器有上限大小的限制,因此该方法还是存在一定的缺陷。

image.png

2. 服务器对单个TCP速度有所限制

一般情况下都会有限制,那么这个时候就看用户的宽度速度了。

结尾

文章写的比较仓促,表达可能不是特别精准,如有错误之处,欢迎各位大佬指出。

回头调研下,有没有网页版百度云加速的插件,如果没有就造一个网页版百度云下载的插件~。

系列文章

参考文献

Nginx带宽控制 : https://blog.huoding.com/2015/03/20/423

openresty 部署 https 并开启 http2 支持 : https://www.gryen.com/articles/show/5.html

聊一聊HTTP的Range : https://dabing1022.github.io/2016/12/24/聊一聊HTTP的Range, Content-Range/

最后

如果我的文章有帮助到你,希望你也能帮助我,欢迎关注我的微信公众号 秋风的笔记,回复好友 二次,可加微信并且加入交流群,秋风的笔记 将一直陪伴你的左右。

image

查看原文

赞 91 收藏 62 评论 7

小马甲 收藏了文章 · 9月7日

js使用xpath

XPath的查询函数,在IE中与其他浏览器(Chrome、Firefox、Opear等)是不一样的,所以如果你的网站需要兼容IE,需要注意。
Xpath在IE中的查询函数为 document.selectNodes(xpath),其返回的是一个集合,通过for循环就可以读取所有的元素。
var nodes=document.selectNodes("//a[@href]");
for (i=0;i // 对 nodes[i] 执行操作;
}

// 这里要注意,IE对于元素的默认索引序列是从0开始的,不符号W3C标准
// 添加下面这行代码,让其符合W3C标准

document.setProperty("SelectionLanguage","XPath"); //设置语言选择
nodes=document.selectNodes("//div[1]");

而其他浏览器的查询函数,调用就稍微复杂一点,都是采用 document.evaluate 这个函数,返回的是一个枚举集合,需要使用 while 循环来枚举元素。

var result = document.evaluate("//a[@href]", document, null, XPathResult.ANY_TYPE, null);
var nodes = result.iterateNext(); //枚举第一个元素
while (nodes){
// 对 nodes 执行操作;
nodes=result.iterateNext(); //枚举下一个元素
}

// 如果只查找单个元素,可以简写成这样

nodes=document.evaluate("//div[1]", document).iterateNext();
查看原文

小马甲 赞了文章 · 9月7日

js使用xpath

XPath的查询函数,在IE中与其他浏览器(Chrome、Firefox、Opear等)是不一样的,所以如果你的网站需要兼容IE,需要注意。
Xpath在IE中的查询函数为 document.selectNodes(xpath),其返回的是一个集合,通过for循环就可以读取所有的元素。
var nodes=document.selectNodes("//a[@href]");
for (i=0;i // 对 nodes[i] 执行操作;
}

// 这里要注意,IE对于元素的默认索引序列是从0开始的,不符号W3C标准
// 添加下面这行代码,让其符合W3C标准

document.setProperty("SelectionLanguage","XPath"); //设置语言选择
nodes=document.selectNodes("//div[1]");

而其他浏览器的查询函数,调用就稍微复杂一点,都是采用 document.evaluate 这个函数,返回的是一个枚举集合,需要使用 while 循环来枚举元素。

var result = document.evaluate("//a[@href]", document, null, XPathResult.ANY_TYPE, null);
var nodes = result.iterateNext(); //枚举第一个元素
while (nodes){
// 对 nodes 执行操作;
nodes=result.iterateNext(); //枚举下一个元素
}

// 如果只查找单个元素,可以简写成这样

nodes=document.evaluate("//div[1]", document).iterateNext();
查看原文

赞 2 收藏 1 评论 0

小马甲 赞了文章 · 9月6日

Vue Router 4的一些新功能

Vue Router 4目前处于测试阶段。让我们看一下这个新版本中的一些很酷的功能。

Vue3支持

Vue 3引入了 createApp API,它改变了将插件添加到Vue实例的方式。由于这个原因,过去版本的Vue Router将不兼容Vue 3。

Vue Router 4引入了 createRouter API,可以创建一个可与Vue 3一起安装的路由器实例。

src/router/index.js

import { createRouter } from "vue-router";

export default createRouter({
  routes: [...],
});

src/main.js

import { createApp } from "vue";
import router from "./router";

const app = createApp({});
app.use(router);
app.mount("#app");

History选项

在之前的Vue Router版本中,你可以设置 mode 选项设置导航的模式。

hash 模式利用URL hash来模拟完整的URL,这样当URL发生变化时,页面不会被重新加载。history 利用HTML5 History API来实现URL导航,而不需要重新加载页面。

src/router/index.js

// Vue Router 3
const router = new VueRouter({
  mode: "history",
  routes: [...]
});

在Vue Router 4中,这些模式已被抽象到模块中,可以将其导入并分配给新的 history 选项。这种额外的灵活性使你可以根据需要自定义路由历史记录的实现。

src/router/index.js

// Vue Router 4
import { createRouter, createWebHistory } from "vue-router";

export default createRouter({
  history: createWebHistory(),
  routes: [],
});

动态路由

当路由使用新的 .addRoute 方法运行时,Vue Router 4将允许你添加动态路由。

这可能不是你每天都会使用的功能,但是确实有一些有趣的用例。例如,假设你正在为一个文件系统应用程序创建一个用户界面,并且希望动态添加路径,你可以这样做:

src/components/FileUploader.vue

methods: {
  uploadComplete (id) {
    router.addRoute({
      path: `/uploads/${id}`,
      name: `upload-${id}`,
      component: FileInfo
    });
  }
}

你还可以使用以下相关方法:

  • removeRoute
  • hasRoute
  • getRoutes

导航守卫可以返回值而不是next

导航守卫是Vue Router的钩子,允许用户在导航事件之前或之后运行自定义逻辑,如 beforeEach`beforeRouterEnter等。

它们通常用于检查用户是否有权限访问某个页面,验证动态路由参数,或者销毁监听器。

自定义逻辑运行后,next 回调用于确认路由、声明错误或重定向。

在第4版中,你可以从钩子中返回一个值或Promise来代替。这将允许像下面这样方便的速记。

// Vue Router 3
router.beforeEach((to, from, next) => {
  if (!isAuthenticated) {
    next(false);
  }
  else { 
    next();
  }
})

// Vue Router 4
router.beforeEach(() => isAuthenticated);

总结

这些只是版本4中新增的一些新功能。您可以在Vue Router Next仓库中了解更多信息。

感谢Eduardo和团队为Vue Router 4所做的巨大努力!

资源

最近整理了一份优质视频教程资源,想要的可以关注公众号即可免费领取哦!
image

查看原文

赞 5 收藏 3 评论 2

小马甲 收藏了文章 · 8月25日

访问github太慢?我写了一个开源小工具一键变快

file

前言

GitHub应该是广大开发者最常去的站点,这里面有大量的优秀项目,是广大开发者寻找资源,交友学习的好地方。尤其是前段时间GitHub公布了一项代码存档计划——Arctic Code Vault,要把代码埋入地下250米深的永久冻土层,可以将代码保存一千年。此外,GitHub 还为开发者在配置文件中设计了纪念徽章。

想想自己的代码可以作为人类的技术瑰宝被保存一千年,是不是有点自豪呢。

好了,言归正传。

虽然GitHub没有被Q,但是由于CDN服务器都在国外,所以国内访问GitHub的速度实在是慢的一匹,有时候经常页面刷不出,在我获取知识的道路上增加了重重的阻碍。

所以,我肝了3小时,写了一个在不用T子的情况下,加速GitHub访问速度的小工具,最后会分享给大家。

同时,这篇文章也会分享其他加速GitHub访问的方法。算是一个比较全的整理吧。

Let's get it!

自动生成最快访问host的小工具

GitHub在国内访问速度慢的原因其实有很多,但最主要的原因就是GitHub的分发加速网络域名遭到DNS的污染。为了解决这个问题,网上有很多文章提供了一个解决方案,就是通过修改Hosts文件,绕过国内的DNS解析,直接访问GitHub的CDN节点,从而达到加速的目的。

但是我看大多数关于此方法的介绍,只提供3个github的相关域名,而且需要在ipaddress.com 一个个去查,根据查到的ip,再去自己ping,肉眼选取最快的ip,自行编辑成IP+域名格式,贴到hosts文件里。

其实GitHub用到相关域名有很多,我整理了下,一共有十几个

github.global.ssl.fastly.net
github.com
assets-cdn.github.com
documentcloud.github.com
gist.github.com
help.github.com
nodeload.github.com
codeload.github.com
raw.github.com
status.github.com
training.github.com
avatars0.githubusercontent.com
avatars1.githubusercontent.com
avatars2.githubusercontent.com
avatars3.githubusercontent.com

这要是一个个去查,一个个去选取,也是挺麻烦的。

为此我写了一个工具,能自动的根据你当前ip,去寻找这十几个域名所对应最快的CDN节点,如果一个ip对应多个CDN节点,工具会自动帮你去ping 10次,取到平均值最小的CDN的IP地址。

你需要做的,只是把最终生成的结果贴到你的hosts文件中即可。

这个小工具,关注「元人部落」输入github即可获取到。

用法很简单,只需要执行以下命令即可运行

java -jar githubhost.jar

运行起来后,浏览器输入127.0.0.1:8880即可自动进行根据你当前Ip进行分析:

file

分析大概需要十几秒,进度条会自动刷新,等进度条满了之后,即可看到生成内容:

file

每个地区每个运营商可能运行出来的都不一样,所以得出结果后,你就可以把这段内容追加到你hosts文件中(如果不知道hosts存放位置,可以自行baidu),然后根据提示让hosts文件生效。

指定了CDN的访问地址,可以让你的github访问至少无卡顿了。

码云GitHub镜像站

码云提供了一个”码云急速下载“站,每天从github上同步一些项目。

https://gitee.com/mirrors

个人感觉应该不是所有的github项目都会同步过来,看仓库数量,有大概15k的项目

file

如果你想clone一些项目去研究,可以先在这里找找有没有。码云因为是国内开源项目站点,git clone速度自然不用担心,但是很可惜的是

1.这个镜像站点不是所有的github项目,不过大多数热门项目都会有

2.issue和release包也没有,只有代码

3.有一天的延迟。即你看到的是一天前的项目状态

4.因为不是github,所以你也没法通过这个push到github上的项目

GitHub镜像站

这个镜像站为:

https://github.com.cnpmjs.org/

进入之后,完全和github没有任何区别,访问也很快。

尤其是clone代码,那是飞快啊。。。

比如,你原先要clone,这样写

git clone https://github.com/kubernetes/kubernetes.git

现在改成:

git clone https://github.com.cnpmjs.org/kubernetes/kubernetes.git

试一下:

file

这个速度,应该无欲无求了吧。。。

不过这个方法可惜的是:

1.这个镜像站很不稳定,你时常会看到:

file

2.你每次clone还需要自己去修改url,有点不方便

3.你依旧没法push

GitClone站点

在寻找解决之道的途中,我又发现一个站点:gitclone

https://gitclone.com/

file

这是一个GitHub的缓存加速节点,也大约缓存了15k个项目,但是gitclone单独做了一个站点,里面可以进行搜索项目,甚至于还可以创建仓库。

gitclone的clone提供了多种方式来clone

file

但是搜索到的项目,最终查看还是跳转到GitHub相应的页面。

所以其实和gitee镜像站都差不多。换汤不换药,问题和之前几个镜像站点差不多,不过你只是要clone,还是不错的选择。

总结

其实在不用T子的情况下,方式无非就两种:

  1. 修改hosts,直接访问最快的CDN节点,这种方式优势在于原汁原味。
  2. 通过镜像去访问和clone,这种方式优势在于clone的速度。

个人推荐如果主要浏览为主,还是用上文推荐的工具去生成hosts进行配置,毕竟原汁原味,clone大项目的话,可以考虑以上镜像站点去加速下载。

关注作者

最后把这个开源工具分享给大家,关注「元人部落」公众号,并回复github即可获取到这个工具jar包。启动后访问127.0.0.1:8880端口即可自动生成。

一个坚持做原创的技术科技分享号,希望你能关注我,我每周会出一篇实用的原创技术文章,陪着你一起走,不再害怕。

img

查看原文

认证与成就

  • 获得 8 次点赞
  • 获得 34 枚徽章 获得 1 枚金徽章, 获得 8 枚银徽章, 获得 25 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2014-09-19
个人主页被 1k 人浏览