Giter VIP home page Giter VIP logo

my-web-note's People

Contributors

dependabot[bot] avatar lizhenqi-jf avatar zhiwenxuan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

my-web-note's Issues

git commit 规范

类型

- feat :新功能
- fix :修复 bug
- chore :对构建或者辅助工具的更改
- refactor :既不是修复 bug 也不是添加新功能的代码更改
- style :不影响代码含义的更改 (例如空格、格式化、少了分号)
- docs : 只是文档的更改
- perf :提高性能的代码更改
- revert :撤回提交
- test :添加或修正测试

格式

type(scope?): subject #scope is optional

例子:

feat(login): add login page

参考

https://github.com/conventional-changelog/commitlint
https://juejin.im/post/5ea9ad78e51d454dca70fbe3

Mac 环境变量

Mac系统下的环境变量:

a. /etc/profile 
b. /etc/paths 
c. ~/.bash_profile 
d. ~/.bash_login 
e. ~/.profile 
f. ~/.bashrc

编写环境变量

vim ~/.bash_profile 

保存环境变量更改

source ~/.bash_profile 

关于注意力

在这个时代,信息已经不再是稀缺资源,注意才是,有效的提高注意能力非常重要!!!注意力确实是一个有意思的话题,心理学有个被玩烂的实验,叫做斯特普效应。

似乎不太容易吧?起码需要你花点时间转个弯。

如果没有这些字的干扰,你肯定很容易就能说出颜色,但为什么这些颜色在字体上出现就变得难以驾驭了?

因为注意干扰。往大了说,注意分为两大类有意识的注意和无意识的注意。

有意识的注意:从一堆扑克牌中找出红桃A。

无意识的注意:低头走路时对面突然出现美女,你盯着看。

所以,提高注意能力的本质就是:加强有意识注意的集中程度和持续时间,尽量避免无意识注意对大脑的干扰。

现在进入正题:

1. 不要浪费你的注意力大脑是以天为周期的,每天你的注意力是有限的。

注意力和意志力一样,都属于有限资源,用完了就没了,这点我想很多人都应该有体会,LOL单排20把以后,基本上你的注意力已经很难集中了,会在不经意间犯很多小错误,可能自己都意识不到。你可以尝试回放你的视频录像,对比之后几把和之前几把的失误次数。在工作中,长时间的脑力劳动你可能很难再回去看书了,就算动用意志力强迫自己看书也非常容易分神。这种现象时有科学解释的:《意志力:再度发现人类最强的力量》一书的作者罗伊鲍曼斯曾为美国《商业周刊》撰文论述该现象。他的是活跃我们大脑细胞的化学物质来源于血液中的血糖,这是一个自控的过程,包括控制我们的专注和集中注意力,这一过程会耗费能量。当我们努力思考、计算做决定的时候,我们消耗体内的血糖,同时也消耗我们的意志力。

**有句古话,叫做玩物丧志,“志”是需要意志力和注意力达成的,而“玩物”的过程浪费了我们大量的注意资源。所以,起床先做重要的事情,如果一天是从娱乐开始,那么你接下来的工作学习会很吃力。可以在工作时放一杯水,每次分神喝水,稍作休息就喝掉一点,并且告诉自己,我的注意在慢慢减少。

2. 减少无意识注意对你的影响我们进入注意是需要时间的

读一本书,开始的一段你可能需要读几遍才能搞懂,那个时间段是注意力慢慢开始集中的时间,效率较低。在2005年的一份研究报告中表示,我们需要花25分钟时间才能把已经分散了的注意力回到原有状态,并且你要记得,之前你已经花了近11分钟完成注意积累过程。网络信息时代的发展,我们的大量信息碎片化,随手拿手机翻阅都有大量不同种类的信息以不同方式干扰我们的注意,并且这样碎片化的吸收效果并不是很好。当你需要阅读,需要头脑风暴的时候,给自己设置一个时间段,一般以一个小时为周期,这个时间段,找一个安静的环境,静音你的手机,拿走桌上的小玩具,让自己真正的做到与任务与书本独处。

3. 跑神是大脑的一项基本设置。不仅外界的信息会干扰你的注意,你的内心也会。

《别把脑袋放冰箱》一书的作者大卫罗克博士曾为《今日心理》撰文:有发现表明,无论完成什么任务,注意力偶尔转移会影响表现,而这些注意力的偶尔转移会激发**前额叶。**前额叶位于前额叶内,在前额**附近。当你考虑自己和他人的时候,这个区域就会活跃起来。大脑的这一区域也属于名为“默认设置”的网络。当你无所事事的时候,这个网络就处于活跃状态。他总结:“当你对外的注意力放松的时候,这个默认的大脑网络就会被激活,你的注意力转移为关注内部信号。”我们的大脑如同计算机,有屏幕保护设置,一定时间不操作就会自动进入屏保,而这个时间的长短可以通过锻炼调节,之后我会介绍一套训练方法的。

4. 相信自己比强迫自己更容易集中注意力

在上文中我引用过《意志力》一书:《意志力:再度发现人类最强的力量》一书的作者罗伊鲍曼斯曾为美国《商业周刊》撰文论述该现象。他的是活跃我们大脑细胞的化学物质来源于血液中的血糖,这是一个自控的过程,包括控制我们的专注和集中注意力,这一过程会耗费能量。当我们努力思考、计算做决定的时候,我们消耗体内的血糖,同时也消耗我们的意志力。这段话不仅说明注意与意志是有限资源,还可以看出,当我们强迫自己集中注意的时候,注意力和意志力同时运作,所消耗的能量要更多,所付出的代价更大,所达成的难度也更大。强迫自己做某一件事情的时候,效率也会变得低下,因为单位时间消耗能量石有上限的,消耗巨大能量用以强迫,那么用以专注的力量就会减少。所以,自信,相信自己读完这本书是可以的,不要给自己以不可能的预设并且试图动用意志力强迫自己,你需要告诉自己你能做到。当然,这个过程的建立需要花费一定时间练习,一些成功的经验自然会带来自信。很多人惊叹学霸学习时间之久,但可能对他们而言只不过是一件简单的事情,他们都没怎么动用意志力,只是做了该做的事情。

5. 不要一心二

用读一遍下文,明白意思,并且数出里面有多少字母a.kan wan dian zan de dou shi hao ren a ,jin tian ken ding hui you hao yun qi de .我们可以一边走路,一边看书,但是基本没有人可以做到一边逛淘宝一边和男朋友讲爱情,一边游戏一边哄女朋友开心,因为注意很难以分裂形式出现。多任务同时进行的时候,特别容易引发焦虑,比如明天要考试了,还有很多作业要写,最近忙死,很多人都有这样的抱怨。你可以想象一下,一天之中,从穿鞋,刷牙到晚上睡觉, 你已经完成了很多任务,之所以这些任务不会引发你的焦虑,一是因为习惯,无需动用注意,二是他们是顺序进行的,不互相干扰,这点非常符合注意的特征。所以,学会列计划,在规定的时间注意应该的事情,其他时间不要去想,你会发现其实很容易,你的焦虑来自大脑同时面对多项任务需要注意的时候的一种不适应。

6. 学会遗忘,学会记忆,减轻注意的压力

