Vue.js源码分析:通过setter内嵌入函数来监听数据的变化

// 这个名为data的变量类同于我们在创建Vue实例的时候给传进去的data对象
const data = {
  msg: '这里的信息并不重要,因为我们要演示的是Vue.js是如何监听数据的变化的'
}
observe(data) // 函数会在下面定义
data.msg = '数据发生了变化'
function callback(){
  console.log('----------callback函数开始----------')
  console.log('我会在任何在上面定义好了的变量data下的值发生变化前运行')
  console.log('----------callback函数结束----------')
}

function observe(data){
  // 确保data存在以及data是合法的对象,以及
  // reactivate里面会调用observe,这样可以
  // 一层一层遍历所有data下的非原始数据类型的值
  if(!data || typeof data !== 'object') return
  // 遍历data变量里面所有可枚举的值
  Object.keys(data).forEach(function(key){
    reactivate(data, key, data[key]) // 后面会定义
  })
}

function reactivate(data, key, val){
  // 柯里函数,遍历掉data里面可能有的对象以及数组等,
  // 在这个示例里面我们没有搞玩得那么复杂
  observe(val)
  // 在JavaScript里面,所有的东西都是“对象”
  // 包括各种原始数据类型的值
  // 所有的“对象”都可以通过.defineProperty
  // 方法来设置新属性
  Object.defineProperty(data, key, {
    enumerable: true         // 可枚举
    , configurable: false    // 不能再被define
    , get: function() { return val }
    , set: function(newVal) { 
      callback()
      val = newVal
    }
  })
}

// 你可以试试运行如下代码
// data.msg = '我有setter,我是reactive的'
// data.newlyCreated = '我没有setter,我不是reactive的'

选择开源前端框架的那些事

  1. 确认自己的需求
  2. 不要听意见
  3. 参考数据
    1. 流行程度:github星数
      1. 注意:
        1. 中国程序员人数技术大,他们偏好对github影响很大
        2. 一个框架的github星数跟(你工作地区对它的)市场需求量没有必然联系。一般来说,生产中经常会用比较成熟的技术,但不一定是github上流行的技术,比如jQuery
    2. 人力市场需求:到招聘平台上以你考虑的框架的名字作为关键词,看该框架被提及的次数
    3. 项目资金来源:比如React是Facebook的,Angular是Google的,Vue.js是赞助商的
      1. 注意:资金稳定只能肯定项目会存在,但是流不流行,好不好用,性能好不好,与之无直接联系
    4. API的稳定性:Angular 1.0与2.0变化较大
    5. Docs的条理性:自己去看吧
    6. 社区的活跃度:新插件的数量,插件的github星数,社区里问题有没有得到解答
    7. 框架的性能:看benchmark(比如“rawgit.com”的,请自行甄别),看评测,或看源码自行分析

本人选框架的方法很简单,项目官网是否符合我的审美。:)

JavaScript知识拾遗(1-2):MouseEvent、isArray()

  1. MouseEvent的mouseover/mouseout(前者)与mouseenter/mouseleave(后者)的对比
    • 前者兼容性更好
    • 前者会冒泡而且会捕获子元素的事件,后者不
  2. isArray()
    if(Array.isArray([true])){
      // doing nothing
    }else{
      Array.prototype.isArray = function(obj){
        if(typeof obj === 'object'){
          return Object.prototype.toString.call(obj) === '[object Array]'
        }else return false
      }
    }

JavaScript面试题(36):逻辑运算符的与跟非的返回值

// 只要“||”前为falsy,返回“||”后面的值
// 只要“||”前为truthy,返回“||”前面面的值
// 只要“&&”前为falsy,返回“||”前面的值
// 只要“&&”前为truthy,返回“||”后面面的值
// “||”跟“&&”都遵从短路原则,也就是,只要确定前面的值,就可以确定返回值
// 这样处理可以节约计算资源,毕竟
// 或“||”的含义是其一为真即为真,否则为假
// 与“&&”的含义是两者为真才为真,否则为假
// 另外,
// “&&”先于“||”执行,同类运算符的话,从左到右执行
{
function stepper(stepMe){
  stepMe.forEach( item => {
    let result = eval(item)
    console.log(`${item} ===> ${result}`)
  } )
}

const OR = [
  "1 || 2"
  , "0 || 1"
  , "false || false"
  , "0 || 0"
  , "false || 0"
  , "NaN || NaN"
  , "NaN || null"
  , "null || NaN"
  , "null || null"
]

const AND = [
  "false && false"
  , "false && 0"
  , "0 && false"
  , "NaN && false"
  , "null && false"
  , "null && NaN"
  , "NaN && null"
]

stepper(OR)
stepper(AND)
}
// “&&”优先于“||”
console.log(1 || 'string' && 2)
// 上面的代码等于
// const andFirst = 'string' && 2
// console.log(1 || result)
console.log(0 || 1 || 2) // 1, 略
console.log(1 && 2 && 3) // 3, 略

