Git Product home page Git Product logo

blog's Introduction

小机灵鬼

blog's People

Stargazers

 avatar ZuoChenxue avatar xiaofeng avatar tangxiaomian avatar 李扬 avatar

Watchers

James Cloos avatar malaxiannv avatar

blog's Issues

《上瘾:让用户养成使用习惯的四大产品逻辑》-习惯的力量

一、核心模型

这本书在开头就提出了一个习惯养成模型,这是一个环形模型:触发-行动-多变的收益-投入-行动。

如图所示:
image

然后用多个事例来说明习惯的力量,以及用户对产品养成习惯后的巨大价值,那么如何让用户养成使用习惯呢?最核心的指标是什么?

以上问题延伸出另一个研究结论:习惯的养成和两个因素强相关-频率、收益。
如图所示:
image

图中透示两点重要信息:

  1. 看纵轴可知,即便行动没有收益,只要频率足够多,也会形成习惯
  2. 看横轴可知,即便一个行为带来了足够的收益,但只要频率不够,也不会养成下意识的习惯。

因此频率是很重要的,我们可以把这个理论应用到实际生活中,比如健身习惯的养成,如果你每天都在早点7点-8点健身,那么坚持多少天后会养成习惯呢?

书中没有给出确切的答案,因为习惯养成需要的频率是和这个行为习惯的复杂度有关的,市场上有很多成功学的书籍说坚持21天,就巴拉巴拉,其实是不科学的,对于复杂的习惯,可能是需要更长久的频率刺激才能固化到大脑的下意识里面。

不过如果你有一个想养成习惯的有益行为,不妨坚持一段时间看看,实践出真知。

element-ui实现单元格点击可编辑

有多种方法实现:
第一种:利用table的cell-dblclick和cell-style
1.template

<el-table
      :data="tableData"
      border
      @cell-dblclick="doubleClickCell"
      highlight-current-row
      :cell-style="defineRow"
      style="width: 100%">
      <el-table-column
        type="index"
        label="序号"
        width="50">
      </el-table-column>
      <el-table-column
        prop="username"
        label="负责人"
        width="180">
        <template v-slot="{ row, column, $index }">
          <el-input
            :ref="$index"
            v-show="editCurrentCell(row, column, $index)"
            v-model="row.username"
            @blur="test"
            placeholder="请输入姓名">
          </el-input>
          <span v-show="!editCurrentCell(row, column, $index)">{{ row.username }}</span>
        </template>
      </el-table-column>
    </el-table>

2.JS

methods: {
    doubleClickCell (row, column, cell, event) {
      if (column.property !== 'username') { // 判断这几列是否可编辑
        return
      }
      this.clickRowIndex = row.rowIndex
      this.clickColProps = column.property
    },
    getRowKeys (row) {
      return row.username
    },
    defineRow (obj) { //重点是这个函数,给row添加rowIndex属性,以便在doubleClickCell函数中使用
      Object.defineProperty(obj.row, 'rowIndex', {
        value: obj.rowIndex,
        writable: true,
        enumerable: false
      })
    },
    editCurrentCell (row, col, index) {
      if (row.rowIndex === this.clickRowIndex && col.property === this.clickColProps) {
        return true
      }
      return false
    }
}

第二种方法:在tableData中添加选中态编辑

  1. template
<el-table
  :data="tableData"
  border
  highlight-current-row
  style="width: 100%">
  <el-table-column
    type="index"
    label="序号"
    width="50">
  </el-table-column>
  <el-table-column
    prop="username"
    label="负责人"
    width="180">
    <template v-slot="{ row, column, $index }">
      <el-input
        :ref="$index"
        v-show="row[`${column.property}Edit`]"
        v-model="row.username"
        placeholder="请输入姓名">
      </el-input>
      <span
        @dblclick="doubleClickCell($index, column)"
        v-show="!row[`${column.property}Edit`]"
        >
        {{ row.username }}
      </span>
    </template>
  </el-table-column>
</el-table>

2.js

data () {
  return {
    tableData: [{
      username: 'lily',
      usernameEdit: false,//编辑态标识
    }, {
      username: 'tony',
      usernameEdit: false,
    }],
  }
}
methods: {
    doubleClickCell (rowIndex, col) {
      this.$set(this.tableData[rowIndex], `${col.property}Edit`, true)
    },
}

网页性能分析工具

之前鼎鼎大名的YSlow很久不维护了,chrome和firefox的插件地址都404,至今没有更新新的地址。

可喜的是,在chrome应用商店搜到了另一款插件All-In-One PageSpeed Test,亲测好用。