这事一项很玄奥的能力,并且很多人认为不必要,我这里所指遗忘为暂时遗忘,暂时的遗忘能够帮助你减轻大脑运行负担,但是我们的大脑似乎不敢我们这样随意的遗忘,那你就需要有一样载体暂时帮你记住。坚持做笔记、写日记,让自己能够心安理得的遗忘,能够有效提高你的效率。那很多人疑问了,那不是看书都没意义了吗?看了就忘。相信大家都知道记忆曲线,记忆的关键不在于短时记忆能输入多少信息,而在于如何保证不流失。笔记的温故,对长时记忆是非常有效果的,当然本片文章不是为了讲记忆,就不赘述了,多一句嘴,根据斯宾塞的实验过程,每个人都可以测试出自己的记忆曲线,属于自己独享的记忆曲线哦。

7. 专注练习

a. 冥想:内心观想莲花法闭上眼睛,然后放松,慢慢的集中注意到呼吸上来,用腹部呼吸,感受气流从鼻子进入胸部,然后腹部。在把注意集中到自己的大脑中,似乎能看到自己的鼻尖,然后慢慢的想象大脑中出现了一朵莲花,他的形状,他的每一个细节.......

b. 数字练习算24点:3 8 3 8(第一个给出答案的有奖励哦)数字迷宫、九宫格等方式

c. 有氧运动

8. 专注其实是有技巧的

a. 模型法注意力是一种资源,我们就要学会合理运用才能起到更好的效果,读一本书,如果内心中没有一定的模型建构,那么你的大量注意力需要先非配到记忆上来,或者大量时间是重复阅读。每一本书,每一个理论都是有着自己的理论模型的,比如看一头大象,你如果每一个细节去摸索要花费多大的注意力啊,只需要简单的记忆他的模型就好,下次见到你肯定能记住。我之前总结过一些模型,找到链接贴过来。

b. 联想法注意的内容前后关联越大,我们再次进入专注的状态就越容易。试想,打完游戏之后学数学,需要多长时间把心静下来?如果打完游戏之后陪女朋友谈恋爱,可能更容易进入状态吧?我们的注意力从一件事到另一件事所需时间和他们关联度成反比。所有,有效的进行联想不仅仅减轻记忆压力(这是记忆训练都不可绕开的一个高级学习模式),也能减轻注意压力。

c. 合理顺序打完游戏之后学数学,你可能需要10分钟静下心来,再花5分钟进入状态,但是你学完数学之后打游戏,可能一分钟不要就进入状态了。合理的安排你的注意顺序,可以节省你很多时间。原则:压力大的往前排重要的往前排需要更多注意力的往前排信息时代,决定成功的是对信息的处理能力,这里面不仅仅涉及到注意,还有记忆、意志力、想象力,这些我都会系统整理出书籍遇到较为合适的问题回答。

作者:极乐
链接:https://www.zhihu.com/question/20148429/answer/50487319
来源:知乎

Nginx 简单配置


#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       8888;
        server_name  localhost;
        root   /Users/macbook/workspace/nginx-html;
        #charset koi8-r;

        #access_log  logs/host.access.log  main;
	
        location /admin {
          try_files $uri $uri/ /admin.html;
        }

        location / {
          index  index.html;
          try_files $uri $uri/ /index.html;
        }
	

        location /api/ {
          proxy_pass http://localhost:8000;
          proxy_set_header Host $host;
        }


        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}
    include servers/*;
}

通过更改图片的源数据来旋转图片

// Based on: https://stackoverflow.com/a/46814952/283851

/**
 * Create a Base64 Image URL, with rotation applied to compensate for EXIF orientation, if needed.
 * 
 * Optionally resize to a smaller maximum width - to improve performance for larger image thumbnails.
 */
export async function getImageUrl(file: File, maxWidth: number|undefined) {
    return readOrientation(file).then(orientation => applyRotation(file, orientation || 1, maxWidth || 999999));
}

/**
 * @returns EXIF orientation value (or undefined)
 */
const readOrientation = (file: File) => new Promise<number|undefined>(resolve => {
    const reader = new FileReader();

    reader.onload = () => resolve((() => {
        const view = new DataView(reader.result as ArrayBuffer);

        if (view.getUint16(0, false) != 0xFFD8) {
            return;
        }

        const length = view.byteLength;
        
        let offset = 2;

        while (offset < length) {
            const marker = view.getUint16(offset, false);

            offset += 2;

            if (marker == 0xFFE1) {
                offset += 2;

                if (view.getUint32(offset, false) != 0x45786966) {
                    return;
                }

                offset += 6;

                const little = view.getUint16(offset, false) == 0x4949;
                
                offset += view.getUint32(offset + 4, little);
                
                const tags = view.getUint16(offset, little);
                
                offset += 2;

                for (var i = 0; i < tags; i++) {
                    if (view.getUint16(offset + (i * 12), little) == 0x0112) {
                        return view.getUint16(offset + (i * 12) + 8, little);
                    }
                }
            } else if ((marker & 0xFF00) != 0xFF00) {
                break;
            } else {
                offset += view.getUint16(offset, false);
            }
        }
    })());

    reader.readAsArrayBuffer(file.slice(0, 64 * 1024));
});

/**
 * @returns Base64 Image URL (with rotation applied to compensate for orientation, if any)
 */
const applyRotation = (file: File, orientation: number, maxWidth: number) => new Promise<string>(resolve => {
    const reader = new FileReader();

    reader.onload = () => {
        const url = reader.result as string;

        const image = new Image();

        image.onload = () => {
            const canvas = document.createElement("canvas");
            const context = canvas.getContext("2d")!;

            let { width, height } = image;

            const [outputWidth, outputHeight] = orientation >= 5 && orientation <= 8
                ? [height, width]
                : [width, height];

            const scale = outputWidth > maxWidth ? maxWidth / outputWidth : 1;

            width = width * scale;
            height = height * scale;

            // set proper canvas dimensions before transform & export
            canvas.width = outputWidth * scale;
            canvas.height = outputHeight * scale;

            // transform context before drawing image
            switch (orientation) {
                case 2: context.transform(-1, 0, 0, 1, width, 0); break;
                case 3: context.transform(-1, 0, 0, -1, width, height); break;
                case 4: context.transform(1, 0, 0, -1, 0, height); break;
                case 5: context.transform(0, 1, 1, 0, 0, 0); break;
                case 6: context.transform(0, 1, -1, 0, height, 0); break;
                case 7: context.transform(0, -1, -1, 0, height, width); break;
                case 8: context.transform(0, -1, 1, 0, 0, width); break;
                default: break;
            }
            
            // draw image
            context.drawImage(image, 0, 0, width, height);

            // export base64
            resolve(canvas.toDataURL("image/jpeg"));
        };

        image.src = url;
    }

    reader.readAsDataURL(file);
});

参考:https://gist.github.com/mindplay-dk/72f47c1a570e870a375bd3dbcb9328fb

Nodejs 下载文件到本地

const fs = require('fs')
const https = require('https')

function downloadFileAsync(uri, dest) {
  return new Promise(resolve => {
    const file = fs.createWriteStream(dest);

    https.get(uri, (res) => {
      if (res.statusCode !== 200) {
        resolve(res.statusCode);
        return;
      }

      res.on('end', () => {
      });

      file.on('finish', () => {
        file.close(resolve);
      }).on('error', (err) => {
       // delete failed file
        fs.unlink(dest);
        resolve(err.message);
      })

      res.pipe(file);
    });
  });
}

