0%

  1. 项目地址
    https://github.com/weibsgz/nodeDemo/tree/main/express/todo

  2. mlab 注册账号 申请一个免费的 mongo 仓库 (添加用户等操作)

  1. 本地页面使用
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="GBK" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script
type="text/javascript"
src="//asserts.xcarimg.com/resource/common/jquery-1.8.3.min.js"
></script>
</head>
<body>
this. is index.html
<script>
//URL 10.200.16.100 anywhere启动的域名 所以服务端也可以设置这个域名保持同源,
//如果服务端随便起域名,需要设置允许跨域
const url = 'http://10.200.16.100:4000/getAutoHome'
$.ajax({
url: url,
success: function (res) {
console.log(res)
},
})
</script>
</body>
</html>


  1. 抓取目标页面接口
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
const axios = require('axios')
const express = require('express')
const app = express()

var Iconv = require('iconv-lite')
//主页 啥都没干
app.get('/', (req, res) => {
res.json({
err: 0,
})
})

//设置请求中间件 设置响应头信息
app.all('*', function (req, res, next) {
res.setHeader('Access-Control-Allow-Origin', 'http://10.200.16.100:7777')
res.setHeader('Access-Control-Allow-Methods', 'GET, POST')
res.setHeader(
'Access-Control-Allow-Headers',
'Content-Type,Content-Length, Authorization, Accept,X-Requested-With"'
)
res.setHeader('Content-Type', 'application/json;charset=utf-8')
next()
})

// app.use(express.json())
// app.use(express.urlencoded({ extended: false }))

//设置路由,前端就请求这个路由接口

app.get('/getAutoHome', (request, response) => {
// 开始请求你要爬的外域名接口
const url = 'https://www.yourWebSite.com/any/....'
//他的接口所需要的参数
const params = {
cityid: '330600',
}

axios
.get(url, {
params: { ...params },
// 如果接口返回的 是 gb2312的 需要用'iconv-lite' 插件 结合设置这个属性使用
responseType: 'arraybuffer',
headers: {
//伪造来源
referer: 'https://www.yourWebSite.com/',
origin: 'https://www.yourWebSite.com/',
},
})
.then((res) => {
const data = res.data
//因为接口请求的是GB2312的 所以需要转为UTF-8返回前端
const _json = Iconv.decode(Buffer.from(data), 'gb2312')
const html = Iconv.encode(_json, 'utf8').toString()
console.log(html)
response.json(JSON.parse(html))
})
})

//这个IP 是anywhere生成的静态服务地址 也可以随便起个域名,那么要设置跨域允许
app.listen(4000, '10.200.16.100', () => {
console.log('server is listenning at port 4000')
})


效果链接 https://github.com/weibsgz/svg-

SVG 实现 LOADING

<div class="container">
    <svg width="200" height="200" viewBox ="0 0 50 50">
        <!--
            要做一个只显示上下两截的圆 圆的周长是 2 * pi * r
            所以stroke-dasharray要用周长平分4等分(dasharray:实 - 虚 - 实。。)
            (2*3.1415926*22 )/ 4 = 34
            stroke-linecap = "round"   圆角
        -->
        <circle
            cx = "25"
            cy = "25"
            r="22"
            fill="none"
            stroke="#3bedcb"
            stroke-width="3"
            stroke-dasharray = "34"
            stroke-linecap = "round"
        >
         <!--
            from 0代表0度  25 25 代表旋转中心点
         -->
        <animateTransform
            attributeName="transform"
            attributeType="XML"
            type="rotate"
            from="0 25 25"
            to="360 25 25"
            dur="2s"
            repeatCount="indefinite"
        >
         </animateTransform>
         <!-- 外圆变色 -->
         <animate
            attributeName="stroke"
            values="#3be6cb; #82bcfe; #3be6cb"
            dur="2s"
            repeatCount="indefinite"
         >

      </animate>
        </circle>

        <circle
            cx = "25"
            cy = "25"
            r="12"
            fill="none"
            stroke="#02bcfe"
            stroke-width="3"
            stroke-dasharray = "19"
            stroke-linecap = "round"
        >
         <!--
            逆时针旋转 调换下FRORM 和 TO
            from to 可以简写为values= "360 25 25; 0 25 25"
         -->
        <animateTransform
            attributeName="transform"
            attributeType="XML"
            type="rotate"
            from = "360 25 25"
            to = "0 25 25"
            dur="2s"
            repeatCount="indefinite"
        >
         </animateTransform>
          <!-- 内圆变色 -->
          <animate
          attributeName="stroke"
          values="#82bcfe;#3be6cb;  #82bcfe;"
          dur="2s"
          repeatCount="indefinite"
       >

        </circle>
    </svg>
</div>

流动边框

1. 需要画2个path 正方的框,将第二个线框设置为MASK
2. 设置蒙版里边画一个圆 设置为白色 这样蒙版圆的这个区域就显示出线段二了
3. 将白色改为渐变色 白色透明到白色不透明
<style>
    body{
        background-color: #000;

    }
    .container{
        position: relative;
    }
    #svg{
        position: absolute;
        left: 0;right: 0;top: 0;bottom: 0;
        margin: auto;
    }
    #content{
        box-sizing: border-box;
        padding: 20px;
        width: 400px;
        height: 400px;
        position: absolute;
        left: 0;right: 0;top: 0;bottom: 0;
        margin: auto;
        z-index: 1;
    }
</style>
<div class="container">
    <svg id="svg" width="400" height="400" viewBox="0 0 400 400">
        <defs>
            <path
            id="fly-box-path"
            d="M5 5 L395 5 L395 395 L5 395 Z"
            fill="none"
            ></path>
            <mask id="fly-box-mask">
                <circle cx="0" cy="0" r="150"  fill="url(#grad2)">
                    <animateMotion
                        path="M5 5 L395 5 L395 395 L5 395 Z"
                        dur="3s"
                        repeatCount="indefinite"
                        rotate="auto"
                    >
                    </animateMotion>
                </circle>
            </mask>

            <!-- 设置渐变  fx="100%" 流行头部白光-->
            <radialGradient id="grad2" fx="100%">
                <stop offset="0%" style="stop-color:#fff;
                stop-opacity:1" />
                <stop offset="100%" style="stop-color:#fff;stop-opacity:0" />
            </radialGradient>
        </defs>
        <!-- 第一个线段 -->
        <use
            href="#fly-box-path"
            stroke-width="1"
            stroke="#235fa7">
        </use>
        <!-- 第二个线段 -->
        <use
            href="#fly-box-path"
            stroke-width="3"
            stroke="#4fd2dd"
            mask="url(#fly-box-mask)"
        >
        </use>


    </svg>
    <div id="content" style="color: #fff;">
        <slot>123</slot>
    </div>
</div>

效果链接 https://github.com/weibsgz/svg-

