Code Monkey home page Code Monkey logo

accumulation's Introduction

Hi here, I'm Zack Zhong 👋

Hi, I'm Zack Zhong, a passionate self-taught frontEnd web developer from China.

  • 🔭 I’m currently working on ...
  • 🌱 I’m currently learning Typescript
  • 📷 I like photography
  • 💬 Ask me about anything here

itboos's GitHub stats

Top Langs

accumulation's People

Contributors

itboos avatar

Stargazers

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

Watchers

 avatar

accumulation's Issues

再谈 js 事件循环(Event-loop)

再谈事件循环

事件循环这个东西, 在 JavaScript 算是一个基础知识但也算是难点了。网络上关于事件循环的文章也是层出不穷,一是事件循环确实在分析代码执行顺序的时候会用到,二则是越来越多的公司在面试的时候会出此类型的题目来考查候选人JavaScript 基础内功了。

所以,不管是出于面试的角度还是提高自己js内功来看,掌握事件循环都是收益挺高的一件事情。事件循环,我在刚毕业的时候研究过一段时间,当时没写成文章。所以,一段时间之后,就又忘记了。然后又去看,然后又忘记,然后陷入了一个死循环。更重要的是,每次看不同的文章,都挺费时间的。于是,想着把自己的理解写成文章,一则可能帮助一些同学理解事件循环,二则加深自己的印象,当忘记的时候回顾可以快速想起。

好了,废话说了这么多,下面进入正题。

首先,澄清一个概念,本文讲的事件循环是指 窗口事件循环(window event loop), 因为 事件循环在挺多语言中都有,不同的语言,执行顺序也不太一样。

首先,我会大致介绍一下事件循环的规范, 然后结合具体的列子来分析。

  • 定义

    定义: 为了协调事件,用户交互,脚本,渲染,联网等,用户代理必须使用本节中描述的事件循环。每个代理都有一个关联的事件循环,该循环对于该代理是唯一的。

    同源窗口代理(similar-origin-window-agent)的事件循环被称为是 window event loop,也就是本文中讨论的事件循环。

  • 几个重要的术语

  • 任务(task): 一个 任务是具有一下内容的 结构

    1. steps: 一系列步骤指定任务要完成的工作(可以任务时function 里面的代码)
    2. 一个源: 将每个任务定义为来自特定任务来源
    3. 一个文件
    4. 一个脚本执行环境设置对象集
  • 可执行任务
    如果任务 的文档为null或完全活动,则该任务是可运行的。(为简单起见,我们可以任务 setTimeout 的时间到了,此回调的任务就可以认为是可以执行的任务了)

  • 宏任务 (task/macrotask)
    宏任务大致分为以下:

    • script(整体代码)
    • setTimeout, setInterval, setImmediate,I/O
    • UI rendering
    • ajax 请求不属于宏任务,js 线程遇到 ajax 请求,会将请求交给对应的 http 线程处理,一旦请求返回结果,就会将对应的回调放入宏任务队列,等请求完成执行。
  • 微任务(microtasks ) 也称为 jobs,但 whatwg规范里没有 出现 jobs 这个说法
    微任务大概有下面这些:

    • process.nextTick
    • Promise
    • Object.observe(已废弃)
    • MutationObserver(html5新特性)
  • 栗子

  • 事件循环处理模型

    一个事件循环只要存在,就会持续的运行下面的步骤:省略了一些暂时不需要关注的点

    1. 假设taskQueue 是事件循环的任务队列之一,以实现定义的方式进行选择,并约束所选任务队列必须包含至少一个可运行 任务。如果没有此类任务队列,请跳至下面的微任务步骤。(跳至7)
    2. oldestTask是在任务队列的第一个可以运行的任务, 并把它从 TASKQUEUE 中删除。
    3. 将事件循环的当前正在运行的任务设置为 oldestTask
    4. taskStartTime为当前的高分辨率时间。(不关注)
    5. 执行 oldestTask的步骤。
    6. 将事件循环的当前正在运行的任务设置回 null
    7. 微任务: 执行微任务检查点(重点关注,看下面的微任务检查点逻辑)
    8. hasARenderingOpportunityfalse(不关注)
    9. 我们现在是在当前的高分辨率时间。HRT(不关注)
    10. 通过执行以下步骤来报告任务的持续时间(不关注)
    11. 更新渲染 (不关注)
    12. 如果满足以下所有条件 **** (不关注)
    13. 如果是 WebWorker 事件循环 , 则...(不关注)
  • 微任务检查点

    当用户代理要执行微任务检查点时:

    1. 如果事件循环的执行微任务检查点为true,则返回。
    2. 将事件循环的执行微任务检查点设置为true
    3. 当事件循环的微任务队列不为空时:
      1. oldestMicrotask 是 从事件循环的微任务队列中出队的结果, 就是出队操作
      2. 将事件循环的当前正在运行的任务设置为 oldestMicrotask
      3. 运行 oldestMicrotask
        Note:
        这可能涉及调用脚本回调,该回调最终在运行脚本步骤后调用 清理,调用此脚本再次执行微任务检查点算法,
        这就是为什么我们使用执行微任务检查点标志来避免重入。
      4. 将事件循环的当前正在运行的任务设置回 null
    4. 遍历负责的事件循环是此次事件循环的环境设置对象, 把环境设置对象通知给 rejected promises.
    5. 清理索引数据库事务。
    6. 将事件循环的执行微任务检查点设置为 false
  • 几个重要的概念

  1. 每一个事件循环 有一个当前运行的任务,可以是一个 task 或者是 null. 起初, 它是 null.
    这是用来处理可重入性。
  2. 每一个事件循环 有一个微任务队列,这个一个队列的微任务, 初始为空。 一个 microtask 是指一个的口语化方式
    的任务,其通过 队列 microtask算法 创建。
  3. 每一个事件循环都有一个执行微任务检查点的布尔值,该布尔值最初为 false.它
    是用来防止执行微任务检查点算法的重入调用。
  4. 一个事件循环有一个或多个宏任务队列。一个任务队列是一组的任务。
    note:
    (宏任务队列) 是 set 而不是 queues, 因为事件循环处理模型的第一步是从选定的队列里
    抓取第一个可以执行的任务, 而不是使第一个任务出队。即 宏任务队列不是标准的 queue, 而是 一系列 task 的集合。
  5. 微任务队列是标准的 queue, 执行任务时,就是先取队头的任务进行执行,新的任务放在队尾。
  • 太长不想看了(TL;DR)

    如果觉得上面的规范和概念太长了, 不想看了或者没时间看的话。可以事件循环可以总结下面的几个成简短的步骤。

  1. 执行 script 里的代码,(这里可以看做是一个宏任务) 遇到宏任务就添加到宏任务队列里,遇到微任务就放到微任务队列里。
  2. 执行微任务检查点
    1. 如果事件循环的执行微任务检查点为true,则跳到 3
    2. 如果将事件循环的执行微任务检查点设置为true
    3. 当事件循环的微任务队列不为空时, 取出队列里队头的任务
    4. 执行此微任务,(遇到宏任务就添加到宏任务队列里,遇到微任务就放到微任务队列里。)PS: 这里推测,只有一个微任务队列。虽然规范里没有明说,但根据后面的 demo 演示推测出来的。
      5 重复 2.3, 直到微任务队列为空。
  3. 选取一个宏任务队列,选一个最老的可以执行的宏任务
  4. 执行宏任务:(遇到宏任务就添加到宏任务队列里,遇到微任务就放到微任务队列里。)
  5. 后续一些其它操作
  6. 跳到 2

事件循环的处理流程的流程图

可以结合流程图看上面的文字描述,当然,有时间的话,还是建议去看下官方文档,链接贴在本文下方。

  • demo 分析

    demo1:

console.log('begin)
setTimeout(function set1(){
  console.log('s1');
},100);

setTimeout(function set2(){
  console.log('s2');
  Promise.resolve(0).then(function p4() {
    console.log('p4')
  })
},0);

new Promise(function p1(resolve){
  console.log('p1');
  resolve();
  console.log('p1-1');
}).then(function p2(){
  console.log('p2');
});

console.log(6);
Promise.resolve().then(function p3() {
  console.log('p7')
})
console.log('end')
// 输出:p1, p1-1, 6, p2, p7, s1, s2

分析:脚本开始执行:
我们假定 当前有一个 宏任务 Set, macroSet: {}
假定此是的微任务队列为 microTaskQueue: []

  1. 输出 begin
  2. 遇到 宏任务(setTimeout), 将 set1 回调放到 宏任务队列里, 此时, macroSet = { set1 }
  3. 遇到 宏任务(setTimeout), 将 set2 回调放到 宏任务队列里, 此时, macroSet = { set1, set2 }
  4. 执行 new Promise, 由于 Promise 构造函数执行会立即调用 executor 函数,所以,会输出 p1, p1-1。同时, executor 执行了 resolve, 所以, 此 promise settled 了,将 resolveCb 放入到 微任务队列里, 此时 microTaskQueue = [p2]
  5. 输出 6
  6. 执行 Promise.resolve, 根据 Promise.resolve(v) 的定义,是创建一个 fulfilledpromise 对象,其值为 v。 然后将回调 函数 p3 放到微任务队列里, 此时 microTaskQueue = [p2, p3]

此时任务队列和执行栈大致如下: PS(忽略执行栈里的 setTimeout1, 偷了个懒,拿了一个之前的老图)

  1. 输出 end
  2. 此时,(第一次是 这个 script) 这个宏任务 执行完了,这个时候会执行我们上面说的执行微任务检查点了。
  3. 此时,事件循环的执行微任务检查点 不为 true, 将检查点设置为 true, 并且此时微任务队列不为空。
  4. 取出队头任务 p2, 执行任务 p2, 输出 p2 此时 microTaskQueue = [p3]
  5. 继续取出队头任务 p3, 执行任务 p3, 输出 p7 此时 microTaskQueue = []
  6. 此时,微任务队列为空,执行事件循环处理模型的第 8-13 步。

----------------- 第二轮循环 分割线 -----------------

  1. 执行事件循环处理模型的的第一步 (此时,选取 了我们的宏任务队列 macroSet )
  2. 选取一个最老可以执行的宏任务, 这里是 set2, 因为 set1 还不能执行。
  3. 执行宏任务 set2 , 输出 s2, 遇到微任务 p4, 把 p4 添加到位任务队列, 此时 microTaskQueue = [p4]
  4. 这个宏任务 执行完了,这个时候会执行我们上面说的执行微任务检查点
  5. 此时,事件循环的执行微任务检查点 不为 true, 将检查点设置为 true, 并且此时微任务队列不为空
  6. 取出队头任务 p4, 执行任务 p4, 输出 p4
  7. 微任务队列为空,执行事件循环处理模型的第 8-13 步。

----------------- 第三轮循环 分割线 -----------------

  1. 执行事件循环处理模型的的第一步 (此时,选取 了我们的宏任务队列 macroSet )
  2. 选取一个最老可以执行的宏任务, 这时是 set1
  3. 执行宏任务 set1 , 输出 s1
  4. ..... 重复事件循环的其它步骤, 到这里我们写的代码算是执行完了。

总结输出

begin
p1
p1-1
6
end
p2
p7
s2
p4
s1

总结: 从这里,我们可以猜测,一个事件循环里只有一个微任务队列(因为规范里没有说明只有一个,目前根据输出的结果猜测)

奇奇怪怪的 demo2

new Promise((resolve, reject) => {
  resolve();
})
  .then(() => {
    console.log('outer tick0');
    new Promise((resolve, reject) => {
      resolve();
    })
      .then(() => {
        console.log('inner tick0');
        // Promise.resolve()
        return Promise.resolve();
      })
      .then(() => {
        console.log('inner tick1');
      })
  })
  .then(() => {
    console.log('outer tick1');
  })
  .then(() => {
    console.log('outer tick2');
  })
  .then(() => {
    console.log('outer tick3');
  })
  .then(() => {
    console.log('outer tick4');
  })
// 猜猜上面的结果是啥。

更复杂一些的情况是有 async await , 和 Promise.resolve 的情况。由于本文是关于事件循环的, 所以先不展开讲了。具体的打算再写一篇关于 Promise 的原理,执行顺序,各种调用的文章。

whatwg-event-loop规范

promises-spec 规范

Node.js

一个简单的简书爬虫

主要技术栈:

  1. cheerio https://github.com/cheeriojs/cheerio 页面数据解析
  2. superagent http://visionmedia.github.io/superagent/ 页面数据下载
superagent是nodejs里一个非常方便的客户端请求代码模块,superagent是一个轻量级的
,渐进式的ajax API,可读性好,学习曲线低,内部依赖nodejs原生的请求API,适用于nodejs环境下。
cheerio :
 cheerio 为服务器特别定制的,快速、灵活、实施的jQuery核心实现, 大部分语法和jquery相同

目标

趴下简书首页前20个文章列表的信息
我们看到简书的首页的每篇文章像下面那样

  // 我们要数据的数据结构可以定义成下面那样👇
 var dataStruct = {
    title: '文章标题',
    author: '作者',
    avatar: '', // 头像
    time: '昨天',
    summery: '文章概览........', 

    seeCouts: 100,
    loves: 999,
    commits: 18,
    money: 2,
    href: '文章地址',
    commitHref: '评论地址',
    photo: '封面',

  };
});
      