《刻意练习》读书笔记

有目的的练习的四个特点

  1. 每次练习确定好总体目标,然后拆解成一个个短期可以完成的目标,并制定计划
  2. 练习时需要专注,需要把全部注意力放在任务上
  3. 一次小练习完后,要有反馈,确定完成练习效果。如果效果不佳,要及时总结
  4. 走出舒适区。每次小练习要比上次有难度。

AWS Lambda Function for zipping files from S3

Main entry

// index.js

'use strict';
const S3Zip = require('./s3-zip')

const params = {
  files: [
    {
      fileName: '1.jpg',
      key: 'key1.JPG'
    },
    {
      fileName: '2.jpg',
      key: 'key2.JPG'
    }
  ],
  zippedFileKey: 'zipped-file-key.zip'
}

exports.handler = async event => {
  const s3Zip = new S3Zip(params);
  await s3Zip.process();

  return {
    statusCode: 200,
    body: JSON.stringify(
      {
        message: 'Zip file successfully!'
      }
    )
  };

}

Zip util

// s3-zip.js

'use strict';
const fs = require('fs');
const AWS = require("aws-sdk");

const Archiver = require('archiver');
const Stream = require('stream');

const https = require('https');
const sslAgent = new https.Agent({
  KeepAlive: true,
  rejectUnauthorized: true
});
sslAgent.setMaxListeners(0);
AWS.config.update({
  httpOptions: {
    agent: sslAgent,
  },
  region: 'us-east-1'
});

module.exports = class S3Zip {
  constructor(params, bucketName = 'default-bucket') {
    this.params = params;
    this.BucketName = bucketName;
  }

  async process() {
    const { params, BucketName } = this;
    const s3 = new AWS.S3({ apiVersion: '2006-03-01', params: { Bucket: BucketName } });

    // create readstreams for all the output files and store them
    const createReadStream = fs.createReadStream;
    const s3FileDwnldStreams = params.files.map(item => {
      const stream = s3.getObject({ Key: item.key }).createReadStream();
      return {
        stream,
        fileName: item.fileName
      }
    });

    const streamPassThrough = new Stream.PassThrough();
    // Create a zip archive using streamPassThrough style for the linking request in s3bucket
    const uploadParams = {
      ACL: 'private',
      Body: streamPassThrough,
      ContentType: 'application/zip',
      Key: params.zippedFileKey
    };

    const s3Upload = s3.upload(uploadParams, (err, data) => {
      if (err) {
        console.error('upload err', err)
      } else {
        console.log('upload data', data);
      }
    });

    s3Upload.on('httpUploadProgress', progress => {
      // console.log(progress); // { loaded: 4915, total: 192915, part: 1, key: 'foo.jpg' }
    });

    // create the archiver
    const archive = Archiver('zip', {
      zlib: { level: 0 }
    });
    archive.on('error', (error) => {
      throw new Error(`${error.name} ${error.code} ${error.message} ${error.path} ${error.stack}`);
    });

    // connect the archiver to upload streamPassThrough and pipe all the download streams to it
    await new Promise((resolve, reject) => {
      console.log("Starting upload of the output Files Zip Archive");

      s3Upload.on('close', resolve());
      s3Upload.on('end', resolve());
      s3Upload.on('error', reject());

      archive.pipe(streamPassThrough);
      s3FileDwnldStreams.forEach((s3FileDwnldStream) => {
        archive.append(s3FileDwnldStream.stream, { name: s3FileDwnldStream.fileName })
      });
      archive.finalize();

    }).catch((error) => {
      throw new Error(`${error.code} ${error.message} ${error.data}`);
    });

    // Finally wait for the uploader to finish
    await s3Upload.promise();

  }
}

Nuxtjs 嵌套组件定义

语法

pages 目录下,定义一个文件夹和文件,且名字一样,如下:

pages/
--| users/
-----| _id.vue
-----| index.vue
--| users.vue

然后在 users.vue 文件中,使用 <nuxt-child /> 匹配子路由,如下

// users.vue
<template>
  <nuxt-child />
</template>

之后 Nuxt会自动生成如下配置:

router: {
  routes: [
    {
      path: '/users',
      component: 'pages/users.vue',
      children: [
        {
          path: '',
          component: 'pages/users/index.vue',
          name: 'users'
        },
        {
          path: ':id',
          component: 'pages/users/_id.vue',
          name: 'users-id'
        }
      ]
    }
  ]
}

详细文档:
https://nuxtjs.org/guide/routing#nested-routes

根据两个地点的GPS,求距离

  // 返回距离,单位 m
  getGPSDistance(start, end) {
        let lat1 = (Math.PI / 180) * start.lat;
        let lat2 = (Math.PI / 180) * end.lat;
        let lon1 = (Math.PI / 180) * start.lng;
        let lon2 = (Math.PI / 180) * end.lng;

        let R = 6371000; //地球半径
        let d = Math.acos(Math.sin(lat1) * Math.sin(lat2) + Math.cos(lat1) * Math.cos(lat2) *
            Math.cos(lon2 - lon1)) * R;
        return d;
    }

Nodejs 对称加密解密

const crypto = require('crypto');

const algorithm = 'aes-256-ctr';
const secretKey = 'vOVH6sdmpNWjRRIqCc7rdxs01lwHzfr3';
const iv = crypto.randomBytes(16);

const encrypt = (text) => {

    const cipher = crypto.createCipheriv(algorithm, secretKey, iv);

    const encrypted = Buffer.concat([cipher.update(text), cipher.final()]);

    return {
        iv: iv.toString('hex'),
        content: encrypted.toString('hex')
    };
};

const decrypt = (hash) => {

    const decipher = crypto.createDecipheriv(algorithm, secretKey, Buffer.from(hash.iv, 'hex'));

    const decrpyted = Buffer.concat([decipher.update(Buffer.from(hash.content, 'hex')), decipher.final()]);

    return decrpyted.toString();
};

module.exports = {
    encrypt,
    decrypt
};

参考: https://attacomsian.com/blog/nodejs-encrypt-decrypt-data

Base64 转 Uint8Array

const base64ToUint8Array = base64 => {
 binary_string = window.atob(base64);
 const len = binary_string.length;
 const bytes = new Uint8Array(len);
 for (let i = 0; i < len; i++) {
  bytes[i] = binary_string.charCodeAt(i);
 }
 return bytes;
}

const uint8Array = base64ToUint8Array(base64);
// 获取 buffer
const buffer = uint8Array.buffer;

Canvas Transform

CanvasRenderingContext2D.transform(a, b, c, d, e, f)

这个函数的参数各自代表如下:

a:水平方向的缩放

b:水平方向的倾斜偏移

c:竖直方向的倾斜偏移

d:竖直方向的缩放

e:水平方向的移动

f:竖直方向的移动

scale, translate, rotate

3个方法都是基于transform的,也就是transform的快捷方式

缩放:scale(a, d) 等同于 matrix(a, 0, 0, d, 0, 0);
平移:translate(e, f) 等同于 matrix(1, 0, 0, 1, e, f);
旋转:rotate(deg) 等同于 matrix(cos(deg), sin(deg), -sin(deg), cos(deg), 0, 0);