svg 基础知识

  • 设置颜色 可以 stroke=”red”这种具体色值 也可以 currentColor 继承父级颜色

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
      <!-- 竖十字  -->
    <div style="color: red; width: 100px; height: 100px;">
    <svg width="100%" height="100%" >
    <line x1="0" y1="50" x2="100" y2="50" stroke="currentColor"
    stroke-width="5" />
    <line x1="50" y1="0" x2="50" y2="100" stroke="currentColor"
    stroke-width="5"/>
    </svg>
    </div>

  • viewBox 的意义 :如果设置了画布大小是固定值,随着容器的大小变化,

  • 那么可能 SVG 的图像就显示不全了

  • viewBox 的四个参数分别代表:最小 X 轴数值;最小 y 轴数值;宽度;高度。
    可根据视窗自动放大缩小

    1
    2
    3
    4
    5
    6
    7
    8
    <div style="color: red; width: 20px; height: 20px;">
    <svg viewBox="0 0 100 100" >
    <line x1="0" y1="50" x2="100" y2="50" stroke="currentColor"
    stroke-width="5" />
    <line x1="50" y1="0" x2="50" y2="100" stroke="currentColor"
    stroke-width="5"/>
    </svg>
    </div>

    再来一个详细的列子 说明 viewBox

    1. 这是外部容器宽度 500 _ 200 缩放比是 500/50=10 200/20=10
      rect 矩形的实际宽度是 width _ 缩放比 height * 缩放比 就是宽 100 高 50
      包括 x y 的起始坐标点,stroke-width 默认是 1 也会都乘以 10

      1
      2
      3
       <svg width="500" height="200" viewBox="0 0 50 20" style="border:  1px solid red;">
      <rect x="20" y="10" width="10" height="5" style="stroke:rgb(0,0,0); fill:none"></rect>
      </svg>
      ### preserveAspectRatio 属性
    2. 默认参数是 xMidYMid Meet 共有 9 个属性
      xMinYMin,
      xMinYMid,
      xMinYMax,
      xMidYMin,
      xMidYMid,
      xMidYMax,
      xMaxYMin,
      xMaxYMid,
      xMaxYMax

    3. preserveAspectRatio 第二个参数 meet 代表保持宽高比将 viewBox 缩放为适合 viewPort 的大小
      meet 模式下,SVG 将优先采纳压缩比较小的作为最终压缩比,meet 为默认参数,比如下边是宽的压缩比
      是 500/200=2.5 高是 200/200=1 所以 viewBox 的还是维持 200 _ 200
      下边 rect 实际宽高比率是 1 就是维持 100 _ 50

    1
    2
    3
    <svg width="500" height="200" viewBox="0 0 200 200"  style="border:  1px solid red;">
    <rect x="100" y="100" width="100" height="50" stroke-width="10px" style="stroke:rgb(0,0,0); fill:none"></rect>
    </svg>
    • 如下图 viewBox 实际是显示区域内按照默认的 xMidYMid 对齐方式 占了一块 200 * 200 的区域

    • 我们的 RECT 要在这个区域内显示 他的 x y 是按照 viewBox 这个区域设置的

    • 如果我们设置了preserveAspectRatio = "xMaxYMin meet" 那么显示区域就在最右边
      其他属性同理

      avatar

    1. preserveAspectRatio 第二个参数还可以是 slice 代表保持宽高比将所有不在 viewBox 的裁剪掉
      slice 模式下,SVG 将优先采纳压缩比较大的作为最终压缩比

      1
      2
      3
      <svg width="500" height="200" viewBox="0 0 200 200" preserveAspectRatio="xMidYMax slice"  style="border:  1px solid red;">
      <rect x="100" y="100" width="100" height="50" stroke-width="10px" style="stroke:rgb(0,0,0); fill:none"></rect>
      </svg>
      以上代码 2.5 就是他采用的压缩比 因为设置的是 Ymax 那么 viewBox 的底部和容器重合,顶部变为 viewBox 的第四个参数 200 高度 * 2.5= 500 所以 viewBox 变为 500 高将超过容器的高度 rect 矩形的宽高也都将*2.5 x,y 也会都乘以 2.5
    2. preserveAspectRatio 第二个参数还可以是 none 代表不保持宽高比

    preserveAspectRatio="none" 那么 rect x,width 都乘以 2.5 y,height 都乘以 1

    svg 动画

    1. transform :translate 偏移量

    2. transform :rotate 旋转

    3. tranform: skewX 倾斜

    4. transform :scale 缩放

      1
      2
      3
      <svg width="500" height="200" viewBox="0 0 500 200"  style="border:  1px solid red;">
      <rect x="0" y="0" width="50" height="50" fill="red" transform = "translate(10,10) rotate(30) skewX(30)"></rect>
      </svg>
    5. matrix 复杂变形

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    先分为2组,然后套用公式
    [2,-1,50] => 新X坐标 = 2*x + (-1)*y + 50
    [1,2,0] => 新Y坐标 = 1*x + 2*y + 0

    新的4个顶点坐标:
    [0,0] => [50,0]
    [100,0] => [250,100]
    [100,50] => [200,200]
    [0,50] => [0,100]

    1
    2
    3
    4
      <svg width="500" height="200" viewBox="0 0 500 200"  style="border:  1px solid red;">
    <rect x="0" y="0" width="50" height="50" fill="red"
    transform = "matrix(2 1 -1 2 50 0)"></rect>
    </svg>
    ### 环形进度条
    • cx 和 cy 属性定义圆点的 x 和 y 坐标。如果省略 cx 和 cy,圆的中心会被设置为(0, 0)

    • r 属性定义圆的半径

    • martix 的作用在此图是为了让圆圈进度条起始位置在正上方,也就是说要旋转这个圆
      套入上边的公式 要解方程式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      <div class="container">
      <svg width="440" height="440" style="border: 1px solid red;" viewBox= "0 0 440 440">
      <!-- 第一个圆圈作为背景 -->
      <circle cx="220" cy="220" r="200" stroke-width="20" stroke="#ccc" fill="transparent"></circle>
      <!-- 第二个进度 -->
      <circle class="circle"
      transform = "matrix(0 -1 1 0 0 440)"
      cx="220" cy="220" r="200"
      stroke-width="20" stroke="blue" fill="none"></circle>
      </svg>
      </div>

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      <style>
      /*
      1131是圆的周长 2 * 3.1415926 * 200 = 1257
      stroke-dasharray 第一个参数代表绘制的进度长度 第二个参数代表 空白长度
      进度条就是让10 一直增长到1131就可以了
      */

      .circle {
      animation: circle 5s linear infinite;

      }

      @keyframes circle{
      from{
      stroke-dasharray: 10 1257;
      }
      to{
      stroke-dasharray: 1257 0;
      }
      }
      </style>

      path

    1.从 icon font 下载 图形 SVG 代码

    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
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
      <style>
    .logo{
    fill:white;
    stroke:#333;
    stroke-width: 5px;
    /* 1代表只执行1次 forwards代表执行完毕后停留在最后的状态 */
    animation: logo 5s linear 1 forwards;
    }

    @keyframes logo {
    0% {
    fill:white;
    stroke:#333;
    /* 通过path.getTotalLength()获得PATH的长度 */
    stroke-dasharray: 5962;
    /* 正数偏移相当于向左偏移 初始就是看不见了*/
    stroke-dashoffset: 5962;
    }
    50%{
    fill:white;
    stroke:#333;
    stroke-dasharray: 5962;
    /* 正数偏移相当于向左偏移 初始就是看不见了*/
    stroke-dashoffset: 0;
    }

    75% {
    fill:red;
    stroke:white
    }

    100% {
    fill:blue;
    stroke:white
    }
    }
    </style>

    <div>

    <svg viewBox="0 0 1024 1024" width="200" height="200">
    <path class="logo" d="M261.888 64c111.68 0 176.192 30.208 252.352 93.632 220.928 0 400 171.904 400 384 0 212.064-179.072 384-400 384s-400-171.936-400-384c0-127.904 65.152-241.216 165.312-311.008C264.16 153.344 150.272 64 261.888 64z m42.144 65.76c11.008 18.432 18.56 32.48 25.12 47.52 4.512 10.368 8.128 20.544 10.816 30.688l2.368 10.176 8.16 41.056-34.368 23.936c-86.208 60.064-137.92 155.232-137.92 258.496 0 176.128 149.888 320 336 320 186.176 0 336-143.872 336-320 0-173.216-144.864-315.2-326.72-319.872l-9.28-0.128h-23.136l-17.792-14.816C414.4 157.76 369.28 135.776 304 129.76z m127.968 283.84a48 48 0 0 1 48 48v96a48 48 0 0 1-96 0v-96a48 48 0 0 1 48-48z m256 0a48 48 0 0 1 48 48v96a48 48 0 0 1-96 0v-96a48 48 0 0 1 48-48z"></path>
    </svg>

    </div>

    <script>
    window.onload = function() {
    const logo = document.getElementsByClassName('logo')[0];
    console.log(logo.getTotalLength()) //5962
    }
    </script>

    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
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
      ### SVG SMIL 动画

    1. <set> 延迟功能。就是指:可以在特定时间之后修改某个属性值(也可以是 CSS 属性值)。

    2. <animate>
    - 类似 css3 keyframes
    3. <animateColor>
    - 已废弃 用animate就可以实现改变颜色
    4. <animateTransform>
    - 主要使用type 属性 改变大小之类的
    5. <animateMotion>

    - attributeName 设置哪个属性要动
    - attributeType 要过度的是 xml
    - to 移动到的位置
    - begin 开始的时间
    - dur 持续时间
    - repeatCount 动画执行的次数,"indefinite"表示无限循环
    - fill 不同于 SVG 上的填充色 这里是动画间隙的填充方式:freeze/remove
    - type transform 动画中的属性名类型 rotate scale 等

    //1S 后先向右移动 15 再 2S 后变色
    <div class="container">
    <svg width="200" height="200">
    <rect x="0" y="0" fill="red" width="100" height="50">
    <set attributeName="x" attributeType="XML" to="15" begin="1s"></set>
    <set attributeName="fill" attributeType="XML" to="blue" begin="2s"></set>
    </rect>
    </svg>
    </div>

    //球体 从左向右下移动 1 次 最后停留在当前位置 fill 在这里指停留不动
    <div class="container">
    <svg width="200" height="200">
    <circle cx="0" cy="0" r="30" fill="blue" stroke="blue" stroke-width="1">
    <animate attributeName="cx" attributeType="XML"
    from="0" to="50" dur="1s" repeatCount="1" fill="freeze" >
    </animate>

    <animate attributeName="cy" attributeType="XML"
    from="0" to="50" dur="1s" repeatCount="1" fill="freeze" >
    </animate>
    <!-- 小球放大从1 到2 -->
    <animateTransform attributeName="transform" attributeType="XML"
    begin="0s" dur="1s" type="scale"
    from="1" to="2" repeatCount="1" fill="freeze">
    </animateTransform>

    </circle>
    </svg>

    </div>

    //animateMotion 运动轨迹
    // animateMotion 牛逼之处在于它的 PATH 属性和 SVG 的 PATH 可以填一样的
    // 这就代表他可以沿 PATH 画的图轨迹去运动
    // rotate="auto | 0" 碰到拐点时候运动物体的动态
    <div class="container">
    <svg width="200" height="200">
    <!--
    M = moveto
    L = lineto
    Z = closepath
    -->
    <rect x="0" y="0" fill="red" width="10" height="10">
    <animateMotion path="M10 10 L110 10 L110 110 L10 110 Z"
    begin="0s" dur="3s" rotate="auto" repeatCount="indefinite"
    ></animateMotion>
    </rect>
    <!-- 从x:10 y:10位置开始 画到 110 10 再画到 110 110 再画到 10 110 最后闭合路径 就是一个正方形 -->
    <path d="M10 10 L110 10 L110 110 L10 110 Z" fill="none" stroke="green"></path>

    </svg>

    </div>

    //让小球运动到终点后原路返回
    //关键点 : - 不让路径闭合了(去掉 Z) - 需药 2 个 animateMotion 不同的 ID 第二个需要反着写一遍 PATH - 设置开始时间 begin="0s; backward.end +0.5s" 第一次是 0S 之后等第二动画运动回来过 0.5S 后执行 - begin="forward.end + 0.5s" 第一动画结束后 0.5S 后执行

    <div class="container">
    <svg width="200" height="200">
    <!--
    M = moveto
    L = lineto
    Z = closepath
    -->
    <rect x="0" y="0" fill="red" width="10" height="10">
    <animateMotion id="forward" path="M10 10 L110 10 L110 110 L10 110"
    begin="0s; backward.end +0.5s" dur="2s" rotate="0" fill="freeze"
    ></animateMotion>

    <!-- 返回的轨迹需要重写PATH 反着走一遍 -->
    <animateMotion id="backward" path="M10 110 L110 110 L110 10 L10 10"
    begin="forward.end + 0.5s" dur="2s" rotate="0" fill="freeze"
    ></animateMotion>

    <!-- 改变颜色 -->
    <animate id="redToBlue" attributeName="fill" attributeType="XML"
    from="red" to="blue" dur="1s" fill="freeze" begin="0; blueToRed.end + 0.5s">
    </animate>

    <animate id="blueToRed" attributeName="fill" attributeType="XML"
    from="blue" to="red" dur="1s" fill="freeze" begin="redToBlue.end + 0.5s">
    </animate>
    </rect>
    <!-- 注意这里去掉Z了 不闭合了为了让小球运动到头后原路返回 -->
    <path d="M10 10 L110 10 L110 110 L10 110 " fill="none" stroke="green"></path>


    </svg>

    </div>

    // 点击事件 点击 g 的时候 两个 RECT 会变色 同时会整体向 50 50 移动(animateTransform)写在<svg></svg>里
    <div class="container">
    <svg viewBox="0 0 200 200" width="200" height="200">
    <g id="rect1">
    <rect x="0" y="0" width="100" height="100" fill="red">
    <animate attributeName="fill" attributeType="XML"
    from="red" to="green"
    dur="2s"
    fill="freeze"
    begin="rect1.click"
    >
    </rect>
    </g>
    <rect x="0" y="100" width="100" height="100" fill="blue">
    <animate attributeName="fill" attributeType="XML"
    from="blue" to="green"
    dur="2s"
    fill="freeze"
    begin="rect1.click"
    >
    </animate>
    </rect>
    <animateTransform attributeName="transform" attributeType="XML"
    type="translate" from="0,0" to="50,50"
    dur="2s" fill="freeze" begin="rect1.click">
    </animateTransform>
    </svg>
    </div>

    ### 蒙版

    <!--
    矩形二设置成蒙版就不会挡住矩形一了
    蒙版内一个绿色的矩形和矩形一的红色颜色会融合

    然后蒙版上设置个圆 他没有填充色 就会显示出矩形一的红色
    -->
    <div class="container">
    <svg width="400" height="400">
    <defs>
    <mask id="test-mask">
    <rect x="5" y="5" width="390" height="390" fill="green"></rect>
    <circle cx="150" cy="150" r="50"></circle>
    </mask>
    </defs>

    <!-- 矩形一 -->
    <rect x="5" y="5" width="390" height="390" fill="red"></rect>

    <!-- 矩形二 -->
    <rect
    x="5" y="5"
    width="390" height="390"
    fill="blue"
    mask="url(#test-mask)"
    ></rect>
    </svg>

    </div>

    ### 渐变

    1. 线性
    <div class="container">
    <svg>
    <defs>
    <linearGradient id="grad1" >
    <stop offset="0%" style="stop-color:red" stop-opacity="0"/>
    <stop offset="100%" style="stop-color:green" stop-opacity="1" />
    </linearGradient>
    </defs>
    <ellipse cx="200" cy="70" rx="85" ry="55" fill="url(#grad1)" />
    </svg>
    </div>

    2. 放射性

    - CX,CY 和 r 属性定义的最外层圆和 Fx 和 Fy 定义的最内层圆 默认都是 50%

    <svg>
    <defs>
    <radialGradient id="grad2"
    fx="25%"
    fy="75%"
    cx="50%"
    cy="50%"
    r="50%">
    <stop offset="0%" style="stop-color:rgb(255,255,255);
    stop-opacity:0" />
    <stop offset="100%" style="stop-color:rgb(0,0,255);stop-opacity:1" />
    </radialGradient>
    </defs>
    <ellipse cx="200" cy="70" rx="85" ry="55" fill="url(#grad2)" />
    </svg>

    ### <defs /> <g/> <use/>

    1. <g>的 id 属性定义一个唯一的名称。
    2. <use>元素通过其 xlink:href 属性引用<g>元素。
    3. #符号定义属性值。
    4. <use>元素用于通过其 x 和 y 属性指定在何处显示重复使用的形状。

    <svg>
    <defs>
    <g id="shape">
    <rect x="40" y="40" width="40" height="40" />
    <circle cx="40" cy="40" r="40" />
    </g>
    </defs>

    <use href="#shape" x="40" y="40" />
    <use href="#shape" x="160" y="40" />

    </svg>