这个插件是多个性能分析工具的集合,包括:Google PageSpeed Insights,Yellow Lab Tools,WebPageTest.org ,GTmetrix (号称包含: Google PageSpeed 和 Yahoo Yslow)。

以下说明以chrome为例:

安装好插件后,打开你想测试的网页,然后点击插件,chrome会打开4个新的Tab页,分别是这四个分析工具的分析结果界面。

对比四个Tab,发现最有参考价值的是PageSpeed Insights这个Tab,可以先根据这个Tab来分析网站,再以Gtmetrix这个Tab的建议作为补充。

以PageSpeed Insights为例,可以看到对网站的总体评分

test

以及详细的优化建议
image

不分析不知道,一分析吓一跳,有很多待优化点。

如何用CMD模块定义规范声明一个组件

https://github.com/hehongwei44/my-blog/wiki/CMD%E6%A8%A1%E5%9D%97%E5%AE%9A%E4%B9%89%E8%A7%84%E8%8C%83

/**
 * 定义声明单个组件,例如 'rax-text'
 * @param {string} componentName 定义的组件名
 * @param {string} moduleName generateIndexFile文件中导出的模块名
 * @param {string} requireModule 依赖的组件名
 */
const defineComponent = (componentName, moduleName, requireModule) => {
  return ` 
     define('${componentName}', [], function(require, exports, module){ ;(function() { var fn = function(){ var {${moduleName}} = require('${requireModule}'); return ${moduleName};}
     if (typeof exports === "object" && typeof module !== "undefined") { module.exports = fn(); } 
     else if (typeof define === "function") {define('${componentName}', function(require, exports, module){ module.exports = fn(); });} 
     else { var root; if (typeof window !== "undefined") { root = window; } 
     else if (typeof self !== "undefined") { root = self; } 
     else if (typeof global !== "undefined") {root = global; } else { root = this; } root['${componentName}'] = fn(); } })(); });
    `;
};

《上瘾:让用户养成使用习惯的四大产品逻辑》-触发

触发分为外部触发和内部触发,其中外部触发是上瘾模型的第一步。

书中介绍了四种外部触发类型:付费型触发、回馈型触发、人际型触发和自主型触发。
付费型触发、回馈型触发、人际型触发都以争夺新用户为主,自住型触发以增加用户的使用次数为主。

关于内部触发,有个很新颖的观点,人们的负面情绪可能会引导他们更多的使用产品。

尤其是现在,压力无处不在,当你在苦思冥想一个算法题迟迟无结论时,当你接收到其他人不友好的反馈时,当你无聊时,这些情绪都会促使你不知不觉就打开了微信、抖音、王者荣耀等等,这些产品都能给我们短暂的慰藉,这就够了,产品已经能帮用户解决问题了,这也是为什么这几款养成类APP产生巨大商业价值的原因。

Web Worker应用性能提升之加载图片

《中卷》里面很笼统的介绍了web worker技术,想要真正掌握就需要实践。
在前端项目中图片加载经常会作为性能提升的重点来研究,以往的技术就是用懒加载、图片压缩、图片裁剪等,现在我们用web worker技术写个demo实现图片加载。

新建一个主线程文件demo.html和一个worker进程文件worker.js。
注意:Worker对象中传入的js url必须是线上链接,因此可以本地启动http server,配置个对应127.0.0.1的域名。

// demo.html
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
  <div id="img"></div>
  <script>
    let w = new Worker('http://test.web.worker:8080/worker.js')
    w.addEventListener('message', function (e) {
      var img = document.createElement("img");
      img.src = window.URL.createObjectURL(event.data);
      document.querySelector('#img').appendChild(img)
    })
  </script>
</body>
</html>
// worker.js
var arr = [
'https://cdn.stocksnap.io/img-thumbs/280h/TLZZIGZ7SJ.jpg',
'https://cdn.stocksnap.io/img-thumbs/280h/AEIULM5A4E.jpg',
'https://cdn.stocksnap.io/img-thumbs/280h/HSHO6ZFX7S.jpg',
];

for (let i = 0, len = arr.length; i < len; i++) {
  let req = new XMLHttpRequest();
  req.open('GET', arr[i], true);
  req.responseType = "blob";
  req.onreadystatechange = () => {
    if (req.readyState == 4) {
      postMessage(req.response);
    }
  }
  req.send(null);
}

worker文件中的图片url是从图片网站爬虫得来的,具体爬虫方法可以参考另一篇文章史上无敌最简单的图片爬虫