一个图形同时需要缩放平移和旋转,就是3个单独的矩阵相乘

举例 transform:translate(10px,20px) rotate(37deg) scale(1.5,2)

image

即 matrix(1.2,0.9,-1.2,1.6,10,20)

详细了解可参考

Transform 的简单理解
MDN
旋转矩阵(Rotation Matrix)的推导及其应用

给 Lambda Function 添加 image-magic layer

Lambda Function 需要用某些库时,可以通过添加 layer 达到目的。下面以添加 image-magic 为例进行说明:

  1. 首先创建 Layer, image-magic 可以通过以下方式创建。
    image

点击 deploy 按钮即可安装。详细可见 文档

  1. Lambda Function 选择 layer
    image
    image

Layer version ARNLayers 列表中可以找到

(转)网页外链用了 target="_blank",结果悲剧了

今天给大家分享一个 Web 知识点。如果你有过一段时间的 Web 开发经验,可能已经知道了。不过对于刚接触的新手来说,还是有必要了解一下的。

我们知道,网页里的a标签默认在当前窗口跳转链接地址,如果需要在新窗口打开,需要给 a 标签添加一个target="_blank"属性。

<a href="http://kaysonli.com/" target="_blank">1024译站</a>

顺便提下一个有意思的现象,很早之前我就发现,国外网站倾向于在当前页跳转,而国内网站喜欢打开新窗口。不信你们可以去验证下。我不知道这是交互设计上的文化差异,还是技术上的开发习惯。

当然,这两种方式各有优缺点。当前页跳转显得操作比较有连贯性,不会贸然打断用户的注意力,也会减少浏览器的窗口(tab 页)数量。但是对于需要反复回到初始页面的场景来说,就很麻烦了。比如搜索结果页面,通常需要查看对比几个目标地址,保留在多个窗口还是比较方便。

今天要说的不只是用户体验上的差别,而是涉及安全和性能。

安全隐患

如果只是加上target="_blank",打开新窗口后,新页面能通过window.opener获取到来源页面的window对象,即使跨域也一样。虽然跨域的页面对于这个对象的属性访问有所限制,但还是有漏网之鱼。

image

这是某网页打开新窗口的页面控制台输出结果。可以看到window.opener的一些属性,某些属性的访问被拦截,是因为跨域安全策略的限制。

即便如此,还是给一些操作留下可乘之机。比如修改window.opener.location的值,指向另外一个地址。你想想看,刚刚还是在某个网站浏览,随后打开了新窗口,结果这个新窗口神不知鬼不觉地把原来的网页地址改了。这个可以用来做什么?钓鱼啊!等你回到那个钓鱼页面,已经伪装成登录页,你可能就稀里糊涂把账号密码输进去了。

image

还有一种玩法,如果你处于登录状态,有些操作可能只是发送一个GET请求就完事了。通过修改地址,就执行了非你本意的操作,其实就是 CSRF 攻击。

性能问题

除了安全隐患外,还有可能造成性能问题。通过target="_blank"打开的新窗口,跟原来的页面窗口共用一个进程。如果这个新页面执行了一大堆性能不好的 JavaScript 代码,占用了大量系统资源,那你原来的页面也会受到池鱼之殃。

解决方案

尽量不使用target="_blank",如果一定要用,需要加上rel="noopener"或者rel="noreferrer"。这样新窗口的window.openner就是null了,而且会让新窗口运行在独立的进程里,不会拖累原来页面的进程。不过,有些浏览器对性能做了优化,即使不加这个属性,新窗口也会在独立进程打开。不过为了安全考虑,还是加上吧。

我特意用自己的博客网站 http://www.kaysonli.com/ 试了一下,点击里面的外链打开新页面,window.openner都是null。查看页面元素发现,a标签都加上了 rel="noreferrer"。博客是用 Hexo 生成的,看来这种设置已经成了基本常识了。

另外,对于通过window.open的方式打开的新页面,可以这样做:

var yourWindow = window.open();
yourWindow.opener = null;
yourWindow.location = "http://someurl.here";
yourWindow.target = "_blank";

文章来源

https://mp.weixin.qq.com/s/g1N8xg4sXiCkQ67Mw0cUhw

设置本地IP 的域名(Mac)

1. sudo vim /etc/hosts
// 增加如下选项
127.0.0.1       dev.bees360.com
0.0.0.0         dev.bees360.com

// 刷新系统缓存
2. sudo dscacheutil -flushcache

// 3. 刷新浏览器缓存
// 4. 如果翻墙,要改为PAC 自动模式

Excel Date 转换成 JavaScript Date

前置知识:

  1. Excel 时间是从1900.01.01 开始算的
  2. Excel 的单元格格式如果是日期格式的话,它的值等于填入值与 1900.01.01 相差的天数。
  3. 在 Excel 中 02/29/1900 是存在的,但是在 JavaScript 中不存在,所以转换时,excelDate 要减一
  4. JavaScript 的时间从 UTC 1970.01.01 开始算的

实现

由于 Excel 时间和 JavaScript 开始时间不一致,可以统一以Excel 中1900为起点,再加上 excelDate - 1

new Date(1900, 0, excelDate-1)

Mac Install aws cli

  1. 更新 pip3
$ curl -O https://bootstrap.pypa.io/get-pip.py
$ python3 get-pip.py --user
  1. 运用 pip3 安装
$ pip3 install awscli --upgrade --user
  1. 更新环境变量
$ vim ~/.bash_profile

// 添加内容
export PATH=/Users/username/Library/Python/3.7/bin:$PATH

$ source ~/.bash_profile
  1. 检测是否安装成功
$ aws --version
  1. 配置证书
$ aws configure
  1. 配置证书位置
~/.aws

参考:

install-macos

关于获取 Canvas 事件位置的坑

常发生的场景

选取物体或拖拽物体时,无法选中

例子

<canvas id="cvs" width="400" style="width: 200px"></canvas>
const canvas = document.getElementById('cvs');
const { width, clientWidth, height, clientHeight } = canvas;

当 Canvas 上的 width 和 clientWidth 不等,或者高度层面不等时,事件的位置要做对应的转化,才能得到真实的值。

const scale = width / clientWidth;
getMousePosition(evt) {
  const rect = canvas.getBoundingClientRect();
  const clientX = evt.clientX;
  const clientY = evt.clientY;

  return {
    x: (clientX - rect.left) * scale,
    y: (clientY - rect.top) * scale
  };
},

接口自动化测试

使用技术栈:jest + supertest

环境: node

Jest

  1. 预处理与后处理
beforeAll(() => console.log('1 - beforeAll'));
afterAll(() => console.log('1 - afterAll'));
beforeEach(() => console.log('1 - beforeEach'));
afterEach(() => console.log('1 - afterEach'));
test('', () => console.log('1 - test'));
describe('Scoped / Nested block', () => {
  beforeAll(() => console.log('2 - beforeAll'));
  afterAll(() => console.log('2 - afterAll'));
  beforeEach(() => console.log('2 - beforeEach'));
  afterEach(() => console.log('2 - afterEach'));
  test('', () => console.log('2 - test'));
});

// 1 - beforeAll
// 1 - beforeEach
// 1 - test
// 1 - afterEach
// 2 - beforeAll
// 1 - beforeEach
// 2 - beforeEach
// 2 - test
// 2 - afterEach
// 1 - afterEach
// 2 - afterAll
// 1 - afterAll
  1. 验证