测试新电脑博客

所有指令源码地址

在 Vue,除了核心功能默认内置的指令 ( v-model 和 v-show ),Vue 也允许注册自定义指令。它的作用价值在于当开发人员在某些场景下需要对普通 DOM 元素进行操作。
Vue 自定义指令有全局注册和局部注册两种方式。先来看看注册全局指令的方式,通过 Vue.directive( id, [definition] ) 方式注册全局指令。然后在入口文件中进行 Vue.use() 调用。
批量注册指令,新建 directives/index.js 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import copy from './copy'
import longpress from './longpress'
// 自定义指令
const directives = {
copy,
longpress,
}

export default {
install(Vue) {
Object.keys(directives).forEach((key) => {
Vue.directive(key, directives[key])
})
},
}

在 main.js 引入并调用

1
2
3
4
import Vue from 'vue'
import Directives from './JS/directives'
Vue.use(Directives)

指令定义函数提供了几个钩子函数(可选):

bind: 只调用一次,指令第一次绑定到元素时调用,可以定义一个在绑定时执行一次的初始化动作。
inserted: 被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。
update: 被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值。
componentUpdated: 被绑定元素所在模板完成一次更新周期时调用。
unbind: 只调用一次, 指令与元素解绑时调用。

v-copy

需求:实现一键复制文本内容,用于鼠标右键粘贴。
思路:

  1. 动态创建 textarea 标签,并设置 readOnly 属性及移出可视区域
  2. 将要复制的值赋给 textarea 标签的 value 属性,并插入到 body
  3. 选中值 textarea 并复制
  4. 将 body 中插入的 textarea 移除
  5. 在第一次调用时绑定事件,在解绑时移除事件
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
39
40
41
42
const copy = {
bind(el, { value }) {
el.$value = value
el.handler = () => {
if (!el.$value) {
// 值为空的时候,给出提示。可根据项目UI仔细设计
console.log('无复制内容')
return
}
// 动态创建 textarea 标签
const textarea = document.createElement('textarea')
// 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘,同时将 textarea 移出可视区域
textarea.readOnly = 'readonly'
textarea.style.position = 'absolute'
textarea.style.left = '-9999px'
// 将要 copy 的值赋给 textarea 标签的 value 属性
textarea.value = el.$value
// 将 textarea 插入到 body 中
document.body.appendChild(textarea)
// 选中值并复制
textarea.select()
const result = document.execCommand('Copy')
if (result) {
console.log('复制成功') // 可根据项目UI仔细设计
}
document.body.removeChild(textarea)
}
// 绑定点击事件,就是所谓的一键 copy 啦
el.addEventListener('click', el.handler)
},
// 当传进来的值更新的时候触发
componentUpdated(el, { value }) {
el.$value = value
},
// 指令与元素解绑的时候,移除事件绑定
unbind(el) {
el.removeEventListener('click', el.handler)
},
}