项目初始化

  npm init -y 
 //   安装依赖:
 npm install superagent cheerio --S
 const superagent = require('superagent');
 const cheerio = require('cheerio');
 const fs = require('fs');
  
 const getUrl = 'https://www.jianshu.com/';
 const homeUrl = 'https://www.jianshu.com/';
 var articlesArr = [];
 
superagent.get(getUrl, (err, res)=> {
  if (err) {
    throw Error(err);
    return;
  }
  // 等待code
  let $ = cheerio.load(res.text);
  let ul = $('#list-container .note-list').children();
  ul.each((index, $ele) => {
    // 这里$ele就是为Dom元素,需要包装成cheerio对象后,才能使用它的方法.
    articlesArr.push(
      parseArticle($($ele))
    );
  });

  
  // 写入数据到本地的json文件
  fs.writeFileSync(__dirname + '/data/aiticle_list.json', 
    JSON.stringify({
      status: 0,
      data: articlesArr,
    }), { encoding: 'utf8'}
  );


/**
 * 根据某个cherrio 节点, query出我们想要的数据
 * @param {cherrio ele}  
 */
function parseArticle($ele) {
  // replace(/\n/g, '').replace(/\s/g, '') 目的是去掉换行和空格
  const article = {
    title:  $ele.find('.title').text().replace(/\n/g, '').replace(/\s/g, ''),
    author: $ele.find('.nickname').text().replace(/\n/g, '').replace(/\s/g, ''),
    home:  `${ homeUrl }${$ele.find('.avatar').attr('href')}`,
    avatar: `http://${$ele.find('.avatar > img').attr('src')}`, // 头像
    time: $ele.find('.info .time').attr('data-shared-at'),
    summery: $ele.find('.abstract').text().replace(/\n/g, '').replace(/\s/g, ''), 

    seeCouts: $ele.find('.meta a:first-child').text().replace(/\n/g, '').replace(/\s/g, ''),
    loves: $ele.find('.meta span').eq(0).text().replace(/\n/g, '').replace(/\s/g, ''),
    commits: $ele.find('.meta a').eq(1).text().replace(/\n/g, '').replace(/\s/g, ''),
    money: $ele.find('.meta span:last-child') && $ele.find('.meta span:last-child').text().replace(/\n/g, '').replace(/\s/g, '') || 0,
    aiticleHref: `${ homeUrl }${$ele.find('.meta a:first-child').attr('href')}`,
    commitHref: `${ homeUrl }${$ele.find('.meta a').eq(1).attr('href')}`,
    photo: `http:${$ele.find('.img-blur').attr('src')} || '' `,
  };
  return article;
}

写一个获取数据的接口

 index.js:
 // 安装express npm i express -S
 // 引入依赖
var express = require('express');

// 建立 express 实例
var app = express();
// 引入数据,前端就可以调用接口获取数据了
var list = require('./data/aiticle_list.json');

app.get('/list', function (req, res) {
  // 从 req.query 中取出我们的 q 参数。
  // 如果是 post 传来的 body 数据,则是在 req.body 里面,不过 express 默认不处理 body 中的信息,需要引入 https://github.com/expressjs/body-parser 这个中间件才会处理,这个后面会讲到。
  var q = req.query.q;
  res.send(JSON.stringify(list));
});

app.listen(3000, function (req, res) {
  console.log('app is running at port 3000');
});

浏览器获取数据:
localshot/list 就能拿到数据了
浏览器请求的展示如下:

这里每次请求到的都是一样的数据,感觉可以写个定时器,如每隔10分钟去爬下数据,然后保存到文件中,
等接口调的时候就能获取到给你更新的数据了

参考链接:
nodejs网络爬虫技术介绍: https://cnodejs.org/topic/56b807c626d02fc6626bb4ec
Node.js 编写爬虫的基本思路及抓取百度图片的实享:https://juejin.im/entry/56e7ad03f3609a0054398f1f

An NPM installer for PhantomJS, headless webkit with JS API:
https://github.com/Medium/phantomjs

微信小程序数据统计和错误统计的实现

某些情况下我们需要对小程序某些用户的行为进行数据进行统计,比如统计某个页面的UV,
PV等,统计某个功能的使用情况等。好让产品对于产品的整个功能有所了解。
在网页里,我们很多人都用过谷歌统计,小程序里也有一些第三方数据统计的库, 比如腾讯的MTA等等。
但是,第三方的数据统计库要么功能太简单,满足不了需求,要么就是要收费。(留下了贫穷的泪水。)
等等,又不是你出钱,怕啥? 贵一点就贵一点呀。

嗯,说的没错。但是,公司团队内部想实现一套完整的自己的数据统计系统以满足自己的需求。所以,还是没有用第三方的。

所以,具体要统计些啥?

产品经理

  • 想知道用户都是怎么进入我们的小程序的?
  • 用户在我们小程序里那个页面停留的时间最长?平均用户停留时间是多少?
  • 想知道我们最近开发的那个功能用的人多不多?
  • 想统计小程序里的一些按钮有多少用户点击了

开发自己

  • 总是很难复现用户端出现的bug,
  • 要是可以知道用户端发生错误时,知道用户当时的用的手机型号,微信版本,网络环境,页面参数,和错误信息就好了
  • 想知道我们小程序启动时间是多少?
  • 接口在用户端的平均响应时间是多少ms? 哪些接口报错了

针对产品经理的需求,我们可以知道,Ta想要的是就是数据统计要实现的功能。对于开发来说,我们关注的更多就是错误统小程序性能这块的东西。

好,到这里,我们需求是明白了。就是要实现一套既能统计普通的埋点数据,也要能统计到小程序里一些特殊触发的事件,比如appLaunch, appHide 等,还要可以统计错误。

好,那先来看看如何实现产品的需求吧

用户进入小程序可以在 小程序 onLaunch 回调里拿到参数 的scene 值,这样就可以知道用户是怎么进入小程序的了。小case, 难不到我。

嗯,第一个需求实现了,那如何统计第二个呢?如何统计某个页面的停留时间呢?

这也难不倒我,用户在进入页面时会触发onShow 事件, 同样,在离开页面(或者切后台时)会触发onHide事件,我只需要在onShow里记录一下时间,同时在onHide 里也记录一下时间,把两个时间一减就可以了。

   Page({
       data: {
        beginTime: 0,
        endTime: 0
       },
       onShow: function() {
         // Do something when page show.
         this.setData({
           beginTime:  new Date().getTime()
         })
       },
       onHide: function() {
         // Do something when page hide.
         let stayTime = new Date().getTime() - this.beginTime;
         // 这个就是用户在这个页面的停留时间了
       },
   })

等等,这样确实实现了需求,万一产品要统计所有也面的停留时长? 那我们岂不要在每一个页面都这样写一遍?有没有更好的方法呢?

好,接下来就是数据统计实现的要点了,即拦截微信原生事件,这样可以在某个特殊事件触发时,做一些我们统计的事情。同时,还要拦截微信发生网络请求的方法,这样可以拿到网络请求相关的数据,最后,为了能统计到错误,还需要拦截微信发生错误的方法。

1.特殊事件的监听

App(Object object)

注册小程序。接受一个 Object 参数,其指定小程序的生命周期回调等。

App() 必须在 app.js 中调用,必须调用且只能调用一次。不然会出现无法预期的后果。

  • 拦截全局的事件:
  • 下面是小程序官方文档对于App 注册方法的文档:
App({
  onLaunch (options) {
    // Do something initial when launch.
  },
  onShow (options) {
    // Do something when show.
  },
  onHide () {
    // Do something when hide.
  },
  onError (msg) {
    console.log(msg)
  },
  globalData: 'I am global data'
}) 

假如我们要在小程序onLaunch 时打印一句hello Word,我们有哪些方法实现?

方法1:

直接写在onLaunch方法里

  onLaunch (options) {
     console.log('hello World')
  }

方法2:

使用 monkey patch方法 猴子补丁(monkey patch)

猴子补丁主要有以下几个用处:

  1. 在运行时替换方法、属性等
  2. 在不修改第三方代码的情况下增加原来不支持的功能
  3. 在运行时为内存中的对象增加patch而不是在磁盘的源代码中增加

举个栗子,假如我们在console.log 方法里都先打印出当前的时间戳,我们可以这样:

var oldLog = console.log
console.log = function() {
  oldLog.call(this, new Date().getTime())
  oldLog.apply(this, arguments)
}

同理,我们针对onLaunch 进行猴子补丁

var oldAp = App
App = function(options) {
  var oldOnLaunch = options.onLaunch
  options['onLaunch'] = function(t) {
    // 做一些我们自己想做的事情
    console.log('hello word....')
    // 调用原来的onLaunch 方法
    oldOnLaunch.call(this, t)
  }
  
  // 调用原来的App 方法
  oldApp(options)
  
  // 想像一下,小程序内部调用onLaunch 方法应该是这样子的:
  options.onLaunch(params)
}

// 问题,有的时候,我们可能没有注册某一个事件,比如页面的onShow, 所有,我们在替换的时候还需要判断一下参数是否传了对应的方法
Page({
  onLoad (options) {},
  onHide (options) {}
})

// 针对这种情况,我们需要这样写
var oldPage = Page
Page = function(options) {
  if (options['onShow']) {
    // 如过有注册onShow 这个回调
    var oldOnShow = options.onShow
    // onShow 方法调用时都是 传了一个对象
    options['onShow'] = function(t) {
      // doSomething()
      oldOnShow.call(this, t)
    }
  }
  // 调用原来的Page 方法。
  oldPage.apply(null, [].slice.call(arguments))
  // 注意: 下面这两种写都会报错: VM23356:1 Options is not object: {"0":{}} in pages/Badge.js 问题具体原因暂时未找到。
  // oldPage.call(null, arguments)
  // oldPage(arguments)
}

通过上面的方法,我们可以拦截了 App 方法注册的一些全局方法,比如 onLaunch , onShow, onHide, 和Page 注册的事件如 onShow, onHide, onLoad, onPullDownRefresh, 等页面注册事件。

2.网络请求的监听

思路: 拦截微信的请求事件。

 let Request = {
      request: function (e) {
        let success = e[0].success,
          fail = e[0].fail,
          beginTime = smaUtils.getTime(),
          endTime = 0
        // 拦截请求成功方法
        e[0].success = function () {
          endTime = smaUtils.getTime()
          const performance = {
            type: constMap.performance,
            event: eventMap.wxRequest,
            url: e[0].url,
            status: arguments[0].statusCode,
            begin: beginTime,
            end: endTime,
            total: endTime - beginTime
          }
          smaUtils.logInfo('success performance:', performance)
          // 这里做上报的事情
          // SMA.performanceReport(performance)
          success && success.apply(this, [].slice.call(arguments))
        }
        // 拦截请求失败方法
        e[0].fail = function () {
          endTime = smaUtils.getTime()
          const performance = {
            type: constMap.performance,
            event: eventMap.wxRequest,
            url: e[0].url,
            status: arguments[0].statusCode,
            begin: beginTime,
            end: endTime,
            total: endTime - beginTime
          }
          smaUtils.logInfo('fail performance:', performance)
          // 这里做上报的事情
          // SMA.performanceReport(performance)
          fail && fail.apply(this, [].slice.call(arguments))
        }
      },
   }
 
 
    // 替换微信相关属性
    let oldWx = wx,
      newWx = {}
    for (var p in wx) {
      if (Request[p]) {
        let p2 = p.toString()
        newWx[p2] = function () {
          Request[p2](arguments)
          // 调用原来的wx.request 方法
          oldWx[p2].apply(oldWx, [].slice.call(arguments))
        }
      } else {
        newWx[p] = oldWx[p]
      }
    }
    // eslint-disable-next-line
    wx = newWx

疑惑:为什么要使用替换整个wx对象的方法呢? 不直接用我们的request 方法 替换 wx.request 方法

var oldRequest = wx.request
wx.request = function(e) {
  // doSomething();
  console.log('请求拦截操作...')
  oldRequest.call(this, e); // 调用老的request方法
}
// 结果报错了:
//  TypeError: Cannot set property request of [object Object] which has only a getter

3.错误的监听

3.1 拦截App里注册的 onError事件

var oldAp = App
App = function(options) {
  var oldOnError = options.onErrr
  options['onErrr'] = function(t) {
    // 做一些我们自己想做的事情
    console.log('统计错误....', t)
    // 调用原来的onLaunch 方法
    oldOnError.call(this, t)
  }
  
  // 调用原来的App 方法
  oldApp(options)
}

3.2 拦截 conole.error

 console.error = function() {
      var e = [].slice.call(arguments)
      if (!e.length) { return true }
      const currRoute = smaUtils.getPagePath()
      // 统计错误事件
      // SMA.errorReport({event: eventMap.onError, route: currRoute, errrMsg: arguments[0]})
      smaUtils.logInfo('捕捉到error 事件,', e)
      oldError.apply(console, e)
  }

至此,我们已经有能力在小程序发起请求时,发生错误时,生命周期或者特殊函数回调时,我们都能在里面做一些我们想要的数据统计功能了。

说了这么多大家估计也看累了。鉴于篇幅,具体的代码就不在这里贴了。

最终实现的数据统计模块大致实现了以下功能:

  • 普通埋点信息上报功能
  • 错误信息上报功能
  • 性能数据上报功能
  • 具体的上报时机支持配置
  • 支持指定网络环境上报
  • 支持统计数据缓存到微信本地功能

整个统计代码的配置文件如下:

const wxaConfig = {
  project: 'myMiniProgram', // 项目名称
  trackUrl: 'https://youhost.com/batch', // 后台数据统计接口
  errorUrl: 'https://youhost.com/batch',  // 后台错误上报接口
  performanceUrl: 'https://youhost.com/batch', // 后台性能上报接口
  version: '0.1',
  prefix: '_wxa_',
  priority: ['track', 'performance', 'error'], // 发送请求的优先级,发送时,会依次发送
  useStorage: true, // 是否开启storage缓存
  debug: false, // 是否开启调试(显示log)
  autoTrack: true, // 自动上报 onShow, onHide, 分享等 内置事件
  errorReport: false, // 是否开启错误上报
  performanceReport: false, // 接口性能上报
  maxReportNum: 20, // 当次上报最大条数
  intervalTime: 15,  // 定时上报的时间间隔,单位 s, 仅当开启了定时上报有效。
  networkList: ['wifi', '4g', '3g'], // 允许上报的网络环境
  opportunity: 'pageHide' // pageHide、appHide、realTime(实时上报)、timing(定时上报) 上报的时机,四选一
}
export default wxaConfig

具体上报时,上报的数据结构大致长这样:

你们是如何实现小程序数据统计的呢? 欢迎在issue里交流~~

经验相关

document.querySelector("#22id"); 不能以数字开头,后面有数字可以支持
这个将导致错误:
document.getElementById('#id22') 这个则可以

VM188:1 Uncaught DOMException: Failed to execute 'querySelector' on 'Document': '#01test' is not a valid selector.
    at <anonymous>:1:10

对于css 2:
该值在元素的主子树中的所有ID中必须是唯一的,并且必须至少包含一个字符。该值不能包含任何空格字符。

身份证可以采用什么形式没有其他限制; 特别是,ID可以只包含数字,以数字开头,以下划线开头,仅包含标点符号等。

但是,querySelector方法使用CSS3选择器来查询DOM,CSS3不支持以数字开头的ID选择器:

在CSS中,标识符(包括元素名称,类和选择器中的ID)只能包含字符[a-zA-Z0-9]和ISO 10646字符U + 00A0及更高版本,以及连字符( - )和下划线_); 他们不能以数字,两个连字符或连字符后跟数字开头。

使用类似于b22ID属性的值,并且您的代码将起作用。
原文链接:
https://stackoverflow.com/questions/37270787/uncaught-syntaxerror-failed-to-execute-queryselector-on-document

杂谈

一些文学相关的东西?

你是因为看见而相信,还是因为相信而看见?

js 基础复习

内存空间

变量对象与堆内存
var a = 20;
var b = 'abc';
var c = true;
var d = { m: 20 }
一、栈与堆

注:栈,也可以叫堆栈
与C/C++不同,JavaScript中并没有严格意义上区分栈内存与堆内存。因此我们可以粗浅的理解为JavaScript的所有数据都保存在堆内存中。但是在某些场景,我们仍然需要基于堆栈数据结构的思路进行处理

要简单理解栈的存取方式,我们可以通过类比乒乓球盒子来分析。如下图左侧。

乒乓球盒子与栈类比
这种乒乓球的存放方式与栈中存取数据的方式如出一辙。处于盒子中最顶层的乒乓球5,它一定是最后被放进去,但可以最先被使用。而我们想要使用底层的乒乓球1,就必须将上面的4个乒乓球取出来,让乒乓球1处于盒子顶层。这就是栈空间先进后出,后进先出的特点。图中已经详细的表明了栈空间的存储原理。

堆存取数据的方式,则与书架与书非常相似。

书虽然也整齐的存放在书架上,但是我们只要知道书的名字,我们就可以很方便的取出我们想要的书,而不用像从乒乓球盒子里取乒乓一样,非得将上面的所有乒乓球拿出来才能取到中间的某一个乒乓球。好比在JSON格式的数据中,我们存储的key-value是可以无序的,因为顺序的不同并不影响我们的使用,我们只需要关心书的名字。

二、变量对象与基础数据类型

JavaScript的执行上下文生成之后,会创建一个叫做变量对象的特殊对象(具体会在下一篇文章与执行上下文一起总结),JavaScript的基础数据类型往往都会保存在变量对象中。

严格意义上来说,变量对象也是存放于堆内存中,但是由于变量对象的特殊职能,我们在理解时仍然需要将其于堆内存区分开来。
基础数据类型都是一些简单的数据段,JavaScript中有5中基础数据类型,分别是Undefined、Null、Boolean、Number、String。基础数据类型都是按值访问,因为我们可以直接操作保存在变量中的实际的值。

三、引用数据类型与堆内存

与其他语言不通,JS的引用数据类型,比如数组Array,它们值的大小是不固定的。引用数据类型的值是保存在堆内存中的对象。JavaScript不允许直接访问堆内存中的位置,因此我们不能直接操作对象的堆内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。因此,引用类型的值都是按引用访问的。这里的引用,我们可以粗浅地理解为保存在变量对象中的一个地址,该地址与堆内存的实际值相关联。

为了更好的搞懂变量对象与堆内存,我们可以结合以下例子与图解进行理解。

var a1 = 0; // 变量对象
var a2 = 'this is string'; // 变量对象
var a3 = null; // 变量对象

var b = { m: 20 }; // 变量b存在于变量对象中,{m: 20} 作为对象存在于堆内存中
var c = [1, 2, 3]; // 变量c存在于变量对象中,[1, 2, 3] 作为对象存在于堆内存中

上例图解
因此当我们要访问堆内存中的引用数据类型时,实际上我们首先是从变量对象中获取了该对象的地址引用(或者地址指针),然后再从堆内存中取得我们需要的数据。

理解了JS的内存空间,我们就可以借助内存空间的特性来验证一下引用类型的一些特点了。

在前端面试中我们常常会遇到这样一个类似的题目

// demo01.js
var a = 20;
var b = a;
b = 30;

// 这时a的值是多少?
// demo02.js
var m = { a: 10, b: 20 }
var n = m;
n.a = 15;

// 这时m.a的值是多少
在变量对象中的数据发生复制行为时,系统会自动为新的变量分配一个新值。var b = a执行之后,a与b虽然值都等于20,但是他们其实已经是相互独立互不影响的值了。具体如图。所以我们修改了b的值以后,a的值并不会发生变化。

demo01图解
在demo02中,我们通过var n = m执行一次复制引用类型的操作。引用类型的复制同样也会为新的变量自动分配一个新的值保存在变量对象中,但不同的是,这个新的值,仅仅只是引用类型的一个地址指针。当地址指针相同时,尽管他们相互独立,但是在变量对象中访问到的具体对象实际上是同一个。如图所示。

因此当我改变n时,m也发生了变化。这就是引用类型的特性。

demo02图解
内存空间管理

因为JavaScript具有自动垃圾收集机制,所以我们在开发时好像不用关心内存的使用问题,内存的分配与回收都完全实现了自动管理。

JavaScript的内存生命周期

  1. 分配你所需要的内存
  2. 使用分配到的内存(读、写)
  3. 不需要时将其释放、归还
    var a = 20; // 在内存中给数值变量分配空间
    alert(a + 100); // 使用内存
    a = null; // 使用完毕之后,释放内存空间
    第一步和第二步我们都很好理解,JavaScript在定义变量时就完成了内存分配。第三步释放内存空间则是我们需要重点理解的一个点。

JavaScript有自动垃圾收集机制,那么这个自动垃圾收集机制的原理是什么呢?其实很简单,就是找出那些不再继续使用的值,然后释放其占用的内存。垃圾收集器会每隔固定的时间段就执行一次释放操作。

在JavaScript中,最常用的是通过标记清除的算法来找到哪些对象是不再继续使用的,因此a = null其实仅仅只是做了一个释放引用的操作,让 a 原本对应的值失去引用,脱离执行环境,这个值会在下一次垃圾收集器执行操作时被找到并释放。而在适当的时候解除引用,是为页面获得更好性能的一个重要方式。

在局部作用域中,当函数执行完毕,局部变量也就没有存在的必要了,因此垃圾收集器很容易做出判断并回收。但是全局变量什么时候需要自动释放内存空间则很难判断,因此在我们的开发中,需要尽量避免使用全局变量,以确保性能问题。
要详细了解垃圾收集机制,建议阅读《JavaScript高级编程》中的4.3节

执行上下文

先随便放张图

我们在JS学习初期或者面试的时候常常会遇到考核变量提升的思考题。比如先来一个简单一点的。

console.log(a); // 这里会打印出什么?
var a = 20;
每次当控制器转到可执行代码的时候,就会进入一个执行上下文。执行上下文可以理解为当前代码的执行环境,它会形成一个作用域。JavaScript中的运行环境大概包括三种情况。

全局环境:JavaScript代码运行起来会首先进入该环境
函数环境:当函数被调用执行时,会进入当前函数中执行代码
eval
因此在一个JavaScript程序中,必定会产生多个执行上下文,JavaScript引擎会以堆栈的方式来处理它们,这个堆栈,我们称其为函数调用栈(call stack)。栈底永远都是全局上下文,而栈顶就是当前正在执行的上下文。

当代码在执行过程中,遇到以上三种情况,都会生成一个执行上下文,放入栈中,而处于栈顶的上下文执行完毕之后,就会自动出栈。为了更加清晰的理解这个过程,根据下面的例子,结合图示给大家展示。

var color = 'blue';

function changeColor() {
var anotherColor = 'red';

function swapColors() {
    var tempColor = anotherColor;
    anotherColor = color;
    color = tempColor;
}

swapColors();

}

changeColor();
我们用ECStack来表示处理执行上下文组的堆栈。我们很容易知道,第一步,首先是全局上下文入栈。

第一步:全局上下文入栈
全局上下文入栈之后,其中的可执行代码开始执行,直到遇到了changeColor(),这一句激活函数changeColor创建它自己的执行上下文,因此第二步就是changeColor的执行上下文入栈。

第二步:changeColor的执行上下文入栈
changeColor的上下文入栈之后,控制器开始执行其中的可执行代码,遇到swapColors()之后又激活了一个执行上下文。因此第三步是swapColors的执行上下文入栈。

第三步:swapColors的执行上下文入栈
在swapColors的可执行代码中,再没有遇到其他能生成执行上下文的情况,因此这段代码顺利执行完毕,swapColors的上下文从栈中弹出。

第四步:swapColors的执行上下文出栈
swapColors的执行上下文弹出之后,继续执行changeColor的可执行代码,也没有再遇到其他执行上下文,顺利执行完毕之后弹出。这样,ECStack中就只身下全局上下文了。

第五步:changeColor的执行上下文出栈
全局上下文在浏览器窗口关闭后出栈。

注意:函数中,遇到return能直接终止可执行代码的执行,因此会直接将当前上下文弹出栈。

整个过程
详细了解了这个过程之后,我们就可以对执行上下文总结一些结论了。

单线程
同步执行,只有栈顶的上下文处于执行中,其他上下文需要等待
全局上下文只有唯一的一个,它在浏览器关闭时出栈
函数的执行上下文的个数没有限制
每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此。
为了巩固一下执行上下文的理解,我们再来绘制一个例子的演变过程,这是一个简单的闭包例子。

function f1(){
var n=999;
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
因为f1中的函数f2在f1的可执行代码中,并没有被调用执行,因此执行f1时,f2不会创建新的上下文,而直到result执行时,才创建了一个新的。具体演变过程如下。

上例演变过程

变量对象

在JavaScript中,不可避免的需要声明变量和函数,可是JS解析器是如何找到这些变量的呢?我们还得对执行上下文有一个进一步的了解。

我们已经知道,当调用一个函数时(激活),一个新的执行上下文就会被创建。而一个执行上下文的生命周期可以分为两个阶段。

创建阶段
在这个阶段中,执行上下文会分别创建变量对象,建立作用域链,以及确定this的指向

代码执行阶段
创建完成之后,就会开始执行代码,这个时候,会完成变量赋值,函数引用,以及执行其他代码。

执行上下文生命周期
(到这里,先有个印象)

变量对象(Variable Object)

变量对象的创建,依次经历了以下几个过程。

建立arguments对象。检查当前上下文中的参数,建立该对象下的属性与属性值。

检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在,那么该属性将会被新的引用所覆盖。

检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性值不会被修改。

变量对象创建过程
根据这个规则,理解变量提升就变得十分简单了。

在上面的规则中我们看出,function声明会比var声明优先级更高一点。为了帮助大家更好的理解变量对象,我们结合一些简单的例子来进行探讨。

// demo01
function test() {
console.log(a);
console.log(foo());

var a = 1;
function foo() {
    return 2;
}

}

test();
在上例中,我们直接从test()的执行上下文开始理解。全局作用域中运行test()时,test()的执行上下文开始创建。为了便于理解,我们用如下的形式来表示

创建过程
testEC = {
// 变量对象
VO: {},
scopeChain: {},
this: {}
}

// 因暂时不解释作用域链和this

// VO 为 Variable Object的缩写,即变量对象
VO = {
arguments: {...}, //注:在浏览器的展示中,函数的参数可能并不是放在arguments对象中,这里为了方便理解,我做了这样的处理
foo: // 表示foo的地址引用
a: undefined
}
未进入执行阶段之前,变量对象中的属性都不能访问!但是进入执行阶段之后,变量对象转变为了活动对象,里面的属性都能被访问了,然后开始进行执行阶段的操作。

变量对象和活动对象的区别:是同一个对象,只是处于执行上下文的不同生命周期。
// 执行阶段
VO -> AO // Active Object
AO = {
arguments: {...},
foo: ,
a: 1
}
因此,上面的例子demo1,执行顺序就变成了这样

function test() {
function foo() {
return 2;
}
var a;
console.log(a);
console.log(foo());
a = 1;
}

test();
再来一个例子,巩固一下我们的理解。

// demo2
function test() {
console.log(foo);
console.log(bar);

var foo = 'Hello';
console.log(foo);
var bar = function () {
    return 'world';
}

function foo() {
    return 'hello';
}

}

test();
// 创建阶段
VO = {
arguments: {...},
foo: ,
bar: undefined
}
// 这里有一个需要注意的地方,因为var声明的变量当遇到同名的属性时,会跳过而不会覆盖
// 执行阶段
VO -> AO
VO = {
arguments: {...},
foo: 'Hello',
bar:
}
需要结合上面的知识,仔细对比这个例子中变量对象从创建阶段到执行阶段的变化,如果你已经理解了,说明变量对象相关的东西都已经难不倒你了。

全局上下文的变量对象

以浏览器中为例,全局对象为window。
全局上下文有一个特殊的地方,它的变量对象,就是window对象。而这个特殊,在this指向上也同样适用,this也是指向window。

// 以浏览器中为例,全局对象为window
// 全局上下文
windowEC = {
VO: window,
scopeChain: {},
this: window
}
除此之外,全局上下文的生命周期,与程序的生命周期一致,只要程序运行不结束,比如关掉浏览器窗口,全局上下文就会一直存在。其他所有的上下文环境,都能直接访问全局上下文的属性。

闭包

攻克闭包难题
一、作用域与作用域链

作用域

在JavaScript中,我们可以将作用域定义为一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找。

这里的标识符,指的是变量名或者函数名
JavaScript中只有全局作用域与函数作用域(因为eval我们平时开发中几乎不会用到它,这里不讨论)。

作用域与执行上下文是完全不同的两个概念,但是一定要仔细区分。

JavaScript代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。编译阶段由编译器完成,将代码翻译成可执行代码,这个阶段作用域规则会确定。执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段创建。

过程
作用域链

执行上下文的生命周期,如下图。

执行上下文生命周期
我们发现,作用域链是在执行上下文的创建阶段生成的。这个就奇怪了。上面我们刚刚说作用域在编译阶段确定规则,可是为什么作用域链却在执行阶段确定呢?

作用域是一套规则,作用域链是这套规则的具体实现。这就是作用域与作用域链的关系

我们知道函数在调用激活时,会开始创建对应的执行上下文,在执行上下文生成的过程中,变量对象,作用域链,以及this的值会分别被确定。

作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。

为了帮助大家理解作用域链,我我们先结合一个例子,以及相应的图示来说明。

var a = 20;

function test() {
var b = a + 10;

function innerTest() {
    var c = 10;
    return b + c;
}

return innerTest();

}

test();
在上面的例子中,全局,函数test,函数innerTest的执行上下文先后创建。我们设定他们的变量对象分别为VO(global),VO(test), VO(innerTest)。而innerTest的作用域链,则同时包含了这三个变量对象,所以innerTest的执行上下文可如下表示。

innerTestEC = {
VO: {...}, // 变量对象
scopeChain: [VO(innerTest), VO(test), VO(global)], // 作用域链
this: {}
}
我们可以直接用一个数组来表示作用域链,数组的第一项scopeChain[0]为作用域链的最前端,而数组的最后一项,为作用域链的最末端,所有的最末端都为全局变量对象。

作用域链图示
注意,因为变量对象在执行上下文进入执行阶段时,就变成了活动对象,因此图中使用了AO来表示。Active Object
是的,作用域链是由一系列变量对象组成,我们可以在这个单向通道中,查询变量对象中的标识符,这样就可以访问到上一层作用域中的变量了。

二、闭包

对于那些有一点 JavaScript 使用经验但从未真正理解闭包概念的人来说,理解闭包可以看作是某种意义上的重生,突破闭包的瓶颈可以使你功力大增。

闭包与作用域链息息相关;
闭包是在函数执行过程中被确认。
先直截了当的抛出闭包的定义:当函数可以记住并访问所在的作用域(全局作用域除外)时,就产生了闭包,即使函数是在当前作用域之外执行。

简单来说,假设函数A在函数B的内部进行定义了,并且当函数A在执行时,访问了函数B内部的变量对象,那么B就是一个闭包。
JavaScript拥有自动的垃圾回收机制,关于垃圾回收机制,有一个重要的行为,那就是,当一个值,在内存中失去引用时,垃圾回收机制会根据特殊的算法找到它,并将其回收,释放内存。

而我们知道,函数的执行上下文,在执行完毕之后,生命周期结束,那么该函数的执行上下文就会失去引用。其占用的内存空间很快就会被垃圾回收器释放。可是闭包的存在,会阻止这一过程。

先来一个简单的例子。

var fn = null;
function foo() {
var a = 2;
function innnerFoo() {
console.log(a);
}
fn = innnerFoo; // 将 innnerFoo的引用,赋值给全局变量中的fn
}

function bar() {
fn(); // 此处的保留的innerFoo的引用
}

foo();
bar(); // 2
在上面的例子中,foo()执行完毕之后,按照常理,其执行环境生命周期会结束,所占内存被垃圾收集器释放。但是通过fn = innerFoo,函数innerFoo的引用被保留了下来,复制给了全局变量fn。这个行为,导致了foo的变量对象,也被保留了下来。于是,函数fn在函数bar内部执行时,依然可以访问这个被保留下来的变量对象。所以此刻仍然能够访问到变量a的值。

这样,我们就可以称foo为闭包。

下图展示了闭包foo的作用域链。

闭包foo的作用域链,图中标题写错了,请无视
我们可以在chrome浏览器的开发者工具中查看这段代码运行时产生的函数调用栈与作用域链的生成情况。如下图。

从图中可以看出,chrome浏览器认为闭包是foo,而不是通常我们认为的innerFoo
在上面的图中,红色箭头所指的正是闭包。其中Call Stack为当前的函数调用栈,Scope为当前正在被执行的函数的作用域链,Local为当前的局部变量。

所以,通过闭包,我们可以在其他的执行上下文中,访问到函数的内部变量。比如在上面的例子中,我们在函数bar的执行环境中访问到了函数foo的a变量。个人认为,从应用层面,这是闭包最重要的特性。利用这个特性,我们可以实现很多有意思的东西。

对上面的例子稍作修改,如果我们在函数bar中声明一个变量c,并在闭包fn中试图访问该变量,运行结果会抛出错误。

var fn = null;
function foo() {
var a = 2;
function innnerFoo() {
console.log(c); // 在这里,试图访问函数bar中的c变量,会抛出错误
console.log(a);
}
fn = innnerFoo; // 将 innnerFoo的引用,赋值给全局变量中的fn
}

function bar() {
var c = 100;
fn(); // 此处的保留的innerFoo的引用
}

foo();
bar();
闭包的应用场景

接下来,我们来总结下,闭包的常用场景。

延迟函数setTimeout
我们知道setTimeout的第一个参数是一个函数,第二个参数则是延迟的时间。在下面例子中,

function fn() {
console.log('this is test.')
// todo 改写
}
var timer = setTimeout(fn, 1000);
console.log(timer);
执行上面的代码,变量timer的值,会立即输出出来,表示setTimeout这个函数本身已经执行完毕了。但是一秒钟之后,fn才会被执行。这是为什么?

按道理来说,既然fn被作为参数传入了setTimeout中,那么fn将会被保存在setTimeout变量对象中,setTimeout执行完毕之后,它的变量对象也就不存在了。可是事实上并不是这样。至少在这一秒钟的事件里,它仍然是存在的。这正是因为闭包。

很显然,这是在函数的内部实现中,setTimeout通过特殊的方式,保留了fn的引用,让setTimeout的变量对象,并没有在其执行完毕后被垃圾收集器回收。因此setTimeout执行结束后一秒,我们任然能够执行fn函数。

柯里化
模块
在我看来,模块是闭包最强大的一个应用场景。如果你是初学者,对于模块的了解可以暂时不用放在心上,因为理解模块需要更多的基础知识。但是如果你已经有了很多JavaScript的使用经验,在彻底了解了闭包之后,不妨借助本文介绍的作用域链与闭包的思路,重新理一理关于模块的知识。这对于我们理解各种各样的设计模式具有莫大的帮助。

(function () {
var a = 10;
var b = 20;

function add(num1, num2) {
    var num1 = !!num1 ? num1 : a;
    var num2 = !!num2 ? num2 : b;

    return num1 + num2;
}

window.add = add;

})();

add(10, 20);
在上面的例子中,我使用函数自执行的方式,创建了一个模块。add是模块对外暴露的一个公共方法。而变量a,b被作为私有变量。在面向对象的开发中,我们常常需要考虑是将变量作为私有变量,还是放在构造函数中的this中,因此理解闭包,以及原型链是一个非常重要的事情。模块十分重要

此图中可以观看到当代码执行到add方法时的调用栈与作用域链,此刻的闭包为外层的自执行函数
为了验证自己有没有搞懂作用域链与闭包,这里留下一个经典的思考题,常常也会在面试中被问到。

利用闭包,修改下面的代码,让循环输出的结果依次为1, 2, 3, 4, 5

for (var i=1; i<=5; i++) {
setTimeout( function timer() {
console.log(i);
}, i*1000 );
}

this

回顾一下,执行上下文的生命周期,如下图。

执行上下文生命周期
在执行上下文的创建阶段,会分别生成变量对象,建立作用域链,确定this指向。

在这里,我们需要得出一个非常重要一定要牢记于心的结论,this的指向,是在函数被调用的时候确定的。也就是执行上下文被创建时确定的。因此我们可以很容易就能理解到,一个函数中的this指向,可以是非常灵活的。比如下面的例子中,同一个函数由于调用方式的不同,this指向了不一样的对象。

var a = 10;
var obj = {
a: 20
}

function fn () {
console.log(this.a);
}

fn(); // 10
fn.call(obj); // 20
除此之外,在函数执行过程中,this一旦被确定,就不可更改了。

var a = 10;
var obj = {
a: 20
}

function fn () {
this = obj; // 这句话试图修改this,运行后会报错
console.log(this.a);
}

fn();
一、全局对象中的this

关于全局对象的this,我之前在总结变量对象的时候提到过,它是一个比较特殊的存在。全局环境中的this,指向它本身。因此,这也相对简单,没有那么多复杂的情况需要考虑。

// 通过this绑定到全局对象
this.a2 = 20;

// 通过声明绑定到变量对象,但在全局环境中,变量对象就是它自身
var a1 = 10;

// 仅仅只有赋值操作,标识符会隐式绑定到全局对象
a3 = 30;

// 输出结果会全部符合预期
console.log(a1);
console.log(a2);
console.log(a3);
二、函数中的this

在总结函数中this指向之前,我想我们有必要通过一些奇怪的例子,来感受一下函数中this的捉摸不定。

// demo01
var a = 20;
function fn() {
console.log(this.a);
}
fn();
// demo02
var a = 20;
function fn() {
function foo() {
console.log(this.a);
}
foo();
}
fn();
// demo03
var a = 20;
var obj = {
a: 10,
c: this.a + 20,
fn: function () {
return this.a;
}
}

console.log(obj.c);
console.log(obj.fn());
这几个例子需要读者老爷们花点时间稍微感受一下,如果你暂时没想明白怎么回事,也不用着急,我们一点一点来分析。

分析之前,我们先直接了当抛出结论。

在一个函数上下文中,this由调用者提供,由调用函数的方式来决定。如果调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的this指向该对象。如果函数独立调用,那么该函数内部的this,则指向undefined。但是在非严格模式中,当this指向undefined时,它会被自动指向全局对象。

从结论中我们可以看出,想要准确确定this指向,找到函数的调用者以及区分他是否是独立调用就变得十分关键。

// 为了能够准确判断,我们在函数内部使用严格模式,因为非严格模式会自动指向全局
function fn() {
'use strict';
console.log(this);
}

fn(); // fn是调用者,独立调用
window.fn(); // fn是调用者,被window所拥有
在上面的简单例子中,fn()作为独立调用者,按照定义的理解,它内部的this指向就为undefined。而window.fn()则因为fn被window所拥有,内部的this就指向了window对象。

但是我们需要特别注意的是demo03。在demo03中,对象obj中的c属性使用this.a + 20来计算,而他的调用者obj.c并非是一个函数。因此他不适用于上面的规则,我们要对这种方式单独下一个结论。

当obj在全局声明时,无论obj.c在什么地方调用,这里的this都指向全局对象,而当obj在函数环境中声明时,这个this指向undefined,在非严格模式下,会自动转向全局对象。可运行下面的例子查看区别。

'use strict';
var a = 20;
function foo () {
var a = 1;
var obj = {
a: 10,
c: this.a + 20,
fn: function () {
return this.a;
}
}
return obj.c;

}
console.log(foo()); // 运行会报错
实际开发中,并不推荐这样使用this;
上面多次提到的严格模式,需要大家认真对待,因为在实际开发中,现在基本已经全部采用严格模式了,而最新的ES6,也是默认支持严格模式。
再来看一些容易理解错误的例子,加深一下对调用者与是否独立运行的理解。

var a = 20;
var foo = {
a: 10,
getA: function () {
return this.a;
}
}
console.log(foo.getA());

var test = foo.getA;
console.log(test());
foo.getA()中,getA是调用者,他不是独立调用,被对象foo所拥有,因此它的this指向了foo。而test()作为调用者,尽管他与foo.getA的引用相同,但是它是独立调用的,因此this指向undefined,在非严格模式,自动转向全局window。

稍微修改一下代码,大家自行理解。

var a = 20;
function getA() {
return this.a;
}
var foo = {
a: 10,
getA: getA
}
console.log(foo.getA());
灵机一动,再来一个。如下例子。

function foo() {
console.log(this.a)
}

function active(fn) {
fn();
}

var a = 20;
var obj = {
a: 10,
getA: foo
}

active(obj.getA);
三、使用call,apply显示指定this

JavaScript内部提供了一种机制,让我们可以自行手动设置this的指向。它们就是call与apply。所有的函数都具有着两个方法。它们除了参数略有不同,其功能完全一样。它们的第一个参数都为this将要指向的对象。

如下例子所示。fn并非属于对象obj的方法,但是通过call,我们将fn内部的this绑定为obj,因此就可以使用this.a访问obj的a属性了。这就是call/apply的用法。

function fn() {
console.log(this.a);
}
var obj = {
a: 20
}

fn.call(obj);
而call与applay后面的参数,都是向将要执行的函数传递参数。其中call以一个一个的形式传递,apply以数组的形式传递。这是他们唯一的不同。

function fn(num1, num2) {
console.log(this.a + num1 + num2);
}
var obj = {
a: 20
}

fn.call(obj, 100, 10); // 130
fn.apply(obj, [20, 10]); // 50
因为call/apply的存在,这让JavaScript变得十分灵活。因此就让call/apply拥有了很多有用处的场景。简单总结几点,也欢迎大家补充。

将类数组对象转换为数组
function exam(a, b, c, d, e) {

// 先看看函数的自带属性 arguments 什么是样子的
console.log(arguments);

// 使用call/apply将arguments转换为数组, 返回结果为数组,arguments自身不会改变
var arg = [].slice.call(arguments);
// http://www.w3school.com.cn/jsref/jsref_slice_array.asp
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/slice

console.log(arg);

}

exam(2, 8, 9, 10, 3);

// result:
// { '0': 2, '1': 8, '2': 9, '3': 10, '4': 3 }
// [ 2, 8, 9, 10, 3 ]
//
// 也常常使用该方法将DOM中的nodelist转换为数组
// [].slice.call( document.getElementsByTagName('li') );
根据自己的需要灵活修改this指向
var foo = {
name: 'joker',
showName: function() {
console.log(this.name);
}
}
var bar = {
name: 'rose'
}
foo.showName.call(bar);
实现继承
// 定义父级的构造函数
var Person = function(name, age) {
this.name = name;
this.age = age;
this.gender = ['man', 'woman'];
}

// 定义子类的构造函数
var Student = function(name, age, high) {

// use call
Person.call(this, name, age);
this.high = high;

}
Student.prototype.message = function() {
console.log('name:'+this.name+', age:'+this.age+', high:'+this.high+', gender:'+this.gender[0]+';');
}

new Student('xiaom', 12, '150cm').message();

// result
// ----------
// name:xiaom, age:12, high:150cm, gender:man;
在向其他执行上下文的传递中,确保this的指向保持不变
如下面的例子中,我们期待的是getA被obj调用时,this指向obj,但是由于匿名函数的存在导致了this指向的丢失,在这个匿名函数中this指向了全局,因此我们需要想一些办法找回正确的this指向。

var obj = {
a: 20,
getA: function() {
setTimeout(function() {
console.log(this.a)
}, 1000)
}
}

obj.getA();
常规的解决办法很简单,就是使用一个变量,将this的引用保存起来。我们常常会用到这方法,但是我们也要借助上面讲到过的知识,来判断this是否在传递中被修改了,如果没有被修改,就没有必要这样使用了。

var obj = {
a: 20,
getA: function() {
var self = this;
setTimeout(function() {
console.log(self.a)
}, 1000)
}
}
另外就是借助闭包与apply方法,封装一个bind方法。

function bind(fn, obj) {
return function() {
return fn.apply(obj, arguments);
}
}

var obj = {
a: 20,
getA: function() {
setTimeout(bind(function() {
console.log(this.a)
}, this), 1000)
}
}

obj.getA();
当然,也可以使用ES5中已经自带的bind方法。它与我上面封装的bind方法是一样的效果。

var obj = {
a: 20,
getA: function() {
setTimeout(function() {
console.log(this.a)
}.bind(this), 1000)
}
}
四、构造函数与原型方法上的this

在封装对象的时候,我们几乎都会用到this,但是,只有少数人搞明白了在这个过程中的this指向,就算我们理解了原型,也不一定理解了this。所以这一部分,我认为将会为这篇文章最重要最核心的部分。理解了这里,将会对你学习JS面向对象产生巨大的帮助。

结合下面的例子,我在例子抛出几个问题大家思考一下。

function Person(name, age) {

// 这里的this指向了谁?
this.name = name;
this.age = age;   

}

Person.prototype.getName = function() {

// 这里的this又指向了谁?
return this.name;

}

// 上面的2个this,是同一个吗,他们是否指向了原型对象?

var p1 = new Person('Nick', 20);
p1.getName();
我们已经知道,this,是在函数调用过程中确定,因此,搞明白new的过程中到底发生了什么就变得十分重要。

通过new操作符调用构造函数,会经历以下4个阶段。

创建一个新的对象;
将构造函数的this指向这个新对象;
指向构造函数的代码,为这个对象添加属性,方法等;
返回新对象。
因此,当new操作符调用构造函数时,this其实指向的是这个新创建的对象,最后又将新的对象返回出来,被实例对象p1接收。因此,我们可以说,这个时候,构造函数的this,指向了新的实例对象,p1。

而原型方法上的this就好理解多了,根据上边对函数中this的定义,p1.getName()中的getName为调用者,他被p1所拥有,因此getName中的this,也是指向了p1。

原文地址:http://www.jianshu.com/p/cd3fee40ef59

大杂烩

记录一些生活中遇到的问题和解决方案,或者一些有用的积累:

  1. 访问网站容易被劫持,有些网站无法访问
    原因: 网络供应商劫持了DNS, 注入了广告
    解决办法:
  2. 设置路由器的公共DNS, 不使用运营商提供的
  3. 独立设置电脑,手机 的DNS (路由器和设备都设置了话,貌似是使用设备设置的)
    知乎:公共DNS哪家强?
    国内部分网络运营商需要配置DNS
    Mac :修改 DNS 及清除 DNS 缓存
    路由器设置DNS
    114官网

JS中作用域和原型链

##JS中作用域和原型链
2017-07-04

####1.作用域:

   var a =1;
   function fn1(a){
    var a =2;
    console.log(a);
   }
   console.log(a);
   fn1(3);

    //变形1"
    // var a =1;
    // function fn1(a){
    //  console.log(a);
    //  var a =2;
    //  console.log(a);
    // }
    // console.log(a);
    // fn1(3);

    //变形3:
    // var a =1;
    // function fn1(a){
    //  console.log(a);
    //  a=4;
    //  console.log(a);
    // }
    // console.log(a);
    // fn1(3);
    // console.log(a);
    

    //放在不同的script标签里
    var o2='the second script';
    console.log(o);

概念:
作用域:
首先我们需要了解的是作用域做什么的?当JavaScript引擎在某一作用域中遇见变量和函数的时候,需要能够明确变量和函数所对应的值是什么,所以就需要作用域来对变量和函数进行查找,并且还需要确定当前代码是否对该变量具有访问权限。也就是说作用域主要有以下的任务:

收集并维护所有声明的标识符(变量和函数)
依照特定的规则对标识符进行查找
确定当前的代码对标识符的访问权限

来个例子:

function foo(b) {
    console.log( b ); 
}

foo( 3 );

执行上述代码流程如下:

查询标识符foo,得到变量后执行该变量
查询标识符b,得到变量后对其赋值为3
查询标识符console,得到变量后准备执行属性log
查询标识符b,得到变量后,作为参数传入console.log执行

作用域链;
代码可以访问当前的作用域的变量,对于嵌套的父级作用域中的变量也可以访问。我们知道JavaScript在ES5中是没有块级作用域的,只有函数可以创建块作用域。

function Outer(){
    var outer = 'outer';
    Inner();
    function Inner(){
        var inner = 'inner';
        console.log(outer,inner) // outer inner
    }
}

``当引擎执行到函数Inner内部的时候,不仅可以访问当前作用域而且可以访问到Outer的作用域,从而可以访问到标识符outer。因此我们发现当多个作用域相互嵌套的时候,就形成了作用域链。词法作用域在查找标识符的时候,优先在本作用域中查找。如果本作用域没有找到标识符,会继续向上一级查找,当抵达最外层的全局作用域仍然没有找到,则会停止对标识符的搜索。`

看个更加复杂一点的例子:

var a=1;
function fn1(){
    console.log(a);  //第一个a 输出什么
    var a=2;
    function fn1_1(){
        var a=3;
        console.log(a); //第二个a 输出什么
    }
    console.log(a); //第三个a输出什么
    fn1_1();
}
fn1();

关于作用域的总结:
词法作用域在查找标识符的时候,优先在本作用域中查找。如果本作用域没有找到标识符,会继续向上一级查找,当抵达最外层的全局作用域仍然没有找到,则会停止对标识符的搜索。

###2. 原型链

首先来看Number函数: Number()
原始类型的值主要是字符串、布尔值、undefined和null,它们都能被Number转成数值或NaN。

(1).普通值转换为
// 数值:转换后还是原来的值
Number(324) // 324

// 字符串:如果可以被解析为数值,则转换为相应的数值
Number('324') // 324

// 字符串:如果不可以被解析为数值,返回NaN
Number('324abc') // NaN

// 空字符串转为0
Number('') // 0

// 布尔值:true 转成1,false 转成0
Number(true) // 1
Number(false) // 0

// undefined:转成 NaN
Number(undefined) // NaN

// null:转成0
Number(null) // 0
思考: 
parseInt('42 cats') 
Number('42 cats') 

####2)对象的转换规则
规则是: Number方法的参数是对象时,将返回NaN,除非是包含单个数值的数组。

Number({a: 1}) // NaN
Number([1, 2, 3]) // NaN
Number([5]) // 5



思考:
Object.prototype.toString=function(){
    console.log('调用Object.ptototype.valueOf....');

Object.prototype.valueOf=function(){
    console.log('调用Object.ptototype.valueOf....');
    return 998;
}
var obj={
    msg: 'this is a test',
    valueOf: function(){
        return 2;
        //return '123';
        //return undefined;
        //return null;
        //return 'abc';
        //return '';
        //return {}; 返回对象
    }
};

x=Number(obj);
这里会输出什么?

Number背后对于对象的转换比较复杂:
1.调用对象自身的valueOf方法。如果返回原始类型的值,则直接对该值使用Number函数,不再进行后续步骤。

2.如果valueOf方法返回的还是对象,则改为调用对象自身的toString方法。如果toString方法返回原始类型的值,则对该值使用Number函数,不再进行后续步骤。

3.如果toString方法返回的是对象,就报错。
所以:

var obj = {x: 1};
Number(obj) // NaN
// 等同于
if (typeof obj.valueOf() === 'object') {
  Number(obj.toString());
} else {
  Number(obj.valueOf());
}
Number函数将obj对象转为数值。背后发生的操作,首先调用obj.valueOf方法, 结果返回对象本身;于是,继续调用obj.toString方法,这时返回字符串[object Object],对这个字符串使用Number函数,得到NaN

除此之外:
valueOf和toString方法,都是可以自定义的。

Number({
  valueOf: function () {
    return 2;
  }
})


Number({
  toString: function () {
    return 3;
  }
})


Number({
  valueOf: function () {
    return 2;
  },
  toString: function () {
    return 3;
  }
})

正式进入原型链:

    例子1:
    function Person(){
     }
    Object.prototype.sayHi=function(){
       console.log("我是Object原型对象的sayHi方法!");
    };
     Person.prototype.name="LXY";
     Person.prototype.age=22;
     Person.prototype.sayHi=function(){
       console.log(this.name+","+this.age+",");
     };
    var p1=new Person();
    var p2=new Person();
    console.log(p1.name);//'LYX'
     
    p1.sayHi();//'LXY,22'
    p2.sayHi();//'LXY,22'
    console.log(p1.sayHi===p2.sayHi);//true
    hasOwnProperty,isPrototypeOf, getPrototypeOf这三个方法:
        //测试某个对象是否是某个对象的原型
        console.log(Person.prototype.isPrototypeOf(p1)); //true
        console.log(Person.prototype.isPrototypeOf(p2));//true
         
        //ES6新增的一个方法:Object.getPrototypeOf() //返回某个对象的原型对象
         
        console.log(Object.getPrototypeOf(p1)); 
        console.log(Object.getPrototypeOf(p2));
         
        console.log( Object.getPrototypeOf(p2).constructor);//function Person(){ }
        console.log( Object.getPrototypeOf(p2).name);//"LXY"
        console.log( Object.getPrototypeOf(p2).age);//22
        //这两个方法都是返回Person { name="LXY", age=22, sayHi=function()}


    //使用hasOwnProperty()方法可以检测一个属性是否存在于对象本身,还是对象的原型中
         function Person(){
         }
         Person.prototype.name="人类";
         Person.prototype.age=150;
         Person.prototype.sayHi=function(){
           console.log(this.name+","+this.age+",");
         };
         var p1=new Person();
         var p2=new Person();
         p1.name="WFD";
      
关于原型链的图:

         //使用hasOwnProperty()方法可以检测一个属性是否存在于对象本身,还是对象的原型中
         console.log(p1.hasOwnProperty("name")); //true p1对象自己有name属性
         console.log(p2.hasOwnProperty("name")); //false p2对象自己m没有name属性,访问得到的是原型对象中的name属性
     
         delete p1.name; //删除p1对象的name属性
         console.log(p1.hasOwnProperty("name"));//false  
         //============================================
         /*
              使用hasOwnProperty()方法可以检测一个属性是否存在于对象本身,还是对象的原型中
            单独使用in运算符:通过对象,可以访问给定的属性时就返回true,无论该属性是在对象中还是原型对象中
         */
         console.log("name" in p1); //只要name属性可以访问到,就返回true
         组合两种方法,可以判断某个属性是否存在于原型对象中
         function hasPrototypeProperty(object,name){
            return !object.hasOwnProperty(name)&& (name in object);
            //第一个返回false,第二个返回true,则可以说明属性存在于原型对象中
         }
     ```    
         //==============================例子:
         var p1=new Person();
         var p2=new Person();
         p1.name="WFD";
         
         function hasPrototypeProperty(object,name){
          return !object.hasOwnProperty(name)&& (name in object);
          //第一个返回false,第二个返回true,则可以说明属性存在于原型对象中
         }
         var res1=hasPrototypeProperty(p1,'name');
         var res2=hasPrototypeProperty(p2,'name');
         console.log(res1);//false
         console.log(res2);//true


例子2:
//2.原型链查找,每一个访问对象的一个属性时就有一次原型链查找过程
   function Person(){
 
   }
   Person.prototype.name="LXY";
   Person.prototype.age=22;
   Person.prototype.sayHi=function(){
     console.log(this.name+","+this.age+",");
   };
 
   var p1=new Person();
   var p2=new Person();
   p1.name="WFD";
   
   console.log(p1.name);// 'WFD' 来自实例对象p1本身
   console.log(p2.name);// 'LXY'  来自原型对象
   p1.name=null;
   console.log(p1.name);//null
  //删除实例的name属性,从而又可以访问到原型对象的name属性
  delete p1.name;
  console.log(p1.name); // 'LXY'

对于有基于类的语言经验的开发人员来说, JavaScript 有点令人困惑 (如Java或C ++) ,因为它是动态的,并且本身不提供一个类实现.。(在ES2015/ES6中引入了class关键字,但只是语法糖,JavaScript 仍然是基于原型的)。

当谈到继承时,Javascript 只有一种结构:对象。每个对象都有一个私有属性(称为是[[Prototype]]), 它持有一个连接到另一个称为其 prototype 对象的链接。该原型对象具有一个自己的原型,等等,直到达到一个对象的 prototype 为 null。

根据定义,null 没有 prototype,并作为这个 prototype chain 中的最后一个环节。

Object是Function,Array,String,RegExp 的父类,Object是上帝类

 Object.prototype.sayHi=function(){
   console.log("Object原型对象里的sayHi!");
 };

 Function.prototype.sayHi=function(){
   console.log("Function原型对象里的sayHi!");
 };
 Array.prototype.sayHi=function(){
   console.log("Array原型对象里的sayHi!");
 };
String.prototype.sayHi=function(){
  console.log("String原型对象里的sayHi");
};
function Person(name){
   this.name=name;
   this.sayHi=function(){
     console.log("Person对象里的sayHi!");
   };
}
Person.prototype.sayHi=function(){
   console.log("Person原型对象里的sayHi!");
};

var s1="abc",
        fn1=function(){},
        arr=[],
        o1={},
        p1=new Person();
   
   arr.sayHi();//Array原型对象里的sayHi!
   s1.sayHi();//String原型对象里的sayHi!
   fn1.sayHi();//Function原型对象里的sayHi!
   o1.sayHi();//Object原型对象里的sayHi!

   
   p1.sayHi();//Person对象里的sayHi!  Person对象里的sayHi! 首先在Person对象p1上找到了  // p1依次删除 sayHi方法,再看看输出





   执行上面代码时,首先会在对象实例p1中查找属性sayHi方法,我们发现实例中不存在sayHi属性。然后我们转到p1内部指针[[Prototype]]指向的Person原型对象去寻找sayHi属性,结果是仍然不存在。这找不到我们就放弃了?开玩笑,我们这么有毅力。我们会再接着到p1原型对象的内部指针[[Prototype]]指向的Object原型对象中查找,这次我们发现其中确实存在sayHi属性,然后我们执行sayHi方法。发现了没有,这一连串的原型形成了一条链,这就是原型链。
     
  总结: 
     当我们访问对象的某个属性或者方法时,我们首先会在当前对象本身上找,如果找到,就使用这个值,整个查找过程结束,如果未找到,就会在对象的原型链中依次查找,如果在当前的原型中已经查找到所需要的属性,那么就会停止搜索,否则会一直向后查找原型链,直到原型链的结尾(这一点有点类似于作用域链),如果直到原型链结尾仍未找到,那么该属性就是undefined

作用域链和原型链的比较:
讲完了作用域链和原型链,我们可以比较一下。作用域链的作用主要用于查找标识符,当作用域需要查询变量的时候会沿着作用域链依次查找,如果找到标识符就会停止搜索,否则将会沿着作用域链依次向后查找,直到作用域链的结尾。而原型链是用于查找引用类型的属性,查找属性会沿着原型链依次进行,如果找到该属性会停止搜索并做相应的操作,否则将会沿着原型链依次查找直到结尾。

拓展__proto__ 和prototype:

huangtengfei/blog#11

参考链接:
http://dmitrysoshnikov.com/ecmascript/javascript-the-core/ JavaScript. The core.
http://www.ecma-international.org/ecma-262/5.1/#sec-4.3.5 ESCMA5官网
http://dmitrysoshnikov.com/tag/ecma-262-3/
creeperyang/blog#9
http://www.th7.cn/web/js/201503/88712.shtml
huangtengfei/blog#11

Web 安全

安全相关:

rel=noopener 解决了什么问题?

当你使用 target='_blank' 打开一个新的标签页时,新页面的 window 对象上有一个属性 opener,
它指向的是前一个页面的 window 对象,因此,后一个页面就获得了前一个页面的控制权,so 可怕!!

解决方案:

 <a href=""  target='_blank'    rel="noreferrer noopener">我是 a 链接</a>

demo 栗子

1. lozad 源码学习

加上了占位图片, 这个库还是很轻巧的,但是,目前还可能有一些兼容性问题,所有不太敢用于实际的项目中.
/*! lozad.js - v1.0.2 - 2017-09-10
* https://github.com/ApoorvSaxena/lozad.js
* Copyright (c) 2017 Apoorv Saxena; Licensed MIT */

// 结构定义类似于这样


// (function(global, factory){
//     console.log('全局对象为:', global);
//     global.lazyload = factory();
//     console.log(global.lazyload);
//     console.log(global.lazyload());
// }(this,
//   function(){
//     var test_lazyload = function() {
//       console.log('我是图片懒加载方法....');
//     };
//     return test_lazyload;
// }));



// 定义的是立即函数
(function (global, factory) {
  //判断执行环境,是浏览器环境还是Node环境
  /*
    typeof define === 'function' && define.amd 
    // If 'define' is not undefined and it is a function and 'amd' (asynchronous module definition) 
      is also defined then the code assumes that require.js is in play.
      检测当前环境是否处于 require.js 的环境
  
  */
	typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
	typeof define === 'function' && define.amd ? define(factory) :
  (global.lozad = factory());
  //将参数里的工厂方法执行结果返回给全局的lozad变量
}(this, (function () { 'use strict';



// 定义assign方法, 存在则是用ES6自带的方法,否则,自定义一个对象赋值的方法.
// _extends(target, [source1, source2.... sourcen] );  将源对象的自身属性赋值给目标对象, 后面的对象属性会覆盖前面的对象的属性
var _extends = Object.assign || 
              function (target) { 
                for (var i = 1; i < arguments.length; i++) {
                   var source = arguments[i];
                    for (var key in source) {
                      // 如果属性是对象自身的,则赋值给目标对象 , 可以直接对象.hasOwnProperty -- 避免原型链的查找过程,原型链的查找很费时间, 参数也可以使用ES6剩余参数的形式, ...args 
                     if (Object.prototype.hasOwnProperty.call(source, key)) {
                        target[key] = source[key];
                      }
                    }
                }
                return target;
              };

var defaultUrl  = 'https://www.google.co.jp/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png';
var loadImageAsync = function loadImageAsync(element) {
  console.log(element);
  console.log(defaultConfig);
  var img = new Image();
  img.onload = function() {
    // 使用目标图片
    console.log('加载成功....');
    element.src = element.dataset.src;
  };
  img.onerror = function () {
    // 加载失败使用的error图片
    console.log('加载失败....');
    element.src = defaultConfig.errorImgUrl;
  }
  img.src = element.dataset.src;
  element.src = defaultConfig.holdImgUrl;
};
// 设置默认配置  考虑怎么加一个默认的占位图,在所需的图片加载完成前,先使用占位图,待图片完全加载后,再赋值.             
var defaultConfig = {
  rootMargin: '0px',
  threshold: 0, // 阈,(入口,开始值
  load: function load(element) {
    console.log( element);
    // 新的H5元素支持直接取dataset.src // 元素以data-shuxing 的形式存在时,可以直接这样 Node.dataset.shuxing 设置或者取值
    // element.src = element.dataset.src;
    loadImageAsync(element);
  },
  holdImgUrl: '',
  errorImgUrl: '',
};

//设置成已经加载
function markAsLoaded(element) {
  element.dataset.loaded = true;
}
// 判断图片是否已经加载
var isLoaded = function isLoaded(element) {
  return element.dataset.loaded === 'true';
};

// 内部选择方法
// https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver
// IntersectionObserver接口 (为开发者提供了一种可以异步监听目标元素与其祖先或视窗(viewport)交叉状态的手段。祖先元素与视窗(viewport)被称为根(root)。
var onIntersection = function onIntersection(load) {
  return function (entries, observer) {
    console.log('entries:', entries);
    console.log('observer:', observer);
    entries.forEach(function (entry) {
      console.log(entry);
      if (entry.intersectionRatio > 0) {
        console.log('Loaded new Imgs....');
        observer.unobserve(entry.target);
        load(entry.target);
        markAsLoaded(entry.target);
      }
    });
  };
};

var lozad = function () {
  // 去的图片懒加载的选择器,默认为参数的第一个参数,没有则使用默认值
  var selector = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '.lozad';
  // 可选参数,为一个对象、没传则设置成空对象
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

  // 设置配置参数对象
  var _defaultConfig$option = _extends({}, defaultConfig, options),
      rootMargin = _defaultConfig$option.rootMargin,
      threshold = _defaultConfig$option.threshold,
      load = _defaultConfig$option.load;
  // 设置占位图和错误图
  defaultConfig.holdImgUrl = _defaultConfig$option.holdImgUrl;
  defaultConfig.errorImgUrl = _defaultConfig$option.errorImgUrl;

  // 设置一个空值
  var observer = void 0;

  // 如果浏览器支持 IntersectionObserver
  if (window.IntersectionObserver) {
    // 创建观察者对象
    /**
     * 参数1: callback, 是个必选参数,当有相交发生时,浏览器便会调用
     * 参数2: options 可选参数 对象里面的三个属性也是可选的
     */
    observer = new IntersectionObserver(onIntersection(load), {
      rootMargin: rootMargin,
      threshold: threshold
    });
    console.log('观察者对象为:', observer);
  }

  return {
    // 返回一个对象,这个对象包含observe 方法
    observe: function observe() {
      // 返回还没有被加载的图片
      var elements = [].filter.call(document.querySelectorAll(selector), function (element) {
        return !isLoaded(element);
      });
      // observer 不存在了, 加载所有还没加载的图片
      if (!observer) {
        elements.forEach(function (element) {
          load(holdImgUrl, errorImgUrl, element);
          markAsLoaded(element);
        });

        return;
      }
      // 监听每一个待加载的图片
      elements.forEach(function (element) {
        observer.observe(element);
      });
    }
  };
};

// 返回lozad 方法
return lozad;

})));

Linux

Linux 下用 ssh 上传和下载文件

ssh上传文件:
scp file username@hostIP:source-file
例:
scp file.sql [email protected]:/var/www/home    上传file.sql 到服务器的  /var/www/home 
上传文件夹:
scp -r /Users/xueqi/Documents/word_cup_2018/main/  [email protected]:/root/nginx/

ssh下载文件:
scp username@hostIP:source-file-dir target-dir
例:
scp [email protected]:/var/www/16bing/test.sql    /Users/coldice/Desktop   (远程目录     本地目录)
然后输入你服务器的 ssh 登陆密码,等待进度条,下载完成。

ServiceWorker

https://www.zybuluo.com/yqlar/note/908619

#Service Worker


1、Service Worker 简介

在页面中注册并安装成功后,运行于浏览器后台,不受页面刷新的影响,可以监听和截拦作用域范围内所有页面的 HTTP 请求。


###1.1 Service Worker 特点

  • 网站必须使用 HTTPS。除了使用本地开发环境调试时(如域名使用 localhost)
  • 运行于浏览器后台,可以控制打开的作用域范围下所有的页面请求
  • 单独的作用域范围,单独的运行环境和执行线程
  • 不能操作页面 DOM。但可以通过事件机制来处理

###1.2 兼容性
https://caniuse.com/#feat=serviceworkers

'https://lzw.me/wp-content/uploads/2017/03/sw-caniuse.png'


###1.3 PWA
谷歌给以 Service Worker API 为核心实现的 web 应用取了个高大上的名字:Progressive Web Apps(PWA,渐进式增强 WEB 应用),并且在其主要产品上进行了深入的实践。那么,符合 PWA 的应用特点是什么?以下为来自谷歌工程师的解答。

Progressive Web Apps 是:

  • 渐进增强 – 能够让每一位用户使用,无论用户使用什么浏览器,因为它是始终以渐进增强为原则。
  • 响应式用户界面 – 适应任何环境:桌面电脑,智能手机,笔记本电脑,或者其他设备。
  • 不依赖网络连接 – 通过 Service Workers 可以在离线或者网速极差的环境下工作。
  • 类原生应用 – 有像原生应用般的交互和导航给用户原生应用般的体验,因为它是建立在 app shell model 上的。
  • 持续更新 – 受益于 Service Worker 的更新进程,应用能够始终保持更新。
  • 安全 – 通过 HTTPS 来提供服务来防止网络窥探,保证内容不被篡改。
  • 可发现 – 得益于 W3C manifests 元数据和 Service Worker 的登记,让搜索引擎能够找到 web 应用。
  • 再次访问 – 通过消息推送等特性让用户再次访问变得容易。
  • 可安装 – 允许用户保留对他们有用的应用在主屏幕上,不需要通过应用商店。
  • 可连接性 – 通过 URL 可以轻松分享应用,不用复杂的安装即可运行。

##2、 Service Worker 的生命周期

install -> installed -> actvating -> Active -> Activated -> Redundant


##3、 使用 Service Worker

首先要注意如下两点:

A. 使用 HTTPS 访问

B. 基础知识了解:

Promise: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
Promise 是 ES6 新增的规范,主要用于 javasCript 的异步处理。

Fetch API: https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API
Fetch API 用于 HTTP 请求处理,可以替代 XMLHttpRequest 实现异步请求(ajax),但功能上更为完善。

Cache API: https://developer.mozilla.org/zh-CN/docs/Web/API/Cache
Cache API 用于对 HTTP 请求进行缓存管理,是在 ServiceWorker 的规范中定义的,一般也跟 ServiceWorker 一起使用,是实现离线应用的关键。但是 Cache API 又不依赖于 Service Worker,可以单独使用


###3.1、 注册
在网站页面上注册实现 Service Worker 功能逻辑的脚本。例如注册 /sw/sw.js 文件,参考代码:

if ('serviceWorker' in navigator) {

    navigator.serviceWorker.register('/sw/sw.js', {scope: '/'})
    
        .then(registration => console.log('ServiceWorker 注册成功!作用域为: ',registration.scope))
        
        .catch(err => console.log('ServiceWorker 注册失败: ', err));
}

注意:
Service Worker 的注册路径决定了其 scope(范围) 默认作用范围。示例中 sw.js 是在 /sw/ 路径下,这使得该 Service Worker 默认只会收到 /sw/ 路径下的 fetch 事件。如果存放在网站的根路径下,则将会收到该网站的所有 fetch 事件。
如果希望改变它的作用域,可在第二个参数设置 scope 范围。示例中将其改为了根目录,即对整个站点生效。
另外应意识到这一点:Service Worker 没有页面作用域的概念,作用域范围内的所有页面请求都会被当前激活的 Service Worker 所监控。


###3.2、 安装

// 缓存文件的版本
var CACHE_VERSION = 'v1.0-2017/9/20-004';   

// 需要初次进入立即缓存的文件
var CACHE_FILES = [                     
    '/index.html',
    // '/static/css/app.css',
    // '/static/js/app.js',
    // '/static/js/1.js',
    // '/static/js/9.js',
];
// 监听 install (安装)事件
self.addEventListener('install', function (event) {

// 此处 event.waitUntil 是等待 CACHE_FILES 中的文件全部加载完毕之后再安装
    event.waitUntil(
    
        // 打开对应名称的缓存
        caches.open(CACHE_VERSION).then (function (cache) {
        
            // 将列表中的所有文件都缓存起来
            // 如果没有全部缓存完毕,则视为安装失败
            return cache.addAll(CACHE_FILES);
        })
    );
});

可以看到,示例中在文件 sw.js 内监听了 install 事件。当 sw.js 被安装时会触发 install 事件,监听该事件可执行安装时要做的事情。示例中是缓存用于离线时使用的静态资源。

需要注意的是,只有 CACHE_FILES 中的文件全部安装成功,Service Worker 才会认为安装完成。否则会认为安装失败,安装失败则进入 redundant (废弃)状态。所以这里应当尽量少地缓存资源(一般为离线时需要但联网时不会访问到的内容),以提升成功率。

安装成功后,即进入等待(waiting)或激活(active)状态。在激活状态可通过监听各种事件,实现更为复杂的逻辑需求。具体参见后文事件处理部分。

event.waitUntil 相关文档解释
Cache相关文档


###3.3、 Service Worker 的更新
如果 sw.js 文件的内容有改动,当访问网站页面时浏览器获取了新的文件,它会认为有更新,于是会安装新的文件并触发 install 事件。但是此时已经处于激活状态的旧的 Service Worker 还在运行,新的 Service Worker 完成安装后会进入 waiting 状态。直到所有已打开的页面都关闭,旧的 Service Worker 自动停止,新的 Service Worker 才会在接下来打开的页面里生效。

如果希望在有了新版本时,所有的页面都得到及时更新怎么办呢?

可以在 install 事件中执行 skipWaiting 方法跳过 waiting 状态,然后会直接进入 activate 阶段。接着在 activate 事件发生时,通过执行 clients.claim 方法,更新所有客户端上的 Service Worker。示例:

// 安装阶段跳过等待,直接进入 active

self.addEventListener('activate', function (event) {

  var cacheWhitelist = [CACHE_VERSION];
  
  event.waitUntil(
  
    caches.keys().then(keyList => {
      return Promise.all(keyList.map(key => {
      
        if (cacheWhitelist.indexOf(key) === -1) {
          return caches.delete(key);
        }
        
      }));
    })
);
});

###3.4、 fetch 事件
在安装过程中我们实现了资源缓存,安装完成后则进入了空闲阶段,此时可以通过监听各种事件实现各种逻辑。

当浏览器发起请求时,会触发 fetch 事件。

Service Worker 安装成功并进入激活状态后即运行于浏览器后台,可以通过 fetch 事件可以拦截到当前作用域范围内的 http/https 请求,并且给出自己的响应。结合 Fetch API ,可以简单方便地处理请求响应,实现对网络请求的控制。

这个功能是十分强大的。

参考下面的示例,这里实现了一个缓存优先、降级处理的策略逻辑:监控所有 http 请求,当请求资源已经在缓存里了,直接返回缓存里的内容;否则使用 fetch API 继续请求,如果是 图片或 css、js 资源,请求成功后将他们加入缓存中;如果是离线状态或请求出错,则降级返回预缓存的离线内容。

self.addEventListener('fetch', function (event) {

  var url = event.request.url;
  // 接口过滤条件
  // || url.indexOf('https://www.91kds.net/get_data') !== -1
  // 'https://cdn.bootcss.com/vue/2.4.2/vue.min.js',
  // 'https://h5cs.yingshiq.com/',

  // 各类请求过滤条件
  var val = (
              url.indexOf('http://127.0.0.1:8887/') !== -1
              || url.indexOf('https://img.yingshiq.com/') !== -1
              || url.indexOf('https://cdn.bootcss.com/') !== -1
              || url.indexOf('https://h5cs.yingshiq.com/') !== -1
              || url.indexOf('https://vodapics.91kds.com') !== -1
            );

  if (val) {
  
    //
    event.respondWith(
      caches.match(event.request).then(function (response) {
          var reqClone = event.request.clone();
          
          //
          if (response) {
            return response;
          } else {
          
            // 请求线上资源
            return fetch(reqClone).then(function (res) {
            
              //
              var resClone = res.clone();
              caches.open(CACHE_VERSION).then(function (cache) {
              
                // 缓存从线上获取的资源
                cache.put(reqClone, resClone);
              });
              
              return res;
            })
          }
        }
      )
    );
  }
});

###3.5、 push 事件

push 事件是为推送准备的。不过首先你需要了解一下 Notification API 和 PUSH API(相关链接见后文)。

通过 PUSH API,当订阅了推送服务后,可以使用推送方式唤醒 ServiceWorker 以响应来自系统消息传递服务的消息,即使用户已经关闭了页面。

推送的实现有两步:

不同浏览器需要用不同的推送消息服务器。以 Chrome 上使用 Google Cloud Messaging 作为推送服务为例,第一步是注册 applicationServerKey(通过 GCM 注册获取),并在页面上进行订阅或发起订阅。每一个会话会有一个独立的端点(endpoint),订阅对象的属性(PushSubscription.endpoint) 即为端点值。将端点发送给服务器后,服务器用这一值来发送消息给会话的激活的 Service Worker (通过 GCM 与浏览器客户端沟通)。

###3.6、 online/offline 事件

当网络状态发生变化时,会触发 online 或 offline 事件。结合这两个事件,可以与 Service Worker 结合实现更好的离线使用体验,例如当网络发生改变时,替换/隐藏需要在线状态才能使用的链接导航等。

下面是一个监听 offline 的示例:

self.addEventListener('offline', function() {
    Notification.requestPermission().then(grant => {
        if (grant !== 'granted') {
            return;
        }

        const notification = new Notification("Hi,网络不给力哟", {
            body: '您的网络貌似离线了,不过访问过的页面还可以继续打开~',
            icon: '我是图标'
        });

        notification.onclick = function() {
            notification.close();
        };
    });
});

something new

近期可看的:
WebAssembly:
1. https://developer.mozilla.org/en-US/docs/WebAssembly
// WebAssembly是一种可在现代Web浏览器中运行的新类型的代码 - 它是一种低级汇编式语言,具有紧凑的二进制格式,可以接近本机的性能运行
http://webassembly.org/
https://blog.techbridge.cc/2017/06/17/webassembly-js-future/ 👍
2. 影子Dom:
https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/%E5%BD%B1%E5%AD%90_DOM
https://aotu.io/notes/2016/06/24/Shadow-DOM/index.html
3.新的打包工具:
parceljs
https://parceljs.org/
https://github.com/parcel-bundler

npm

###npm 一些小的工具包的源码学习:
repeat-string
(https://github.com/jonschlinkert/repeat-string)

 /*!
 * repeat-string <https://github.com/jonschlinkert/repeat-string>
 *
 * Copyright (c) 2014-2015, Jon Schlinkert.
 * Licensed under the MIT License.
 */

'use strict';

/**
 * Results cache
 */

var res = '';
var cache;

/**
 * Expose `repeat`
 */

module.exports = repeat;

/**
 * Repeat the given `string` the specified `number`
 * of times.
 *
 * **Example:**
 *
 * ```js
 * var repeat = require('repeat-string');
 * repeat('A', 5);
 * //=> AAAAA
 * ```
 *
 * @param {String} `string` The string to repeat
 * @param {Number} `number` The number of times to repeat the string
 * @return {String} Repeated string
 * @api public
 */

function repeat(str, num) {
  if (typeof str !== 'string') {
    throw new TypeError('expected a string');
  }

  // cover common, quick use cases
  if (num === 1) return str;
  if (num === 2) return str + str;

  var max = str.length * num; 
  if (cache !== str || typeof cache === 'undefined') {
    cache = str;
    res = '';
  } else if (res.length >= max) {
    return res.substr(0, max);
  }
  while (max > res.length && num > 1) {
    if (num & 1) { // 是奇数, 奇数与奇数 & 为奇数, 与偶数 2 & 1  = 0  位操作的目的是效率更高
      res += str;
    }

    num >>= 1; // num / 2 ( 等于num整除2 )
    str += str;
  }

  res += str;
  res = res.substr(0, max);
  return res;
}

参考链接:
https://stackoverflow.com/questions/4115045/ruby-what-does-the-snippet-num-1-0-exactly-do

Fullscreen API:全屏操作

 /*
      浏览器全屏API:https://javascript.ruanyifeng.com/htmlapi/fullscreen.html#toc1
      note: :32 Failed to execute 'requestFullscreen' on 'Element': API can only be initiated by a user gesture.
      出于安全考录必须通过用户一个操作才能触发全屏/取消全屏操作,比如 点击按钮后触发全屏
   */
  // 开启全屏
  function launchFullscreen(element) {
    if(element.requestFullscreen) {
      element.requestFullscreen();
    } else if(element.mozRequestFullScreen) {
      element.mozRequestFullScreen();
    } else if(element.msRequestFullscreen){
      element.msRequestFullscreen();
    } else if(element.webkitRequestFullscreen) {
      element.webkitRequestFullScreen();
    }
    // 当上面的的代码执行后,第一次返回的全屏元素为空, 如果已经在全屏的状态的时候,就会返回当前全屏的元素
    var fullscreenElement =
        document.fullscreenElement ||
        document.mozFullScreenElement ||
        document.webkitFullscreenElement;
    console.log('全屏状态:', fullscreenElement);
  }
  // launchFullscreen(document.documentElement);
  // 点击文档开启全屏
  document.onclick = function() {
    launchFullscreen(document.getElementById("my-video"));
  };
    // 关闭全屏
  function exitFullscreen() {
    if (document.exitFullscreen) {
      document.exitFullscreen();
    } else if (document.msExitFullscreen) {
      document.msExitFullscreen();
    } else if (document.mozCancelFullScreen) {
      document.mozCancelFullScreen();
    } else if (document.webkitExitFullscreen) {
      document.webkitExitFullscreen();
    }
  }

  // 双击文档关闭全屏
  document.ondblclick= function() {
    exitFullscreen();
  };

  // 全屏事件:
  // fullscreenchange事件:浏览器进入或离开全屏时触发。
  // fullscreenerror事件:浏览器无法进入全屏时触发,可能是技术原因,也可能是用户拒绝。
  document.addEventListener("fullscreenchange", function( event ) {
    console.log(e);
    if (document.fullscreenElement) {
      console.log('进入全屏');
    } else {
      console.log('退出全屏');
    }
  });
    
// 全屏状态的CSS:
:-webkit-full-screen {
  /* properties */
}

:-moz-full-screen {
  /* properties */
}

:-ms-fullscreen {
  /* properties */
}

:full-screen { /*pre-spec */
  /* properties */
}

:fullscreen { /* spec */
  /* properties */
}

/* deeper elements */
video:-webkit-full-screen {
  width: 100%;
  height: 100%;
}

参考链接:
https://javascript.ruanyifeng.com/htmlapi/fullscreen.html#toc1
https://developer.mozilla.org/en-US/docs/Web/API/DocumentOrShadowRoot/fullscreenElement

JS事件循环(event-loop)

whatwg-event-loop规范
microtask-queue

为了协调事件、用户交互、脚本、渲染、网络等,用户代理必须使用本节中描述的事件循环。每个代理都有一个关联的事件循环,这是该代理所独有的。

macrotasks(宏任务): setTimeout ,setInterval, setImmediate,requestAnimationFram,I/O ,UI渲染

microtasks(微任务): Promise, process.nextTick, Object.observe, MutationObserver

macrotask whatwg 规范里称为 task queues, 一个 event-loop 中可能有一个或者多个 task queues ,
有一个 microtask 队列(不清楚是否只有一个), 微任务队列不是任务队列

举个🌰:

setTimeout(function set1(){
  console.log('s1');
},0);

setTimeout(function set2(){
  console.log('s2');
},0);

new Promise(function p1(resolve){
  console.log('p1');
  resolve();
  console.log('p1-1');
}).then(function p2(){
  console.log('p2');
});

console.log(6);
Promise.resolve().then(function p3() {
  console.log('p7')
})

// 输出:p1, p1-1, 6, p2, p7, s1, s2

image

更复杂一些的🌰:

 setTimeout(function(){
  console.log('s1');
  Promise.resolve().then(function() {
    console.log('p1')
  })
},0);

setTimeout(function(){
  console.log('s2');
  Promise.resolve().then(function() {
    console.log('p2')
  })
},0);

new Promise(function(resolve){
  console.log('p3');
  resolve();
  console.log(4);
}).then(function(){
  console.log('p4');
  Promise.resolve().then(function() {
    console.log('p5')  
  })  
});

console.log(6);
Promise.resolve().then(function() {
  console.log('p7')
})
// 输出:  p3, 4, 6, p4, p7, p5, s1, p1, s2, p2

总结:

  1. 执行主线程代码
    1.1 遇到微任务,就加到微任务队列中.
    1.2 遇到宏任务,就加到宏任务sets 中.
  2. 当js执行栈为空时, 就检查微任务队列,如果队列不为空的话,依次执行完队列里所有的微任务。否则跳到3
    2.1 当此时执行的微任务里面又 创建了微任务或者宏任务的话
    2.2 将宏任务加到宏任务队列,将微任务加到微任务队尾
    2.3 当微任务队列全部为空时,微任务执行完毕。
  3. 检测宏任务队列,如果不为空的话,取出最老的(即队头那个)那个宏任务,执行完成 ,移出队列
  4. 开始下一轮循环( 跳到1 )

(注:
Task queues are sets, not queues, because step one of the event loop processing
model grabs the first runnable task from the chosen queue, instead of dequeuing the first task.)

PS: event-loop 还是比较复杂的,规范看起来有难度(英文加涉及到的概念太多)目前没看完,后续慢慢学习。
因为 Node 里面的event-loop 实现机制不太一样,上面的解释对于Node里的event-loop不适合

参考:
从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理
difference-between-microtask-and-macrotask-within-an-event-loop-context
理解事件循环二(macrotask和microtask)

各种坑......

微信小程序 里面 atob 方法不存在, 同时也没有错误提示, 导致base64解密失败:

解决办法: 引入第三方的实现:

  1. https://github.com/dankogai/js-base64 这个库存在问题,部分数据解密失败(可能有的数据转换不了)
  2. https://github.com/MaxArt2501/base64-js/blob/master/base64.js 这个解决了问题
    有空还是得深入了解原理
  3. cover 文字的换行问题 , 默认是不换行的,
    官方文档-换行问题
 wrap-line {
    word-break: break-all; 
    word-wrap: break-word;
    white-space: pre-line;
 }
行高还是有问题

js深拷贝

先附上结论:

 Object.assign(),  Array的 slice  concat 方法  对于引用类型数据都是浅拷贝
 JSON.strigify  JSON.parse 是深拷贝

此段代码来自

JSON.strigify和JSON.parse 🌰

    
    // 例1
    var source = { name:"source", child:{ name:"child" } } 
    var target = JSON.parse(JSON.stringify(source));
    target.name = "target";  //改变target的name属性
    console.log(source.name); //source 
    console.log(target.name); //target
    target.child.name = "target child"; //改变target的child 
    console.log(source.child.name); //child 
    console.log(target.child.name); //target child
    
    
    //例2
    var source = { name:function(){console.log(1);}, child:{ name:"child" } } 
    var target = JSON.parse(JSON.stringify(source));
    console.log(target.name); //undefined
    
    
    //例3
    var source = { name:function(){console.log(1);}, child:new RegExp("e") }
    var target = JSON.parse(JSON.stringify(source));
    console.log(target.name); //undefined
    console.log(target.child); //Object {}
    
    优点:
     /*
       * 1. 这两个方法是 ES5中引入的新的类型(支持的浏览器为IE8+) 兼容性较好
       * 2. 使用起来简单
       * 3. 可以满足基本的深拷贝需求,而且能够处理JSON格式能表示的所有数据类型
      */
    
     缺点:
      /*
       * 1. 不能拷贝方法,会直接忽略属性为方法的属性
       * 2. 不能拷贝正则,正则属性会变成一个空对象
       * 3. 如果元素是一个对象,则拷贝后此对象的__proto__ 上的constructor 属性会丢失,
       *  会变成 Object
       * 4. 无法处理对象存在循环引用的情况(JSON.parse方法对于存在循环引用的对象会报错,不能正确准换)
      */

Css 相关的

一些网站:

html5rocks-tutorials
html5rocks.com
Capturing Audio & Video in HTML5
simpl.info

requestAnimationFrame

链接:
[细说Web API中的Blob](https://blog.csdn.net/xuchaobei123/article/details/78244409) 

图片:

![](http://5f5798ab-970a-4eec-9314-b80402b89605.jpg@200w_200h.jpg)

MDN上说明

 style: 
.box {
  width: 200px;
  height: 200px;
  background: red;
  position: absolute;
}

一个div无限左右滑动的动画:

   // 使用这个API,一旦页面不处于浏览器的当前标签,就会自动停止刷新。这就节省了CPU、GPU和电力。
   // 兼容性处理
      window.requestAnimFrame = (function(){
        return  window.requestAnimationFrame       ||
                window.webkitRequestAnimationFrame ||
                window.mozRequestAnimationFrame    ||
                function(callback){
                  window.setTimeout(callback, 1000 / 60); 
                  // 回调函数执行的时间约等于浏览器的刷新频率, 一般浏览器的刷新频率在1s 60次, 即回调函数约会1s执行60次, 具体的值取决于浏览器的刷新频率, 这样可以节省性能
                };
      })();

      var e = document.querySelector(".box");
      var flag = true;
      var count = 0;
      var left = 0;
      function render() {
        left == 0 ? flag = true : left == 300 ? flag = false : '';
        flag ? e.style.left = ` ${left++}px` :
              e.style.left = ` ${left--}px`;
      }

      function render2() {
        left == 0 ? flag = true : left == 600 ? flag = false : '';
        flag ? ((e.style.left = ` ${left++}px`) && (e.style.bottom = ` ${left++}px`)) :
              ((e.style.left = ` ${left--}px`) && (e.style.bottom = ` ${left--}px`));
      }

      (function animloop(stmp) {
          count ++ ;
          render();
          console.log(`动画执行:${count}----${stmp}`);
          requestAnimFrame(animloop); // 浏览器在执行下一次从绘前会调用那个回调函数
      })();

cancelAnimationFrame方法: 用于取消重绘。

     window.cancelAnimationFrame(requestID);
    它的参数是requestAnimationFrame返回的一个代表任务ID的整数值。

如何停止动画?
当达到某个条件时, 我们需要停止动画, 只需要在符合某个条件, 变不再执行requestAnimationFrame即可

    var gid = 0;
    gid =  requestAnimationFrame(callback);  // 这个gid是递增的
    if (count === 100) { // 执行到100 次时,停止动画,清除requestAnimationFrame 操作(因为它会定时调用我们的回调函数)
            // window.cancelAnimationFrame(gId);
            return;
      }

参考链接:
阮一峰-requestAnimationFrame
张旭鑫-CSS3动画那么强,requestAnimationFrame还有毛线用?
更加健壮的polyfill

 (function() {
    var lastTime = 0;
    var vendors = ['webkit', 'moz'];
    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
        window.cancelAnimationFrame =
          window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
    }

    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function(callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function() { callback(currTime + timeToCall); },
              timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };

    if (!window.cancelAnimationFrame)
        window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
        };
}());

笔记

一些知识的笔记

书: 你不知道的js

   JavaScript       型:
    null、undefined、boolean、number、string、object  symbol,
    其中, object 称为引用数据类型, 其它称为简单数据类型

    检测null: 
    由于 typeof null = "object"
    所以使用:
     typeof null === "object" && !null   返回true, 所以可以用这种方式来检查 null

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.