// Truthiness
toBeNull matches only null
toBeUndefined matches only undefined
toBeDefined is the opposite of toBeUndefined
toBeTruthy matches anything that an if statement treats as true
toBeFalsy matches anything that an if statement treats as false

// Number
test('two plus two', () => {
  const value = 2 + 2;
  expect(value).toBeGreaterThan(3);
  expect(value).toBeGreaterThanOrEqual(3.5);
  expect(value).toBeLessThan(5);
  expect(value).toBeLessThanOrEqual(4.5);

  // toBe and toEqual are equivalent for numbers
  expect(value).toBe(4);
  expect(value).toEqual(4);
});

// String
test('there is no I in team', () => {
  expect('team').not.toMatch(/I/);
});

test('but there is a "stop" in Christoph', () => {
  expect('Christoph').toMatch(/stop/);
});

// Arrays and iterables
const shoppingList = [
  'diapers',
  'kleenex',
  'trash bags',
  'paper towels',
  'beer',
];

test('the shopping list has beer on it', () => {
  expect(shoppingList).toContain('beer');
  expect(new Set(shoppingList)).toContain('beer');
});

Supertest

初始化

const supertest = require('supertest');
const request = supertest(BaseURL);

请求

let auth = ['Authorization', 'Token']
request.post('/oauth/token')
      .set(...auth)
      .set('Accept', 'application/json') // 设置头部
      .send('password=' + Password) // form data
      .send('username=' + env.Username)
      .send('scope=' + env.Scope)
      .send('grant_type=password');

request.post('/project')
      .set(...auth)
      .set('Accept', 'application/json')
      .send(data); // json data

request.get(`/project/${projectId}`)
      .set(...auth)
      .timeout(10*60*1000)
      .set('Accept', 'application/json');

npm 发包简单流程

npm 发包简单流程

  1. 官网 注册账号,并验证邮箱

  2. 在终端登录

$ npm login
  1. 初始化工程
$ npm init package-name -y
  1. 补充完整 package.json 文件
必须带有的字段

name :包名(全部小写,没有空格,可以使用下划线或者横线)
version: 版本

其他内容

author:作者信息
main:程序入口文件,一般都是 index.js
description:描述信息,有助于搜索
keywords:[] 关键字,有助于在人们使用 npm search 搜索时发现你的项目
scripts:支持的脚本,默认是一个空的 test
license:默认是 MIT
bugs:当前项目的一些错误信息,如果有的话
dependencies:在生产环境中需要用到的依赖
devDependencies:在开发、测试环境中用到的依赖
repository:代码仓库
  1. 完成工程开发

  2. 发布工程

$ npm publish

Gitlab CI 构建定时任务

参考文章:

https://gitlab.bees360.com/help/user/project/pipelines/schedules

核心点:

1 new schedule

  1. Navigate to the project's CI / CD > Schedules page.
  2. Click the New schedule button.
  3. Fill in the Schedule a new pipeline form.
  4. Click the Save pipeline schedule button.

2 在.gitlab-ci.yml 文件添加

job:on-schedule:
  only:
    - schedules
  script:
    - make world

job:
  except:
    - schedules
  script:
    - make build

不想跑定时任务的job,使用 except schedules

Firebase 发布

发布前端

  1. 如果使用翻墙代理,需要设置 http_proxyhttps_proxy 环境变量。端口要看翻墙软件的配置。Mac 系统 shadowsocks 一般是 1087, windows 系统下一般是 1080
export http_proxy=http://127.0.0.1:1087;export https_proxy=http://127.0.0.1:1087;export NO_PROXY=localhost,127.0.0.1
  1. 使用 firebase login:ci 获取 token
  2. 使用 firebase deploy --only hosting --project ${PROJECT_NAME} --token ${TOKEN}

返回不同国家地区指定格式的时间

new Date(timestamp).toLocaleString('en-US', {
 year:"numeric",
 month:"2-digit",
 day:"2-digit",
 hour:"2-digit",
 minute:"2-digit",
timeZoneName: "short"
})
// 01/08/2020, 04:55 PM GMT+8

详细见 MDN

发布订阅者模式实现 LazyMan

// 发布订阅者模式
const Event = {
  taskList: [],

  subscribe: function () {
    const params = {};
    const args = Array.prototype.slice.call(arguments);
    params.msg = args[0];
    params.args = args.slice(1);
    this.taskList.push(params)
  },
  publish: function () {
    if (this.taskList.length) {
      const { msg, args } = this.taskList.shift();
      Task.run(msg, args);
    }
  }
}

// 执行完一个任务后,再发布,保证任务队列顺序执行。
const Task = {
  run: function (msg, args) {
    this[msg].apply(this, args);
  },
  lazyMan: function () {
    console.log(`lazyMan start`)
    Event.publish();
  },
  eat: function (str) {
    console.log(`Eat: ${str}`)
    Event.publish();
  },
  play: function (str) {
    console.log(`Play: ${str}`)
    Event.publish();
  },
  sleep: function (time) {
    setTimeout(function () {
      console.log(`Sleep: ${time}s`)
      Event.publish();
    }, time * 1000)
  },
}

// 返回 对象本身,保证链式调用
const _LazyMan = function () { };

_LazyMan.prototype.eat = function (str) {
  Event.subscribe('eat', str);
  return this;
}
_LazyMan.prototype.play = function (str) {
  Event.subscribe('play', str);
  return this;
}

_LazyMan.prototype.sleep = function (time) {
  Event.subscribe('sleep', time);
  return this;
}

// 启动辅助
const LazyMan = function () {
  Event.subscribe('lazyMan');
  // 事件循环:订阅完后,下个宏任务启动发布。
  setTimeout(function () {
    Event.publish();
  })
  return new _LazyMan();
}

const lazyMan = new LazyMan();

lazyMan.eat('apple').play('ball').sleep(2).eat('rice');

Flex 设置默认值的方法

有些场景需要容器具有初始值,比如 element table。如果父容器是flex 布局,要注意设置默认值。设置默认值的方法:

// 宽度初始值
min-width: 0;
// 高度初始值
min-height: 0;

原封不动迁移 git 仓库方法

如果你想从别的 Git 托管服务那里复制一份源代码到新的 Git 托管服务器上的话,可以通过以下步骤来操作。

1). 从原地址克隆一份裸版本库,比如原本托管于 GitHub。

git clone --bare git://github.com/username/project.git

2). 然后到新的 Git 服务器上创建一个新项目,比如 GitCafe。

3). 以镜像推送的方式上传代码到 GitCafe 服务器上。

cd project.git

git push --mirror [email protected]/username/newproject.git

4). 删除本地代码

cd ..

rm -rf project.git

5). 到新服务器 GitCafe 上找到 Clone 地址,直接 Clone 到本地就可以了。

git clone [email protected]/username/newproject.git

这种方式可以保留原版本库中的所有内容。

参考: https://segmentfault.com/q/1010000000124379

《深入浅出 Node.js》 学习笔记

《深入浅出 Node.js》 学习笔记

第 1 章 Node简介

1.4 Node的特点

异步I/O、事件与回调函数、单线程、跨平台

1.5 Node的应用场景