export default copy

使用:给 Dom 加上 v-copy 及复制的文本即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<button v-copy="copyText">复制</button>
</template>

<script>
export default {
data() {
return {
copyText: 'a copy directives',
}
},
}
</script>

v-longpress

需求:实现长按,用户需要按下并按住按钮几秒钟,触发相应的事件
思路:

  1. 创建一个计时器, 2 秒后执行函数
  2. 当用户按下按钮时触发 mousedown 事件,启动计时器;用户松开按钮时调用 mouseout 事件。
  3. 如果 mouseup 事件 2 秒内被触发,就清除计时器,当作一个普通的点击事件
  4. 如果计时器没有在 2 秒内清除,则判定为一次长按,可以执行关联的函数。
  5. 在移动端要考虑 touchstart,touchend 事件
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
39
40
41
42
43
44
45
46
47
48
49
50
const longpress = {
bind: function (el, binding, vNode) {
if (typeof binding.value !== 'function') {
throw 'callback must be a function'
}
// 定义变量
let pressTimer = null
// 创建计时器( 2秒后执行函数 )
let start = (e) => {
if (e.type === 'click' && e.button !== 0) {
return
}
if (pressTimer === null) {
pressTimer = setTimeout(() => {
handler()
}, 2000)
}
}
// 取消计时器
let cancel = (e) => {
if (pressTimer !== null) {
clearTimeout(pressTimer)
pressTimer = null
}
}
// 运行函数
const handler = (e) => {
binding.value(e)
}
// 添加事件监听器
el.addEventListener('mousedown', start)
el.addEventListener('touchstart', start)
// 取消计时器
el.addEventListener('click', cancel)
el.addEventListener('mouseout', cancel)
el.addEventListener('touchend', cancel)
el.addEventListener('touchcancel', cancel)
},
// 当传进来的值更新的时候触发
componentUpdated(el, { value }) {
el.$value = value
},
// 指令与元素解绑的时候,移除事件绑定
unbind(el) {
el.removeEventListener('click', el.handler)
},
}

export default longpress

使用:给 Dom 加上 v-longpress 及回调函数即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<button v-longpress="longpress">长按</button>
</template>

<script>
export default {
methods: {
longpress () {
alert('长按指令生效')
}
}
}
</script>

v-debounce

背景:在开发中,有些提交保存按钮有时候会在短时间内被点击多次,这样就会多次重复请求后端接口,造成数据的混乱,比如新增表单的提交按钮,多次点击就会新增多条重复的数据。
需求:防止按钮在短时间内被多次点击,使用防抖函数限制规定时间内只能点击一次。
思路:

  1. 定义一个延迟执行的方法,如果在延迟时间内再调用该方法,则重新计算执行时间。
  2. 将事件绑定在 click 方法上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const debounce = {
inserted: function (el, binding) {
let timer
el.addEventListener('click', () => {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
binding.value()
}, 1000)
})
},
}

export default debounce

使用:给 Dom 加上 v-debounce 及回调函数即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<button v-debounce="debounceClick">防抖</button>
</template>

<script>
export default {
methods: {
debounceClick () {
console.log('只触发一次')
}
}
}
</script>

v-LazyLoad

背景:在类电商类项目,往往存在大量的图片,如 banner 广告图,菜单导航图,美团等商家列表头图等。图片众多以及图片体积过大往往会影响页面加载速度,造成不良的用户体验,所以进行图片懒加载优化势在必行。
需求:实现一个图片懒加载指令,只加载浏览器可见区域的图片。
思路:

  1. 图片懒加载的原理主要是判断当前图片是否到了可视区域这一核心逻辑实现的
  2. 拿到所有的图片 Dom ,遍历每个图片判断当前图片是否到了可视区范围内
  3. 如果到了就设置图片的 src 属性,否则显示默认图片

图片懒加载有两种方式可以实现,一是绑定 srcoll 事件进行监听,二是使用 IntersectionObserver 判断图片是否到了可视区域,但是有浏览器兼容性问题。

下面封装一个懒加载指令兼容两种方法,判断浏览器是否支持 IntersectionObserver API,如果支持就使用 IntersectionObserver 实现懒加载,否则则使用 srcoll 事件监听 + 节流的方法实现。

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
const LazyLoad = {
// install方法
install(Vue, options) {
const defaultSrc = options.default
Vue.directive('lazy', {
bind(el, binding) {
LazyLoad.init(el, binding.value, defaultSrc)
},
inserted(el) {
if (IntersectionObserver) {
LazyLoad.observe(el)
} else {
LazyLoad.listenerScroll(el)
}
},
})
},
// 初始化
init(el, val, def) {
el.setAttribute('data-src', val)
el.setAttribute('src', def)
},
// 利用IntersectionObserver监听el
observe(el) {
var io = new IntersectionObserver((entries) => {
const realSrc = el.dataset.src
if (entries[0].isIntersecting) {
if (realSrc) {
el.src = realSrc
el.removeAttribute('data-src')
}
}
})
io.observe(el)
},
// 监听scroll事件
listenerScroll(el) {
const handler = LazyLoad.throttle(LazyLoad.load, 300)
LazyLoad.load(el)
window.addEventListener('scroll', () => {
handler(el)
})
},
// 加载真实图片
load(el) {
const windowHeight = document.documentElement.clientHeight
const elTop = el.getBoundingClientRect().top
const elBtm = el.getBoundingClientRect().bottom
const realSrc = el.dataset.src
if (elTop - windowHeight < 0 && elBtm > 0) {
if (realSrc) {
el.src = realSrc
el.removeAttribute('data-src')
}
}
},
// 节流
throttle(fn, delay) {
let timer
let prevTime
return function (...args) {
const currTime = Date.now()
const context = this
if (!prevTime) prevTime = currTime
clearTimeout(timer)

if (currTime - prevTime > delay) {
prevTime = currTime
fn.apply(context, args)
clearTimeout(timer)
return
}

timer = setTimeout(function () {
prevTime = Date.now()
timer = null
fn.apply(context, args)
}, delay)
}
},
}

export default LazyLoad

使用,将组件内 标签的 src 换成 v-LazyLoad

1
2
<img v-LazyLoad="xxx.jpg" />

v-permission

背景:在一些后台管理系统,我们可能需要根据用户角色进行一些操作权限的判断,很多时候我们都是粗暴地给一个元素添加 v-if / v-show 来进行显示隐藏,但如果判断条件繁琐且多个地方需要判断,这种方式的代码不仅不优雅而且冗余。针对这种情况,我们可以通过全局自定义指令来处理。
需求:自定义一个权限指令,对需要权限判断的 Dom 进行显示隐藏。