via 孟坤博客via穆乙

JavaScript的非主流平台

除了浏览器,Node.js这些主流JavaScript平台,我们还有:

好玩的前端(3):TamperMonkey(JavaScript本地注入)

官方表示没有很谦虚:

Tampermonkey is the most popular userscript manager, with over 10 million users. It’s available for Chrome, Microsoft Edge, Safari, Opera Next, and Firefox.

TamperMonkey有正经的社区,还有不正经的社区,如Sleazy Fork(一个专门分享成人网站脚本的社区)。

如果你之前没有想过给网站注入JavaScript,那么TamperMonkey会给你打开一道新世界的大门。如果你不满足于TamperMonkey,不妨看看Chrome Extension,它将给你更多的浏览器APIs。

我的JavaScript最初就是在给网站写脚本的时候学的

JavaScript Quality Code (Lint and Test) Essentials

  1. Ideally, Quality Codes are codes that are
    1. readable & maintainable by other programmer
    2. reusable for other projects
    3. bug-free
  2. For Readability & Maintainability
    1. Use Consistent Formatting and Logical Naming convention
    2. Practice a team coding convention and automate the enforcement
      1. Avoid using: too many rules, needlessly strict rules, overly pedantic rules
    3. Use structures and naming to convey the intent of the codes. Only use comments when it is needed badly
    4. Modularize
    5. Solution: EditorConfig, ESLint (jump start with AirbnbGoogle, or Feross’ Style Guide, and customize it)
  3. For Reusability
    1. Do Functional Programming if possible
    2. Modularize components
  4. For Bug-Free > Code Testing
    1. Unit Testing (单元测试): Testing a small piece of non-vendor code, say a module, usually with a simulated dependencies
    2. Integration Testing (集成测试): Unit Testing with APIs, UIs and result, usually cross-units
    3. Functional Testing (功能测试): Testing focusing on the user interface against the specification (business logics)
    4. System Testing (系统测试): Run the app on a production-like environment. It’s may be a Stress Test (for reliability), Load Tests (for scalability), a Security Scan and UX (User Experience) Test, Compatibility Test.
    5. Regression Test (回归测试): Rerunning Unit and Integration tests after bugs found in a feature upgrade
  5. Testing Frameworks:
    1. ava.li: opinionated, minimalist, has an assertions lib, fast with async parallel testing, zero pollution to global namespace
    2. jesmine: synchronous,  no external dependencies, adds to global namespace
    3. jest: asynchronous, React support, includes test doubles, interact test, adds to global namespace
    4. mocha: mature, async, flexible, plugable, not assertion lib or mocks, adds to global namespace
  6. Testing Tool: Assertion Library
    1. Definition: a super set of Node.js’ Assert module, which evaluate output against its expectation
    2. Chai.js (TDD or BDD)
    3. Should.js (BDD)
  7. Testing Pattern: Testing-Driven Development (TDD)
  8. Testing Pattern: Behavior-Driven Development (BDD)
    // A dead simple test
    // inside of "src/index.js"
    module.exports = class Demo{
      static add(a,b){return a+b}
    }
    // inside of "src/index.test.js"
    const chai = require("chai")
      , should = chai.should()
      , Demo = require("./index.js")
    
    describe("add", function(){
      context("1 + 2", function(){
        it("should be 3", function(){
          Demo.add(1,2).should.equal(3)
        })
      })
    })
    // inside of "buildScripts/testSetup.js"
    // This file is not transiplied, and so CommonJS and ES5
    
    // register babel to transpile before our tests run.
    require('babel-register')();
    
    // disable webpack features that Mocha doesn't support
    require.extensions['.css'] = function(){};
    
    $ mocha --reporter progress buildScripts/testSetup.js \"src/**/*.test.js\

My Starter Kit / Boilerplate / Dev Environment Setup

  1. GUI
    1. Text Editor: Sublime Text 3
      1. Plugins: AdvancedNewFile, Boxy Theme, BracketHighlighter, EditorConfig, Emmet, ESLint, Package Control, Sass, SideBarEnhancements, SublimeLinter, SublimeLinter-contrib-eslint, Sublime-contrib-htmlhint, SublimeLinter-contrib-scss-lint, SublimeLinter-csslint, SublimeLinter-json, Terminal, Vue Syntax Highlight
  2. CLI
    1. Console Wrapper: Cmder
    2. OS: Windows 10 Pro
    3. Environment: Node.js
    4. Template (click to download)