I/O密集型、是否不擅长CPU密集型业务、与遗留系统和平共处、分布式应用

第 2 章 模块机制

2.1 CommonJS 规范

2.1.1 CommonJS 的出发点

弥补当前 JavaScript 没有标准的缺陷,以达到像 Python、Ruby和Java具备开发大型应用的基础能力。
规范涵盖了模块、二进制、Buffer、字符集编码、I/O流、进程环境、文件系统、套接字、单元测试、Web服务器网关接口、包管理等。

2.1.2 CommonJS 的模块规范

模块规范分为:模块引用、模块定义和模块标识三个部分。

  1. 模块引用
var math = require('math');
  1. 模块定义
// math.js
exports.add = function() {
  // something
}
  1. 模块标识
    模块标识其实是传递给 require() 方法的参数,它必须符合小驼峰命名的字符串,或者以.、..开头的相对路径,或者绝对路径。

2.2 Node 的模块实现

Node 引入模块步骤

  1. 路径分析
  2. 文件定位
  3. 编译执行

模块分类

核心模块(Node提供的模块)、文件模块(用户编写的模块)

  • 核心模块部分在Node源代码的编辑过程中,编译进了二进制执行文件。在Node进程启动时,部分核心模块就被直接加载进内存中,所以这部分核心模块引入时,文件定位和编译执行这两个步骤可以省略掉,并且在路径分析中优先判断,所以它的加载速度是最快的。
  • 文件模块则是在运行时动态加载,需要完整的路径分析、文件定位、编译执行过程,速度比核心模块慢。

2.2.1 优先从缓存加载

Node 对引入的模块都会进行缓存,以减少二次引入时的开销。

2.2.2 路径分析和文件定位

  1. 模块标识符分析
    模块分析符有下面几类:
  • 核心模块,如:http、fs、path
  • .或..开始的相对路径文件模块
  • 以/开始的绝对路径文件模块
  • 非路径形式的文件模块,如自定义的connect模块

模块路径:
模块路径是Node在定位文件模块的具体文件时制定的查找策略,具体表现为一个路径组成的数组。如下:

[
  '/Users/macbook/workspace/web_workspace/daily-test/node_modules',
  '/Users/macbook/workspace/web_workspace/node_modules',
  '/Users/macbook/workspace/node_modules',
  '/Users/macbook/node_modules',
  '/Users/node_modules',
  '/node_modules'
]

查找方式如下:以当前文件为起点,首先在当前文件目录下找,找不到就去父级目录下找,不断逐级往上。所以当前文件离node_modules越远时,查找越费时。

  1. 文件定位
  • 文件扩展名分析
    在标识符不包含扩展名的情况下,按照 .js、.json、.node 的次序补足扩展名

  • 目录分析和包
    通过分析文件扩展名之后,可能没有查找到对应文件,但却得到一个目录,此时Node会净目录当做包处理。通过JSON.parse()解析包描述对象,从中取出 main 属性指定的文件名进行定位。如果main指定的文件查找错误,会将index当做默认文件名,依次查找 index.js index.json index.node

2.2.3 模块编译

在Node 中,每个模块都是一个对象,定义如下:

function Module(id, parent) {
  this.id = id;
  this.exports = {};
  this.parent = parent;
  if (parent && parent.children) {
    parent.children.push(this);
  }
  this.filename = null;
  this.loaded = false;
  this.children = [];
}
  1. JavaScript 模块的编译
    require、exports、module、__filename、__dirname 这几个变量都没有在文件模块中定义,但是可以在文件模块中取到。原因是,在编译过程中,Node对获取的JavaScript文件内容进行头尾包装。如下:
(function (exports, require, module, __filename, __dirname) { 
  var math = require('math'); 
  exports.area = function (radius) { 
    return Math.PI * radius * radius; 
  }; 
});

这样每个模块进行作用域隔离,避免污染。

  1. C/C++模块的编译
    调用 process.dlopen()

  2. JSON文件编译
    利用 fs 模块获取内容,调用 JSON.parse() 方法得到对象。

2.3 核心模块

核心模块分为 C/C++编写的和JavaScript编写的两部分,其中C/C++文件存放在Node项目的src目录下,JavaScript文件存在lib目录下。

2.3.1 JavaScript核心模块的编译过程

  1. 转存为C/C++代码
  2. 编译JavaScript核心模块

2.3.2 C/C++核心模块的编译过程

由纯C/C++编写的部分统一称为内建模块

文件模块可能依赖核心模块,核心模块(JavaScript)可能依赖内建模块

2.4 C/C++ 扩展模块

2.5 模块调用栈

文件模块、核心模块、内建模块、C/C++扩展模块

JavaScript核心模块主要职责:1. 作为C/C++内建模块的封装层和桥接层,供文件模块调用;2. 纯粹的功能模块,不需要跟底层打交道,但是十分重要。

文件模块通常由第三方编写,包括普通JavaScript模块和C/C++扩展模块。

2.6 包和 NPM

CommonJS的包规范定义,由包结构和包描述文件两个部分组成。

2.6.1 包结构

包实际上是一个存档文件,即一个目录直接打包为.zip或tar.gz格式的文件,安装后解压为还原为目录。完全符合CommonJS规范的包目录应该包含以下文件:

  • package.json: 包描述文件
  • bin: 存放可执行二进制文件目录
  • lib: 用于存在JavaScript代码的目录
  • doc: 文档
  • test: 测试

2.6.2 包描述文件与 NPM

包描述文件:JSON 格式的文件 - package.json

字段
name, description, version, keywords, maintainers, contributors, bugs, licenses,
repositories, dependencies, homepage, os, cpu, engine, bultin, directories, implements, scripts.

NPM 基于包规范实现,并且多了 author, bin, main, devDependencies 4个字段。

2.6.3 NPM常用功能

  • 安装依赖包:npm i
  • npm钩子命令: scripts 中配置
  • 发布包
  • 分析包: npm ls

2.6.4 局域网NPM

第三是工具 Nexus

2.7 前后端共用模块

规范: CommonJS、AMD、CMD

兼容多种模块规范

;(function (name, definition) { 
  // 检测上下文环境是否为AMDईCMD
  var hasDefine = typeof define === 'function', 
    // 检查上下文环境是否为Node
    hasExports = typeof module !== 'undefined' && module.exports; 
  if (hasDefine) { 
    // AMD环境或CMD环境
    define(definition); 
  } else if (hasExports) { 
    // 定义为普通Node模块
    module.exports = definition(); 
  } else { 
    // 将模块的执行结果挂在window变量中,在浏览器中this指向window对象
    this[name] = definition(); 
  } 
})('hello', function () { 
  var hello = function () {}; 
  return hello; 
});

第 3 章 异步I/O

3.1 为什么要异步I/O

3.1.1 用户体验

3.1.2 资源分配

单线程同步编程模型会因阻塞I/O导致硬件资源得不到更优的使用。多线程编程模型也因为编程中的死锁、状态同步等问题让开发头疼。
Node处理方案:利用单线程,远离多线程死锁、状态同步问题;利用异步I/O,让单线程远离阻塞,以便更好使用CPU。

3.2 异步I/O实现现状

  1. 异步I/O与非阻塞I/O
  2. 理想的非阻塞异步I/O
  3. 现实的异步I/O

3.3 Node的异步I/O