思路:

  1. 自定义一个权限数组
  2. 判断用户的权限是否在这个数组内,如果是则显示,否则则移除 Dom
    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
    function checkArray(key) {
    let arr = ['1', '2', '3', '4']
    let index = arr.indexOf(key)
    if (index > -1) {
    return true // 有权限
    } else {
    return false // 无权限
    }
    }

    const permission = {
    inserted: function (el, binding) {
    let permission = binding.value // 获取到 v-permission的值
    if (permission) {
    let hasPermission = checkArray(permission)
    if (!hasPermission) {
    // 没有权限 移除Dom元素
    el.parentNode && el.parentNode.removeChild(el)
    }
    }
    },
    }

    export default permission

    使用:给 v-permission 赋值判断即可
1
2
3
4
5
6
7
<div class="btns">
<!-- 显示 -->
<button v-permission="'1'">权限按钮1</button>
<!-- 不显示 -->
<button v-permission="'10'">权限按钮2</button>
</div>

vue-waterMarker

需求:给整个页面添加背景水印

思路:

  1. 使用 canvas 特性生成 base64 格式的图片文件,设置其字体大小,颜色等。
  2. 将其设置为背景图片,从而实现页面或组件水印效果
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
function addWaterMarker(str, parentNode, font, textColor) {
// 水印文字,父元素,字体,文字颜色
var can = document.createElement('canvas')
parentNode.appendChild(can)
can.width = 200
can.height = 150
can.style.display = 'none'
var cans = can.getContext('2d')
cans.rotate((-20 * Math.PI) / 180)
cans.font = font || '16px Microsoft JhengHei'
cans.fillStyle = textColor || 'rgba(180, 180, 180, 0.3)'
cans.textAlign = 'left'
cans.textBaseline = 'Middle'
cans.fillText(str, can.width / 10, can.height / 2)
parentNode.style.backgroundImage = 'url(' + can.toDataURL('image/png') + ')'
}

const waterMarker = {
bind: function (el, binding) {
addWaterMarker(binding.value.text, el, binding.value.font, binding.value.textColor)
},
}

export default waterMarker

使用,设置水印文案,颜色,字体大小即可

1
2
3
<template>
<div v-waterMarker="{text:'lzg版权所有',textColor:'rgba(180, 180, 180, 0.4)'}"></div>
</template>

v-draggable

需求:实现一个拖拽指令,可在页面可视区域任意拖拽元素。
思路:

  1. 设置需要拖拽的元素为相对定位,其父元素为绝对定位。
  2. 鼠标按下(onmousedown)时记录目标元素当前的 left 和 top 值。
  3. 鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 left 和 top 值
  4. 鼠标松开(onmouseup)时完成一次拖拽
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
const draggable = {
inserted: function (el) {
el.style.cursor = 'move'
el.onmousedown = function (e) {
let disx = e.pageX - el.offsetLeft
let disy = e.pageY - el.offsetTop
document.onmousemove = function (e) {
let x = e.pageX - disx
let y = e.pageY - disy
let maxX = document.body.clientWidth - parseInt(window.getComputedStyle(el).width)
let maxY = document.body.clientHeight - parseInt(window.getComputedStyle(el).height)
if (x < 0) {
x = 0
} else if (x > maxX) {
x = maxX
}

if (y < 0) {
y = 0
} else if (y > maxY) {
y = maxY
}

el.style.left = x + 'px'
el.style.top = y + 'px'
}
document.onmouseup = function () {
document.onmousemove = document.onmouseup = null
}
}
},
}
export default draggable


使用: 在 Dom 上加上 v-draggable 即可

1
2
3
4
<template>
<div class="el-dialog" v-draggable></div>
</template>

ref 和 reactive

  1. 我的理解是 原则上是区分基本类型(ref)和引用类型(reactive)的。功能是一样的,其实是两种风格的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 风格1
let age = 10
let name = 'zhangsan'

function updatePerson() {
age = 20
}

// 风格2
let person = {
age: 10,
name: 'zhangsan',
sex: 'male'
}

function updatePerson() {
person.age = 20
}

  1. ref 代码片段 注意 ref 必有 value
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
<template>
<div id="app">
<h1>{{count}}</h1>
<h1>{{double}}</h1>
<button @click="increase">+1</button>
</div>

</template>

<script>
import { ref, computed, reactive } from 'vue'
export default {
name: "App",
setup() {
const count = ref(0)
const double = computed(() => {
return count.value * 2
})

const increase = () => {
count.value++;
}
return {
count, increase, double
}
}
};
</script>

  1. reactive 注意返回对象 形式(toRefs)
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
<template>
<div id="app">
<h1>{{count}}</h1>
<h1>{{double}}</h1>
<button @click="increase">+1</button>
</div>

</template>

<script>
import { computed, reactive, toRefs } from "vue";
export default {
name: "App",
setup() {
const data = reactive({
count: 0,
increase: () => {
//这里不需要value了
data.count++;
},
double: computed(() => {
return data.count * 2;
})
});

//直接返回data 也没发现什么问题
// return data

//如果要返回对象,用展开运算符 需要用 toRefs包装一下,让展开后都变成响应式对象
//这样 template中 直接用 count 可以不用 data.count
const refData = toRefs(data)
return {
...refData
};
}
};
</script>

生命周期对应

vue2 vue3
created setup
beforeCreate setup
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeDestroy onBeforeUnmount
destroyed onUnmounted
activated onActivated