BOM APIs: WebWorker (Worker) & ServiceWorkers

  1. WebWorker (can)
    1. Start another Thread to execute JavaScript
  2. ServiceWorkers (can do)
    1. Start another Thread and execute JavaScript on the background
    2. Push Notification
    3. Background Sync
    4. Offline Experience
  3. Resources
    1. Cookbooks: awesome-service-workers & serviceworke.rs
    2. Playground: Notification Generator
  4. Annotated Examples
    Web Workers or Service Workers don’t have access of the DOM.

    // for WebWorkers
    // which could only react to data sent specifically to them
    
    if(window.Worker){
      let myWorker = new Worker("worker.js")
    
      // sending objects to myWorker's scope
      myWorker.postMessage("worker takes in object")
    
      // handing the return value from myWorker async
      myWorker.onmessage = function(e){
        handingTheResult(e.data)
      }
    }
    // for ServiceWorkers
    // which mainly react to external (network) events
    // YOUR_MAIN_JS_FILE.js
    
    if(navigator.serviceWorker){
      // the worker can control any URL on the same domain
      // that starts with, for example, "/js/"
      // or we could specify it with the second param (param.scope)
      navigator.serviceWorker.register("/js/sw.js", { scope: "/ServiceWorkerScope"})
        .then(function(registration){
          // it's async
      })
        .catch(function(err){
          // console.log(err)
          // navigator.sendBeacon("/err", err) 
      })
    }
    // inside of the sw.js file
    
    const CACHE_VER = 20170926
    let filesToCache = [
        "/css/userMayNeedIt.css"
        , "/js/userMayNeedIt.js"
    ]
    // the global object in the sw.js's scope is "self"
    // as window is a DOM object 
    self.addEventListener("install", (e) => {
      // have fun using ES6 here
      // as ES6 is better supported than ServiceWorker
      // 
      // we could cache an entire request here!
      //
      // to avoid hogging resources
      e.waitUntil(
        // use "CacheStorage" API by caches
        // caches.open() will create a new cache
        caches.open(CACHE_VER)
        .then((cache) => {
          console.log("cache opened")
          // adding files to the newly opened cache
          return cache.addAll(filesToCache)
        })
      )
    })
    
    // handling the XMLHttpRequest offline if possible
    self.addEventListener("fetch", e => {
      e.respondWith(
        cache.match(e.request)
          .then(res => {
            // answer the request locally if possible
            if(res){ return res }
            // if not, let's fetch
            return fetch(e.request).then(res => {
              // overwrite or create a new entry on cache
              // res.clone() allows us to handle stream, say
              // caching the starting part of a 1080P movie
              cache.put(e.request, res.clone())
              return res
            })
          }
        })
      )
    })

    another pattern for fetch handling

    // inside of the sw.js file
    self.addEventListener("fetch", e => {
      // respond to the fetch immediately
      e.respondWith(cache.match(e.request)
        .then(res => res))
    
      // starting a new fetch request
      e.waitUntil(
        fetch(e.request)
        .then(res => {
          cache.put(e.request, e.clone())
          .then(() => res)
          .then(reFetchCallback)
        })
      )
    })
    
    function reFetchCallback(){
      // the serviceWorker is not linked to any opened tab or domain
      // so, we have to check on each tabs
      // also, the serviceWorker could serve multiple tabs
      return self.clients.matchAll().then(tabs => {
        tabs.forEach(tab => {
          let message = {
            // this object is not standard
            type: "refresh"
            , url: response.url
            , eTag: response.headers.get("ETag")
          }
          tab.postMessage(JSON.stringify(message))
        })
      })
    }
    // inside of the main.js
    if(navigator.serviceWorker){
      navigator.serviceWorker.register("/js/sw.js", { scope: "/ServiceWorkerScope"})
    
      // listening for messages from serviceWorker
      navigator.serviceWorker.onmessage = function(e){
        let message = JSON.parse(e.data)
        let lastETag = localStorage.currentETag
        if(lastETag !== message.eTag){
          // the content are updated
        }
      }
    }

    Example of Push Notification

    self.addEventListener("push", e => {
      let push = e.data.json()
      // let push = e.data.text()
    
      e.waitUntil(){
        self.registration
        .showNotification(push.title, 
          {
            body: push.body
            , icon: "/path/to/icon.jpg"
            // actions are buttons
            , actions: {
              action: true, title: "OK"
              , action: false, title: "Cancel"
            }
          }
        )
      }
    })

    via Service Workers – Patrick Kettner