3.3.1 事件循环

在进程启动时,Node便会创建一个类似于while(true)的循环,每执行一次循环体的过程我们称为Tick
每个Tick的过程就是查看是否有事件待处理,如果有,就取出事件及其相关的回调函数。如果存在关联的回调函数,就执行它们。
(见图3-11)

3.3.2 观察者

每个事件循环中有一个或者多个观察者,而判断是否有事件要处理的过程就是向这些观察者询问是否又要处理的事件。

3.3.3 请求对象

从JavaScript发起调用到内核执行完I/O操作的过渡过程中,存在一种中间产物,它叫做请求对象。所有的状态都保存在这个对象中,包括送入线程池等待执行以及I/O操作完毕后的回调处理。

从JavaScript调用Node的核心模块,核心模块调用C++内建模块,内建模块通过libuv进行系统调用,这是Node里经典的调用方式。

3.3.4 执行回调

组装好请求对象、送入I/O线程池等待执行,实际上完成了异步I/O的第一部分,回调通知是第二部分。
回调过程中,动用了事件循环的I/O观察者,每次Tick执行中,它会调用IOCP相关的GetQueuedCompletionStatus() 方法检查线程池中是否有执行完的请求,
如果存在,会将请求对象加入到I/O观察者的队列中,然后将其当做事件处理。

见图 3-13

3.3.5 小结

事件循环、观察者、请求对象、I/O线程池这四者共同构成了Node异步I/O模型的基本要素。

在Node中,除了JavaScript是单线程外,Node自身其实是多线程的,只是I/O线程使用的CPU较少。另外一个需要重视的观点是,除了用户代码无法并行执行外,所有I/O(网络I/O、磁盘I/O等)则是可以并行起来的。

3.4 非I/O的异步 API

与I/O无关的API:setTimeout(), setInterval(), setImmediate(), process.nextTick()

3.4.1 定时器

setTimeout() 和 setInterval() 与浏览器的API一致。调用setTimeout() 或者 setInterval() 创建的定时器会被插入到定时器观察者内部的一个红黑树中。
每次Tick执行时,会从该红黑树中迭代取出定时器对象,检查是否超过定时时间,如果超过,就形成一个事件,它的回调函数将立即执行。

见图3-14

3.4.2 process.nextTick()

每次调用process.nextTick()方法,只会将回调函数放入队列中,在下一轮Tick时取出执行。
定时器中采用红黑树的操作时间复杂度为O(lg(n)), nextTick的时间复杂度为O(1)。相比较之下,process.nextTick() 更高效。
另外,采用定时器需要动用红黑树,创建定时器对象和迭代等操作,因此 process.nextTick() 也更轻量。

3.4.3 setImmediate()

setImmediate()方法与process.nextTick()方法十分相似,都是将回调函数延迟执行。但是 process.nextTick() 中的回调函数比 setImmediate() 优先。
在具体实现上,process.nextTick()的回调函数保存在一个数组中,setImmediate()的结果则是保存在链表中。
在行为上,process.nextTick() 在每轮循环中将数组中的回调函数全部执行完,而 setImmediate() 只执行链表中的一个回调函数。如下:

// 加入两个nextTick()的回调函数
process.nextTick(function () { 
 console.log('nextTick延迟执行1'); 
}); 
process.nextTick(function () { 
 console.log('nextTick延迟执行2'); 
}); 
// 加入两个setImmediate()的回调函数
setImmediate(function () { 
 console.log('setImmediate延迟执行1'); 
 // 进入ူْ循环
 process.nextTick(function () { 
 console.log('强势插入'); 
 }); 
}); 
setImmediate(function () { 
 console.log('setImmediate延迟执行2'); 
}); 
console.log('正常执行'); 

执行结果:
正常执行
nextTick延迟执行1
nextTick延迟执行2
setImmediate延迟执行1
强势插入
setImmediate延迟执行2

3.5 事件驱动与高性能服务器

事件驱动的实质:通过主循环加事件触发的方式来运行程序

Node 通过事件驱动的方式处理请求,无须为每一个请求创建额外的对应线程,可以省掉创建线程和销毁线程的开销,同时操作系统在调度任务时因为线程较少,上下文切换的代价很低。这是Node高性能的一个原因。

3.6 总结

事件循环是异步实现的核心,它与浏览器中的执行模型基本保持一致。

4 异步编程

5 内存控制

5.1 V8 的垃圾回收机制与内存控制

1. V8 的内存限制

64位系统约为 1.4GB,32位系统约为0.7GB

2. 查看内存情况

$ node
> process.memoryUsage(); 
{ rss: 14958592,
     heapTotal: 7195904,
     heapUsed: 2821496 }

3. 限制堆大小原因

  1. V8 最初为浏览器设计,用不到这么多内存
  2. V8 垃圾回收机制限制,垃圾回收会引起 JavaScript 线程暂停执行

4. 临时放宽内存限制的方法

node --max-old-space-size=1700 test.js // 单位为MB 
node --max-new-space-size=1024 test.js // 单位为KB

Mac 关于 Apache 的命令

  1. 关闭mac自带apache的启动。
sudo launchctl unload -w /System/Library/LaunchDaemons/org.apache.httpd.plist

如果哪天你想让它开机启动了,则将unload 改为 load:

sudo launchctl load -w /System/Library/LaunchDaemons/org.apache.httpd.plist
  1. 重启apache:sudo /usr/sbin/apachectl restart

  2. 关闭apache:sudo /usr/sbin/apachectl stop

  3. 开启apache:sudo /usr/sbin/apachectl start

通过img 的 `crossorigin` 属性解决 Canvas/XHR 可能的跨域问题

一个关于图片的跨域场景:

如果平台里存在对同一张跨域图片的 <img> 标签请求XHR 请求,且先进行 <img> 标签请求,之后再进行 XHR 请求。浏览器会取 <img> 标签请求过的缓存,但是这个时候不会返回头部信息,既不会存在 Access-Control-Allow-Origin 头部信息。最后则会报跨域错误。

通过设置 <img> 标签的 crossorigin 属性的值为 anonymous,强制图片每次请求都使用 XHR 的 CORS 请求。

参考:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-crossorigin

关于跨域的相关属性:referrerpolicy

关于 Google Map Geocode Address 容易报 OVER_QUERY_LIMIT 问题

需要通过 Web Service (即手动发送 HTTP 请求)来编码,而不能通过 JavaScript SDK 来。这样才可以每秒发送 50 次请求。另外一个注意点,只有绑定信用卡的 API Key 才支持 Web Service。

部分代码:

import Axios from 'axios';

Axios.get('https://maps.googleapis.com/maps/api/geocode/json', {
  params: {
    address,
    key: 'API_KEY'
  }
}).then(res => {
  if (res.data.status === 'OK') {
    console.log(res.data.results[0])
  } else {
    console.log(res.data.status)
  }
}).catch(error => {
  console.log(error)
})

关于浏览器缓存

浏览器缓存

浏览器缓存总共有以下四类,优先级如下:

  1. Memory Cache
  2. Service Worker Cache(离线缓存)
  3. HTTP Cache
  4. Push Cache

image

1. HTTP Cache

1.1 强缓存

主要利用Expires 和 Cache-Control两个字段来控制,如果命中强缓存,直接从缓存中拉取内容,不再与服务器通信。