onRenderTriggered 新增调试生命周期

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
import { onMounted, onUpdated, onRenderTriggered } from "vue";
export default {
name: "App",
setup() {
onMounted(() => {
console.log('onMounted')
})
onUpdated(() => {
console.log('onUpdated')
})
//调试用,数据变更可以看到属性
onRenderTriggered((event) => {
console.log(event)
})


打印的event 如下:
effect: ƒ reactiveEffect()
key: "count"
newValue: 1
oldTarget: undefined
oldValue: 0
target: {count: 1, double: ComputedRefImpl, increase: ƒ}
type: "set"


watch

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
39
40
41
42
43
44
45
46
47
import { computed, reactive, toRefs, ref, watch } from "vue";
export default {
name: "App",
setup() {

const data = reactive({
count: 0,
increase: () => {
//这里不需要value了
data.count++;
},
double: computed(() => {
return data.count * 2;
})
});

const greetings = ref('')
const updateGreeting = () => {
greetings.value += 'Hello!'
}

//只观察一个
// watch(greetings, (newV, oldV) => {
// document.title = 'update' + greetings.value
// })

//观察多个对象 打印的 newV 和 oldV 分别是数组 一个代表第greetings,第二个是count
//注意 如果直接观察data 打印出个proxy 想精确观察data.count 需要函数返回,否则
//watch中不接受data.count 因为他表示一个数字 没法watch
watch([greetings, () => data.count], (newV, oldV) => {
console.log(newV, oldV) //点击updateGreeting第一次打印 ["Hello!", 0] , ["", 0]
document.title = 'update' + greetings.value + data.count;
})

//直接返回data 也没发现什么问题
// return data

//如果要返回对象,用展开运算符 需要用 toRefs包装一下,让展开后都变成响应式对象
const refData = toRefs(data)
return {
...refData,
updateGreeting,
greetings
};
}
};

computed

  1. 如果一个对象中包含响应式的属性,需要用 computed 包裹这个对象返回
  2. 注意 computed 包裹对象后 需要.value
1
2
3
4
5
6
7
8
9
10
11
12
13
const currentPage = ref(params.currentPage);
const requestParams = computed(() => ({
currentPage: currentPage.value,
pageSize: params.pageSize
}))

//点击加载更多
const loadMorePage = () => {
store.dispatch(actionName, requestParams.value).then(() => {
currentPage.value++;
})
}

hooks 模块化

  1. 场景:需要抽离一个鼠标位置的功能
  2. 在 src/hooks/useMousePosition.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { ref, onMounted, onUnmounted } from 'vue'

export default function useMousePosition() {
const x = ref(0);
const y = ref(0);

const updateMouse = (e: MouseEvent) => {
x.value = e.pageX;
y.value = e.pageY;
}

onMounted(() => {
document.addEventListener('click', updateMouse)
})

onUnmounted(() => {
document.removeEventListener('click', updateMouse)
})

return {
x, y
}
}
  1. 在任意组件中使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div>
<h1>X:{{x}}, Y:{{y}}</h1>
</div>
</template>

import useMousePosition from './hooks/useMousePosition.ts'
export default {
name: "App",
setup() {
const { x, y } = useMousePosition()
return {
x, y
};

}

hooks 模块化 2

  1. 场景: 接口请求 loading 效果
  2. 在 src/hooks/useURLLoader.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import axios from 'axios'
import { ref } from 'vue'

export default function useURLLoader(url: string) {
const result = ref(null)
const loading = ref(true)
const loaded = ref(false)
const error = ref(null)

axios.get(url).then(res => {
loading.value = false;
loaded.value = true;
result.value = res.data;
}).catch(err => {
error.value = err;
loading.value = false;
})

return {
result, loading, error, loaded
}
}
  1. 在任意组件中使用
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
<template>
<div id="app">
<div v-if="loading">loading</div>
<div v-if="loaded"><img :src="result.message" /></div>
</div>
</template>

<script>
import { computed, reactive, toRefs, ref, watch } from "vue";
import useMousePosition from './hooks/useMousePosition.ts'
import useURLLoader from './hooks/useURLLoader.ts'


export default {
name: "App",
setup() {
const { result, loading, loaded, error } = useURLLoader('https://dog.ceo/api/breeds/image/random')

return {
result,
loading,
error,
loaded
};
}

一个弹窗组件 teleport(瞬间移动)

  1. 在 index.html 增加一个空 DIV#modal 作为弹窗组件的父级,和#app 并列(不会被业务样式干扰)
1
2
3
4
5
6
7
8
9
<body>
<noscript>
...
</noscript>
<div id="app"></div>
<div id="modal"></div>

</body>

  1. modal 组件 (teleport 用法,emits 需要作为一个 option 否则报警告)
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
39
40
41
42
43
<template>
<teleport to="#modal">
<div id="center" v-if="isOpen">
<h2>
<slot></slot>
</h2>
<button @click="closeHandler">关闭</button>
</div>
</teleport>
</template>
<script lang="ts">
export default {
props: {
isOpen: {
type: Boolean,
default: false
}
},
emits: ["close-modal"],
setup(props, context) {
const closeHandler = () => {
context.emit("close-modal");
};

return {
closeHandler
};
}
};
</script>
<style>
#center {
width: 200px;
height: 200px;
border: 2px solid black;
background: white;
position: fixed;
left: 50%;
top: 50%;
margin-left: -100px;
margin-top: -100px;
}
</style>
  1. 正常在业务组件中使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div id="app">
<button @click="onModalOpen">打开窗口</button>
Modal :isOpen="modalIsOpen" @close-modal="onModalClose">my modal...</Modal>
</div>
</template>


setup() {
const modalIsOpen = ref(false)
const onModalOpen = () => {
modalIsOpen.value = true;
}
const onModalClose = () => {
modalIsOpen.value = false;
}
}

异步组件 supense

  1. 场景:组件内容需要异步加载

AsyncShow.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<h1>{{result}}</h1>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
setup() {
return new Promise(resovle => {
setTimeout(() => {
return resovle({
result: "异步组件supense!"
});
}, 2000);
});
}
});
</script>
  1. 使用
1
2
3
4
5
6
7
8
<template #default>
<AsyncShow></AsyncShow>
//这里也可以包括多个组件
</template>
<template #fallback>
<h1>loading</h1>
</template>

createApp

  1. 不同于 vue2 属性和方法都在 vue 上挂在,容易污染全局,vue3 通过 createApp 创建 app 实例挂载
1
2
3
4
5
6
7
8
9
10
11
12
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)

app.use(...)
app.mixin(...)
app.directive(...)
app.component(...)


app.mount('#app')

VUE3 使用 es6 import 引入一些行为 可以更好的 treeShaking

1
import {nextTick,observable} from 'vue'
  1. 不同于 vue2 使用 Vue.nextTick Vue.observable 这样可以 treeShaking

hooks & 点击其他地方 让下拉菜单关闭

  1. hooks/useClickOutside.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 { ref, onMounted, onUnmounted } from "vue";
const useClickOutside = (elementRef) => {
const isClickOutside = ref(false);
const handleClick = (e) => {
if (elementRef.value) {
isClickOutside.value = (elementRef.value.contains(e.target)) ? false : true
}
}

onMounted(() => {
document.addEventListener("click", handleClick);
});

onUnmounted(() => {
document.removeEventListener("click", handleClick);
});

return isClickOutside
}

export default useClickOutside

  1. 使用
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
39
40
41
42
43
44
45
46
47
48
49
<template>
<div class="dropdown" ref="dropDownRef">
<a href="#" class="btn btn-outline-light my-2 dropdown-toggle" @click.prevent="toggleOpen">
{{title}}
</a>
<ul class="dropdown-menu" :style="{display: 'block'}" v-if="isOpen">
<slot></slot>
</ul>
</div>
</template>


<script lang="ts">
import { defineComponent, ref, watch } from "vue";
import useClickOutside from "../hooks/useClickOutside.js";
export default defineComponent({
name: "Dropdown",
props: {
title: {
type: String,
required: true
}
},

setup() {
const isOpen = ref(false);
//ref
const dropDownRef = ref(null);

const isClickOut = useClickOutside(dropDownRef);

watch(isClickOut, () => {
if (isClickOut.value && isOpen.value) {
isOpen.value = false;
}
});

const toggleOpen = e => {
isOpen.value = !isOpen.value;
};

return {
isOpen,
toggleOpen,
dropDownRef //ref
};
}
});
</script>

组件 v-model

v-model 语法语法糖拆解

  1. 组件内部 input 去掉 v-model 改成 :value = “inputRef.val”
  2. 组件内部 input 增加 @input 方法 其实就是不用 v-model 语法糖了
  3. 组件内部 props 上增加 modelValue
  4. 定义@input 方法 并在方法内部 emit(‘update:modelValue’)
  5. 父组件中使用 <child v-model="emailVal"></child>

子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<input :value="inputRef.val" @input="updateValue">


export default {
props: {
modelValue: String //组件绑定v-model 必须这么写
},
setup(props, { emit }) {
const inputRef = reactive({
val: props.modelValue || '',
});

const updateValue = (e) => {
inputRef.val = e.target.value;
//组件中绑定v-model的固定写法emit('update:modelValue'
emit('update:modelValue', e.target.value)
}

父组件 直接使用

1
2
3
4
5
6
7
<ValidateInput placeholder="shit" v-model="emailVal" :rules="emailRules"></ValidateInput>
{{emailVal}}
const emailVal = ref("weibin"); //默认值
return {
emailVal
}

组件 v-model 实践

父组件 可以传递多个 v-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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<div id="app">
<h3>vue3自定义组件的v-model</h3>
<p>msg: {{msg}} msg1: {{msg1}}</p>
<div class="child">
<h4>自定义input组件</h4>
<custom-input v-model:mv="msg" v-model:mv1="msg1"></custom-input>
<!-- <custom-input :model-value="msg" @update:model-value="msg = $event"></custom-input> -->
</div>
<p>count: {{count}}</p>
<div class="child">
<h4>自定义count组件</h4>
<custom-count v-model="count"></custom-count>
<!-- <custom-count :model-value="count" @update:model-value="count = $event"></custom-count> -->
</div>
</div>


<script>

const App = {
data(){
return {
msg: 'Hey! young guy.',
msg1:'test',
count: 0
}
}
};

const app = Vue.createApp(App);

//两种写法 直接子组件用v-model 或者 用拆开的写法 :value="mv1" @input="onNameInput" 可以直接发送给父组件 父组件直接接就完事了 双向绑定
app.component('custom-input', {
props: ['mv','mv1'],
computed:{
query: {
get(){
return this.mv
},
set(v){
this.$emit('update:mv', v);
}
}
},
methods:{
onNameInput(e){
console.log(e.target.value)
this.$emit('update:mv1',e.target.value)
}
},
template: `
<input v-model="query">
<input type="text" :value="mv1" @input="onNameInput"/>
`,
});

app.component('custom-count', {
props: {
modelValue: Number,
},
methods: {
increment() {
this.$emit('update:modelValue', ++this.modelValue);
},
decrement() {
this.$emit('update:modelValue', --this.modelValue);
},
},
template: `
<button @click="increment">+1</button> ~
<button @click="decrement">-1</button>
<p>{{modelValue}}</p>
`,
});

app.mount('#app');

路由

  1. 路由配置 main.ts
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
import { createRouter, createWebHistory } from 'vue-router'

import Home from './views/Home.vue'
import Login from './views/Login.vue'
import ColumnDetail from './views/ColumnDetail.vue'
//替代 2.0 中 的 mode
const routerHistory = createWebHistory()
const router = createRouter({
history: routerHistory,
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/Login',
name: 'Login',
component: Login
},
{
path: '/column/:id',
name: "column",
component: ColumnDetail
}
]
})
const app = createApp(App)
app.use(router)
app.mount('#app')
  1. 使用 useRoute 获取路由信息