一个简单的应用demo就写好了,那这种方式到底性能提升如何呢?我们做个对比试验,下例是不使用web worker的demo

<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
  <div id="img"></div>
  <script>
    var arr = [
      'https://cdn.stocksnap.io/img-thumbs/280h/TLZZIGZ7SJ.jpg',
      'https://cdn.stocksnap.io/img-thumbs/280h/AEIULM5A4E.jpg',
      'https://cdn.stocksnap.io/img-thumbs/280h/HSHO6ZFX7S.jpg',
    ];
    for (let i = 0, len = arr.length; i < len; i++) {
      let req = new XMLHttpRequest();
      req.open('GET', arr[i], true);
      req.responseType = "blob";
      req.onreadystatechange = () => {
        if (req.readyState == 4) {
          var img = document.createElement("img");
          img.src = window.URL.createObjectURL(req.response);
          document.querySelector('#img').appendChild(img)
        }
      }
      req.send(null);
    }
  </script>
</body>
</html>

实际的例子中我使用了40张图片,这里为节省文章空间,没有把剩余的37个图片url写出来,大家可以自己参考上面的爬虫例子去爬。
以下是耗费的时间对比,运行环境chrome浏览器,第一张图是用了web worker的,第二张图是没用的,对比非常明显:

worker
noworker

可以看出web worker独立于主线程之外,对性能的提升非常大,此API目前不是所有浏览器都支持,可以根据自己的业务酌情使用。

实现一个Promise

先看日常我们是怎么使用Promise的

// 决议为成功
var p = new MyPromise(function (resolve, reject) {
      resolve(42)
 })
p.then(function fulfilled (v) {
    console.log(v)
 })

// 决议为拒绝
var p = new MyPromise(function (resolve, reject) {
        reject(new Error('fail'))
      })
p.then(null, function rejected(error) {
     console.error(error)
})

从以上代码分析Promise API的特点:

  • Promise构造函数接受一个函数作为参数,这个函数又接受两个内置方法作为参数,分别是resolve和reject,有三种状态,初始值为pending态,还有完成态fulfilled和拒绝态rejected

  • 有then、resolve、reject三大核心方法

  • resolve方法使Promise决议为完成状态,存储传入的参数值,若传入的参数值为普通值,则不处理,若传入的参数值是Promise对象,则会展开这个对象,取出里面的普通值

  • then方法接受两个函数作为参数,如果没有就启用默认函数,若promise的决议是完成态,就调用完成函数,若Promise的决议是拒绝态,就调用拒绝函数,并把promise决议后的返回值传给回调函数,最后返回一个新的Promise对象

  • reject方法使Promise决议为拒绝态,记录传入的参数,但是并不像resolve那样对传入的promise对象展开

根据以上特点,我们开始自定义Promise

class MyPromise {
  constructor (fn) {
      this.state = 'pending' // 决议态
      this.value = null // 记录resolve和reject的参数值
      this.resolve = this.resolve.bind(this)
      this.reject = this.reject.bind(this)
      fn(this.resolve, this.reject) // 执行回调,并传入内置方法resolve和reject
   }
}

构造函数创建完毕,接下来开始构建then方法

class MyPromise {
  constructor (fn) {
      this.state = 'pending' // 决议态
      this.value = null // 记录resolve和reject的参数值
      this.resolve = this.resolve.bind(this)
      this.reject = this.reject.bind(this)
      fn(this.resolve, this.reject) // 执行回调,并传入内置方法resolve和reject
   }
   
  then (fulfilled, rejected) {
      fulfilled = typeof fulfilled === 'function' ? fulfilled : (v => v)
      rejected = typeof rejected === 'function' ? rejected : (v => {throw (v)})
      if (this.state === 'fulfilled') {
           fulfilled(this.value)
      } else if (this.state === 'rejected') {
          rejected(this.value)
      }
      new MyPromise(this.resolve, this.reject)
   }
}

紧接着开始构建resolve方法

class MyPromise {
  constructor (fn) {
      this.state = 'pending' // 决议态
      this.value = null // 记录resolve和reject的参数值
      this.resolve = this.resolve.bind(this)
      this.reject = this.reject.bind(this)
      fn(this.resolve, this.reject) // 执行回调,并传入内置方法resolve和reject
   }
   