1.1.1 Expires

用法

使用一个到期时间来表示资源是否到期。设置时,以服务器时间为依据;判断时,以客户端时间为依据。

expires: Wed, 11 Sep 2019 16:12:18 GMT

缺陷

依赖客户端时间:如果服务器时间和客户端不一致,容易误判;用户改动客户端时间时,也会出差错。

1.1.2 Cache-Control

用法

HTTP1.1新增Cache-Control来解决Expires的缺陷或者是替代。通过max-age来设置资源的有效期,避免依赖客户端时间。如下:

cache-control: max-age=31536000

cache-control相对expires更准确,如果同时存在两者,以cache-control为准。

几个相关的字段

  • s-maxage
    只在缓存服务器有效,优先级比max-age高

  • public 和 private
    public:设置资源既可以被缓存服务器缓存,也可以被浏览器缓存
    private: 设置资源只可以被浏览器缓存,private为默认值

  • no-store 和 no-cache
    no-store: 设置资源不被缓存,每次都重新获取
    no-cache: 设置资源绕过浏览器缓存,直接询问服务器资源是否过期。走协商缓存路线。

1.2 协商缓存:浏览器与服务器合作之下的缓存策略

浏览器向服务器询问缓存的相关信息,进而判断是重新发起请求获取资源,还是从本地获取缓存资源。

如果服务器提示资源未改动(Not Modified),资源重定向到浏览器缓存,这时响应状态码是304。如下:

Request Mehtod: GET
Status Code: 304 Not Modified

协商缓存的实现: 从Last Modified 到 Etag

1.2.1 Last Modified

用法
Last-Modified是一个时间戳,如果启用协商缓存,它会在首次请求时随着Response Headers返回:

Last-Modified: Fri, 27 Ocy 2017 06:35:57 GMT

随后每次请求,会带上If-Modified-Since的时间戳字段,它的值正是上次response返回给它的
last-modified值:

If-Modified-Since: Fri, 27 Ocy 2017 06:35:57 GMT

服务器接收到这个时间戳后,会对比该时间戳和资源在服务器上的最后的修改时间是否一致,从而判断资源是否变化。

变化: 返回完整响应内容,并在Response Headers中添加新的Last-Modified值。

没有变化:返回304响应,如下:

Status Code: 304 Not Modified

Last-Modified缺陷
我们编辑文件,内容没改,但是修改时间改了,服务器会误判,重新返回新的内容
修改文件速度很快,在1秒内完成(比如100ms),由于If-Modified-Since最小计量单位是秒,这时服务器也会误判,需要返回新的内容,但却没有返回。

1.2.2 Etag

Etag为解决Last-Modified的缺陷而出现。作为Last-Modified的补充,优先级比Last-Modified高。

Etag是由服务器为每一个资源生成的唯一标识字符串,这个标识字符串基于文件内容编码。不同的文件,对应的Etag不同,所以能精准感知文件的变化。

用法

和Last-Modified使用类似。首次请求时,响应头有一个最初的标识字符串,如下:

ETag: W/"2a3b-1602480f459"

下次请求时,请求头有If-None-Match的字符串供服务器比较,如下:

If-None-Match: W/"2a3b-1602480f459"

缺陷

Etag的生成需要服务器额外的开销,影响服务端性能。视情况使用。

1.1.3 HTTP Cache使用总结

当我们的资源内容不可复用时,直接为 Cache-Control 设置 no-store,拒绝一切形式的缓存;否则考虑是否每次都需要向服务器进行缓存有效确认,如果需要,那么设 Cache-Control 的值为 no-cache;否则考虑该资源是否可以被代理服务器缓存,根据其结果决定是设置为 private 还是 public;然后考虑该资源的过期时间,设置对应的 max-age 和 s-maxage 值;最后,配置协商缓存需要用到的 Etag、Last-Modified 等参数。

2. Memory Cache

内存缓存:优先级最高,最快,生命周期比较短。

使用规则: 考虑内存大小,一些Base64 图片、小体积的css,js会被缓存在内存中。

Service Worker Cache

Service Worker 是一种独立于主线程之外的 Javascript 线程。它脱离于浏览器窗体,因此无法直接访问 DOM。这样独立的个性使得 Service Worker 的“个人行为”无法干扰页面的性能,这个“幕后工作者”可以帮我们实现离线缓存、消息推送和网络代理等功能。我们借助 Service worker 实现的离线缓存就称为 Service Worker Cache。

Service Worker 的生命周期包括 install、active、working 三个阶段。一旦 Service Worker 被 install,它将始终存在,只会在 active 与 working 之间切换,除非我们主动终止它。这是它可以用来实现离线存储的重要先决条件。

大家注意 Server Worker 对协议是有要求的,必须以 https 协议为前提。

Push Cache

Push Cache 是指 HTTP2 在 server push 阶段存在的缓存。这块的知识比较新,应用也还处于萌芽阶段,我找了好几个网站也没找到一个合适的案例来给大家做具体的介绍。但应用范围有限不代表不重要——HTTP2 是趋势、是未来。在它还未被推而广之的此时此刻,我仍希望大家能对 Push Cache 的关键特性有所了解:

Push Cache 是缓存的最后一道防线。浏览器只有在 Memory Cache、HTTP Cache 和 Service Worker Cache 均未命中的情况下才会去询问 Push Cache。

Push Cache 是一种存在于会话阶段的缓存,当 session 终止时,缓存也随之释放。

不同的页面只要共享了同一个 HTTP2 连接,那么它们就可以共享同一个 Push Cache。

更多的特性和应用,可以在日后的开发过程中去挖掘和实践。

浏览器缓存性能优化总结

缓存作用更多是减少网络请求,适当应用以上四种请求,可以极大提升我们的性能。

解决 `npm i node-sass` 慢或容易失败的问题

虽然已经将node-sass 缓存到内部组件,但是安装 node-sass 时,还会源码执行下载操作,原因是要根据平台(Windows,Linux,Mac...)来不同的文件。下载的文件是在GitHub上的,没有翻墙,容易下载失败或者慢。

比如下面的报错:

npm install

> [email protected] install /builds/root/upstream/bees360ai/frontend/node_modules/node-sass
> node scripts/install.js

Downloading binary from https://github.com/sass/node-sass/releases/download/v4.12.0/linux-x64-64_binding.node
Cannot download "https://github.com/sass/node-sass/releases/download/v4.12.0/linux-x64-64_binding.node": 

ESOCKETTIMEDOUT

Hint: If github.com is not accessible in your location
      try setting a proxy via HTTP_PROXY, e.g. 

      export HTTP_PROXY=http://example.com:1234

or configure npm proxy via

      npm config set proxy http://example.com:8080

> [email protected] postinstall /builds/root/upstream/bees360ai/frontend/node_modules/core-js
> node -e "try{require('./postinstall')}catch(e){}"


> [email protected] postinstall /builds/root/upstream/bees360ai/frontend/node_modules/ejs
> node ./postinstall.js

这时可以下载的源头来自淘宝。如下操作:

方法一:
在 .npmrc 文件中设置:

// .npmrc
sass_binary_site=https://npm.taobao.org/mirrors/node-sass/

方法二:

npm config set sass_binary_site=https://npm.taobao.org/mirrors/node-sass/

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.