1
2
3
4
5
6
7
8
9
10
11
import { useRoute } from "vue-router";
export default defineComponent({
setup() {
const route = useRoute();
return {
route
};
}
});

//这样页面上就可以通过 route.params.xxx 来获取信息了
  1. 使用 userRouter 来设置路由的行为
1
2
3
4
5
6
7
import { useRouter } from "vue-router";
setup() {
const router = useRouter();
//配置路由设置了 path: '/column/:id', 这里必须有params
router.push({ name: "column", params: { id: 1 } });

}

vuex

  1. main.js
1
2
3
4
5
6
import store from './store'

const app = createApp(App)
app.use(router)
app.use(store)
app.mount('#app')
  1. store.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 { createStore } from 'vuex'
import { testData, testPosts } from './testData'

const store = createStore({
state: {
columns: testData,
posts: testPosts,
user: { isLogin: false, name: "" }
},
mutations: {
login(state) {
state.user = { ...state.user, isLogin: true, name: 'weibin' }
console.log(state.user)
}
},
getters: {
biggerColumnsLen(state) {
return state.columns.filter(c => c.id > 2).length
}
}
})

export default store

  1. 组件内使用
1
2
3
4
5
6
7
8
9
10
import { useStore } from "vuex";

setup() {
const store = useStore();
const list = computed(() => store.state.columns);
console.log(list);
return {
list: list
};
}`

commit

1
2
3
4
5
setup() {
const store = useStore();
store.commit("login");
}

  1. getters
    getters 的作用是可以将 state 的值做一些处理
    否则在多地方用 都要自己写一遍 computed 处理
1
2
3
4
5
6
getters: {
biggerColumnsLen(state) {
return state.columns.filter(c => c.id > 2).length
}
}

1
2
3
4
5
6
const bigger = computed(() => store.getters.biggerColumnsLen);

return {
bigger
};

  1. getters 也可以是一个函数 传入参数
1
2
3
4
5
6
7
8
9
10
11
12
13
getters: {
getColumnById(state) {
return (id: number) => {
return state.columns.find(c => c.id === id)
}
},
getPostsByCid(state) {
return (cid: number) => {
return state.posts.filter(post => post.columnId === cid)
}
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
setup() {

const store = useStore();
const currentId = +route.params.id;

const column = computed(() =>
store.getters.getColumnById(currentId)
);

const list = computed(() =>
store.getters.getPostsByCid(currentId)
);

return {
column,
list
};
}

classList 设置在 DOM 上添加和删除一个类

  1. 比如 $el对象 (一般是VUE实例关联的DOM对象 本项目中components/base/loading/directive.js中用到)
    或者 dom 上的 ref (this.$refs.xxx)

    1
    2
    this.$refs.recommend.classList.add('test'),
    this.$el.classList.remove('recommend')

    以上的$el 指当前 vue 组件上的根节点

    本项目中根据指令封装

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    export function addClass(el, className) {
    if (!el.classList.contains(className)) {
    el.classList.add(className)
    }
    }

    export function removeClass(el, className) {
    el.classList.remove(className)
    }

    loading 自定义指令 (含动态参数)

  2. 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
<div class="recommend" v-loading:[loadingText]="loading"></div>

data() {
return {
loadingText: '疯狂加载中。。。',
}
},
computed: {
loading() {
//数据是否加载
return !this.sliders.length && !this.albums.length
},
},
  1. main.js 导入
1
2
3
4
5
6
7
8
9
10
11
import loadingDirective from '@/components/base/loading/directive'
createApp(App)
.use(store)
.use(router)
.use(lazyPlugin, {
loading: require('@/assets/images/default.png'),
})
.directive('loading', loadingDirective)
.mount('#app')


  1. 具体代码见 vue3-music base/loading

过渡动画

  1. 点击按钮进入子路由过渡,屏幕右侧抽屉效果

css

1
2
3
4
5
6
7
8
.slide-enter-active,.slide-leave-active {
transition:al 0.3s
}

.slide-enter-from, .slide-leave-to {
transform:translate3d(100%,0,0)
}

  1. 路由部分

name=”slide” slide 对应样式 slide-enter-active….
appear 代表进入就有动画

1
2
3
4
5
6
<router-view v-slot="{Component}">
<transition appear name="slide">
<component :is="Component">
</transition>
</router-view>

概念

  1. 语法 arr.reduce(callback,[initialValue])

  2. reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用 reduce 的数组。

1
2
3
4
5
6
7
8
9
callback (执行数组中每个值的函数,包含四个参数)

1、previousValue (上一次调用回调返回的值,或者是提供的初始值(initialValue))
2、currentValue (数组中当前被处理的元素)
3、index (当前元素在数组中的索引)
4、array (调用 reduce 的数组)

initialValue (作为第一次调用 callback 的第一个参数。)

示例

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//计算数组中每个元素出现的次数
const names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];

function CharacterTimes(arr) {
return arr.reduce((prev, cur, i) => {
if (prev.hasOwnProperty(cur)) {
prev[cur]++;
}
else {
prev[cur] = 1;
}
return prev
}, {})
}
console.log(CharacterTimes(names))

//将二维数组转化为一维
let arr = [[0, 1], [2, 3], [4, 5]];
const _arr = arr.reduce((prev, cur) => {
return prev.concat(cur)
}, [])

console.log(_arr)

//数组 对象 互相转换
const testData = [
{ id: 1, name: 'a' },
{ id: 2, name: 'b' }
]

const testData2 = {
1: { id: '1', name: 'a' },
2: { id: '2', name: 'b' }
}
//将 testData 转为 testData2 形式 (数组转对象)
function ArrayToObj(arr) {
return arr.reduce((prev, cur) => {
if (cur.id) {
prev[cur.id] = cur
}
return prev
}, {})
}
const res = ArrayToObj(testData)
console.log(res)

//将 testData2 转为 testData 形式 (对象转数组)
function ObjToArray(obj) {
return Object.keys(obj).map(key => obj[key])
}
console.log(ObjToArray(testData2))

组合三种折扣 取得最后价格

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
//商品打九折
function discount1(x) {
return x * 0.9
}

//满200 - 50
function discount2(x) {
return x > 200 ? x - 50 : x;
}

//再打九五折
function discount3(x) {
return x * 0.95
}

function compose(...arguments) {

if(arguments.length === 0) {
return arg => arg
}
return arguments.reduce((a,b)=>{

return (...args)=>{

return a(b(...args))
}
})
}

const getPrize = compose(discount3,discount2,discount1)

console.log( getPrize(200))


扁平化

1
2
3
4
5
6
7
8
9
10
11

var arr = [1,[2,3],[4,5,[6,7]]]

function flat(arr) {
return arr.reduce((prev,cur)=>{
return prev.concat(Array.isArray(cur) ? flat(cur) : cur)
},[])
}
console.log(JSON.stringify(flat(arr)))


数组去重

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
39
40
41
42
43
//数组去重
var a = [1,2,3,1,2,4,5]
var c = a.reduce((prev,cur)=>{
return prev.includes(cur) ? prev : prev.concat(cur)
},[])

console.log(c)


//对象数组去重
var arr_obj = [
{
id:'a',
c:'123'
},
{
id:'b',
c:'223'
},
{
id:'a',
c:'323'
}
]
var obj = []
var _arr_ = function(arr) {
return arr.reduce((prev,cur)=>{
// obj[cur.id] ? "" : obj[cur.id]=true && prev.push(cur)
// return prev

if(!obj.includes(cur.id)) {
obj.push(cur.id)
prev.push(cur)
}
return prev

},[])


}

console.log(_arr_(arr_obj))

使用 Vue2.6 提供的新 API Vue.observable 手动打造一个 Vuex(简单的 vuex 替代方案)

  1. 创建 store
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import Vue from 'vue'

// 通过Vue.observable创建一个可响应的对象
export const store = Vue.observable({
userInfo: {},
roleIds: []
})

// 定义 mutations, 修改属性
export const mutations = {
setUserInfo(userInfo) {
store.userInfo = userInfo
},
setRoleIds(roleIds) {
store.roleIds = roleIds
}
}

  1. 在组件中引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div>
{{ userInfo.name }}
</div>
</template>
<script>
import { store, mutations } from '../store'
export default {
computed: {
userInfo() {
return store.userInfo
}
},
created() {
mutations.setUserInfo({
name: '子君'
})
}
}
</script>

attrs

  1. 父组件上定义的属性,样式 可以直接完全让子组件继承

  2. 子组件可以设置inheritAttrs: false 父组件中的样式 不在跟元素中继承 在你定义的 v-bind=”$attrs” 的元素中继承

按钮权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
app.directive('has', {
// 比如 v-has:add="'user-create'"(注意,一定要用字符串引起来,否则是个函数了,)
// el是绑定过的元素 , binding.args = 'add' binding.value = 'user-crate'
//使用 USER.VUE <el-button type="danger" v-has="'user-patch-delete'"@click="handlePatchDel">批量删除</el-button>
beforeMount: function (el, binding) {
let actionList = storage.getItem('actionList')
let value = binding.value
let hasPermission = actionList.includes(value)
if (!hasPermission) {
el.style = 'display:none'
//光隐藏不够,删了他,做个异步因为在beforeMount钩子了 他还没有渲染到DOM上
setTimeout(() => {
el.parentNode.removeChild(el)
}, 0)
}
}
})

404 页面

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
//路由配置
{
name: '404',
path: '/404',
meta: {
title: '页面不存在'
},
component: () => import('@/views/404.vue')
}


// 判断当前地址是否可以访问
function checkPermission(path) {
console.log(router.getRoutes()) //所有路由的数组
let hasPermission = router
.getRoutes()
.filter((route) => route.path == path).length
if (hasPermission) {
return true
} else {
return false
}
}
// 导航守卫
router.beforeEach((to, from, next) => {
if (checkPermission(to.path)) { //或者直接用 router.hasRoute(to.name)
document.title = to.meta.title
next()
} else {
next('/404')
}
})


storeage 封装

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
import config from '../config/index.js'

//这个项目所有的storage存储在一个命名空间对象上

export default {
getStorage() {
return JSON.parse(window.localStorage.getItem(config.namespace) || '{}')
},

setItem(key, val) {
let storage = this.getStorage()
storage[key] = val
window.localStorage.setItem(config.namespace, JSON.stringify(storage))
},

getItem(key) {
return this.getStorage()[key]
},
clearItem(key) {
let storage = this.getStorage()
delete storage[key]
window.localStorage.setItem(config.namespace, JSON.stringify(storage))
},
clearAlliItem() {
window.localStorage.clear()
}
}


config.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
const env = import.meta.env.MODE || 'production'

const envConfig = {
development: {
baseApi: '/api',
mockApi:
'https://www.fastmock.site/mock/3aa8fc6d77553c0405584d0dc88c5457/api'
},
test: {
baseApi: 'test.xxx.com',
mockApi:
'https://www.fastmock.site/mock/3aa8fc6d77553c0405584d0dc88c5457/api'
},
production: {
baseApi: 'prod.xxx.com',
mockApi: ''
}
}

export default {
env,
mock: false,
namespace: 'manager',
...envConfig[env]
}


request.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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import axios from 'axios'
import config from '../config'
import { ElMessage } from 'element-plus'
import router from '../router'
import storage from './storage'

const NETWORKERROR = '网络错误'
const TOKEN_INVALID = 'TOKEN认证失败,请重新登录'
const ERROR_OK = 200

const service = axios.create({
baseURL: config.baseApi,
timeout: 8000
})

service.interceptors.request.use((req) => {
//TOKEN判断
// const headers = req.headers
// const userInfo = storage.getItem('userInfo')
// let token = ''
// if (userInfo) {
// token = userInfo.token
// }
// if (!headers.Authorization) headers.Authorization = 'Bearer ' + token

const headers = req.headers
const { token } = storage.getItem('userInfo') || {}

if (!headers.Authorization)
headers.Authorization = 'Bearer ' + token ? token : ''

return req
})

service.interceptors.response.use((res) => {
const { code, data, msg } = res.data
if (code === ERROR_OK) {
return data
} else if (code === 500001) {
ElMessage.error(TOKEN_INVALID)
setTimeout(() => {
router.push('/login')
}, 1500)
return Promise.reject(TOKEN_INVALID)
} else {
ElMessage.error(msg || NETWORKERROR)
return Promise.reject(msg || NETWORKERROR)
}
})

export default function request(options) {
options.method = options.method || 'get'
if (options.method.toLowerCase() === 'get') {
options.params = options.data
}
//取全局的MOCK 就是config/index.js配置的mock:false
let isMock = config.mock
if (typeof options.mock !== 'undefined') {
isMock = options.mock
}

//以防万一 线上地址一定用线上接口
if (config.env === 'production') {
service.defaults.baseURL = config.baseApi
} else {
service.defaults.baseURL = isMock ? config.mockApi : config.baseApi
}

return service(options)
}

;['get', 'post', 'put', 'delete', 'patch'].forEach((item) => {
request[item] = (url, data, options) => {
return request({
url,
method: item,
data,
...options
})
}
})


将一个 prop 限制在一个类型的列表中

1
2
3
4
5
6
7
8
9
10
11
12
13
export default {
name: 'Image',
props: {
src: {
type: String,
},
style: {
type: String,
validator: s => ['square', 'rounded'].includes(s)
}
}
};

这个验证函数接受一个 prop,如果 prop 有效或无效,则返回 true 或 false。

当单单传入的 true 或 false 来控制某些条件不能满足需求时,我通常使用这个方法来做。

按钮类型或警告类型(信息、成功、危险、警告)是最常见的用法、、。颜色也是一个很好的用途。

页面加载进度条

1
2
3
4
5
6
7
8
9
10
11
import NProgress from 'nprogress';

router.beforeEach(() => {
NProgress.start();
});

router.afterEach(() => {
NProgress.done();
});


移动端 100vh 问题

  • 在移动端使用 100vh 时,发现在 Chrome、Safari 浏览器中,因为浏览器栏和一些导航栏、链接栏导致不一样的呈现:

  • 你以为的 100vh === 视口高度

  • 实际上 100vh === 视口高度 + 浏览器工具栏(地址栏等等)的高度

  • 安装 vh-check npm install vh-check --save

1
2
3
import vhCheck from 'vh-check';
vhCheck('browser-address-bar');

  • 定义一个 CSS Mixin
1
2
3
4
5
6
@mixin vh($height: 100vh) {
height: $height;
height: calc(#{$height} - var(--browser-address-bar, 0px));
}


之后就是哪里不会点哪里。

instance

  • instance 方法本质是 instanceof 第一个变量是一个对象 A,第二个变量是一个函数 B,
  • 沿着 A 的原型链proto一直向上找,如果能找到一个proto等于 B 的 prototype,则返回 true;
  • 如果找到终点还没找到则返回 false。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function _instance(a, b) {
if (!a || !b) {
return false;
}
var A = a.__proto__;
var B = b.prototype;
while (true) {
if (A === null) {
return false;
} else if (A === B) {
return true;
} else {
//每次循环继续找__proto__
A = A.__proto__;
}
}
}

  • new 本质上就是调用构造函数生成一个对象,
  • 这个对象能够访问构造函数的的原型对象,因为我们来尝试模拟一下 new 的实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function _new(Fn, ...param) {
var obj = {};
//apply第二个参数接收一个数组,call则不是,call可以有n个参数有多少放多少就行
var result = Fn.call(obj, ...param);
obj.__proto__ = Fn.prototype;
// 如果构造函数返回一个对象,则将该对象返回,否则返回步骤1创建的对象
return typeof result === "object" ? result : obj;
}

function Person(name) {
this.name = name;
this.say = function() {
console.log("say", this.name);
};
}

var obj = _new(Person, "weibin");
console.log(obj);