  then (fulfilled, rejected) {
      fulfilled = typeof fulfilled === 'function' ? fulfilled : (v => v)
      rejected = typeof rejected === 'function' ? rejected : (v => {throw (v)})
      if (this.state === 'fulfilled') {
           fulfilled(this.value)
      } else if (this.state === 'rejected') {
          rejected(this.value)
      }
      new MyPromise(this.resolve, this.reject)
   }
resolve (value) {
     this.state = 'fulfilled'
     if (value instanceof MyPromise) {
        value.then((v) => {
            this.value = v // 注意这里一定要用箭头函数,否则this会是undefined
        })
     } else {
        this.value = value
     }
  }
}

现在只剩下最简单的reject方法了

class MyPromise {
  constructor (fn) {
      this.state = 'pending' // 决议态
      this.value = null // 记录resolve和reject的参数值
      this.resolve = this.resolve.bind(this)
      this.reject = this.reject.bind(this)
      fn(this.resolve, this.reject) // 执行回调,并传入内置方法resolve和reject
   }
   
  then (fulfilled, rejected) {
      fulfilled = typeof fulfilled === 'function' ? fulfilled : (v => v)
      rejected = typeof rejected === 'function' ? rejected : (v => {throw (v)})
      if (this.state === 'fulfilled') {
           fulfilled(this.value)
      } else if (this.state === 'rejected') {
          rejected(this.value)
      }
      new MyPromise(this.resolve, this.reject)
   }
resolve (value) {
     this.state = 'fulfilled'
     if (value instanceof MyPromise) {
        value.then((v) => {
            this.value = v // 注意这里一定要用箭头函数,否则this会是undefined
        })
     } else {
        this.value = value
     }
  }
 reject (value) {
     this.state = 'rejected'
     this.value = value
  }
}

至此自定义Promise已经实现完毕,让我们来用这个MyPromise写个使用demo

var p1 = new MyPromise(function (resolve, reject) {
        resolve(42)
      })
 var p2 = new MyPromise(function (resolve, reject) {
        resolve(p1)
 })
      
p2.then(function fulfilled (v) {
     console.log(v); // 42
})
var p = new MyPromise(function (resolve, reject) {
        reject(new Error('fail'))
})
p.then(null, function rejected(error) {
        console.error(error) // Error: fail
})

掌握一个skill最好的方式就是实现它
Done !

史上无敌最简单的Node爬虫扒图片

有个项目需要一堆网上的图片url做mock数据,就想到可以用Node去图片网站上爬虫取,但是没有取bing,因为Bing上的dom结构有点复杂,转而爬了另一个图片网站stocksnap

新建一个main.js文件,直接上代码

var https = require('https');
var url = 'https://stocksnap.io';

https.get(url, function (res) {
  var content = ''
  res.on('data', function (chunk) {
    content += chunk
  })
  res.on('end', function () {
    var reg = /src="(.*?\.jpg)"/img;
    var filename;
    while (filename = reg.exec(content)) {
      console.log(filename[1])
    } 
  })
})

是不是很简单?
直接在命令终端执行node main,就可以得到一系列的图片url了。

参考链接:用 Node.js 写一个下载图片的爬虫

为什么CDN要开启gzip,源站文件不能配置md5校验?

首先说MD5校验的原理:
MD5值是一串hash值,文件只要有字节的变动,MD5值就会跟着改变,因此可以作为校验文件的证据。

如果源站文件配置了MD5校验规则,CDN开启了gzip压缩,那么对源站文件压缩优化的时候,会改变这个文件的MD5值,导致压缩后的文件md5值和压缩前的不一致,因此才有这个限制。

《你不知道的JS中卷》- 生成器

总体来说,《中卷》这一章讲的生成器有点乱,不系统,我又把阮一峰翻译的ES6入门中的Generator看了一遍,提炼出一些容易被忽略的点。

一、迭代器对象的throw方法

var g = function *() {
    try {
        yield
    } catch (e) {
        console.log('内部捕获', e)
    }
}
var i = g()  // 生成iterator对象
i.next() 
try {
   i.throw('a') // iterator对象有个throw方法,抛出的错误可以被generator函数内部捕获
   i.throw('b')
} catch (e) {
    console.log('外部捕获', e)
}

最后执行的log为:
内部捕获a
外部捕获b

第一个throw抛出后被内部catch捕获,第二个抛出后,内部catch语句已经执行过了,这个错误被抛出Generator函数体,被外部的catch捕获。

var g = function *() {
    try {
        yield
    } catch (e) {
        console.log('内部捕获', e)
    }
}
var i = g()  
i.throw() 

i.throw方法抛出的错误要想被内部捕获,需要至少执行过一次next方法,因此上例不会被内部catch捕获。

二、实现一个Generator执行器
一般我们执行generator的时候都是一句一句的执行

function *foo () {
      console.log('第一个yield', yield 1)
      console.log('第二个yield', yield 2)
    }
    var i = foo()
    i.next()
    i.next(1)
    i.next(2)

这样执行太麻烦了,我们写一个简单的run函数来自动执行

function run (fn) {
      var i = fn()
      function recursive(preValue) {
        const { value, done } = i.next(preValue)
        if (done) {
          return Promise.resolve(value)
        } else {
          recursive(value)
        }
      }
      recursive()
    }
    run(foo)

迭代执行即可

《你不知道的JS中卷》- 异步之回调和Promise

JS的早期祖先在实现异步时采用的是回调函数的方法,只不过业务复杂后,发现回调函数的写法容易造成callback hell,所以后期ES6提出了Promise。

先从一个例子说起:

//异步获取X的值
ajax('url1', getX)
//异步获取Y的值
ajax('url2', getY)
//求x和y之和
function add () {
console.log(x+y)
}

我们知道要想求x和y之和,必须要拿到x和y的值之后才能求和,在没有Promise的年代,我们如何实现呢?

改造上例如下:
再封装一个函数add(拿到x的值,拿到y的值,拿到值之后要做的事情)

function add(getX, getY, cb) {
   getX(function (x) {
      if(y != undefined ){
         cb(x, y)
       }
   })
  getY(function (y) {
      if(x != undefined ){
         cb(x, y)
       }
   })
}

add(fetchX, fetchY, function(x, y) {console.log(x+y)})

用回调函数的**改造完后,我们先不说好坏,接着用Promise再改造一次,然后对比二者的实现思路

function add (promiseX, promiseY) {
    Promise.all([promiseX, promiseY])
                  .then(function(res) {
                       console.log(res[0]+res[1])
                   })
}

add(fetchX, fetchY)

可以看到代码简洁了很多,需要注意的一点是Promise.all()会创建一个新的Promise对象,接下来的then方法也会创建一个Promise对象。

我们日常在项目开发中不能命名then函数,因为可能会被当做promise对象来处理,引发难捕捉的异常。
Promise也提供了API来确保我们得到一个真正的Promise,那就是Promise.resolve(),Promise.resolve能接受一个普通值,也能接受一个Promise。

比如:

Promise.resolve(42)
var p1 = new Promise(function (resolve, reject) {
      resolve(42)
})
var p2 = Promise.resolve(p1)

如果传递给Promise.resolve的是一个Promise,那么Promise.resolve会直接把这个值返回;
如果传递的是一个普通值,Promise.resolve会把它包装成Promise后再返回,同时resolve也会展开promise\thenable的值。
比如:

Promise.resolve(42)
              .then(function(v) {
                   console.log(v) // 42
               })
var p1 = new Promise(function (resolve, reject) {
      resolve(42)
})
var p2 = Promise.resolve(p1)
p2.then(function(v) {
    console.log(v) //也是 42
})

与Promise.resolve相对应的还有一个API是用来处理拒绝态的:Promise.reject(),与Promise.resolve不同,reject不会展开thenable的值,传递给reject是什么值,它就会原封不动的把它设置为拒绝理由,后续的拒绝处理函数接受到的就是thenable,而不是其底层的立即值。
比如:

var p1 = new Promise(function (resolve, reject) {
      resolve(42)
})
var p2 = Promise.reject(p1)
p2.catch(function(v) {
      console.log(v) // 打印出来的不是42,而是Promise对象
})

Promise.race()接受一个数组参数,只要有一个promise决议为完成,就完成了;有任何一个promise决议为拒绝,他就会拒绝,这意味着你永远不能给Promise.race传递一个空数组,因为它会一直hang住。

在ES9中定义了Promise的新API Promise.finally,旨在提供一个Promise决议完成后的回调函数,如果一段逻辑需要在promise决议结束后执行,在没有这个API的时候,我们需要在then和catch中各写一遍,有了这个API,就可以只写一遍了。
比如:

var isLoading = true
fetch('url')
.then(function(res){
 // 请求成功
})
.catch(function () {
// 请求异常
})
.finally(function () {
  isLoading = false
})

参考资料:https://tc39.es/ecma262/#sec-promise.prototype.finally

vue-cli3如何关闭开发环境的eslint

两种方法:

一、找到工程中的eslintrc.js文件,注释掉eslint规则
'extends': [ 'plugin:vue/essential', // '@vue/standard' ],

二、在项目根目录中创建一个vue.config.js文件,在里面加上配置项
module.exports = { lintOnSave: false }

改完后重启项目就会生效

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.