唐洋洋的个人博客

\(^o^)/~


  • 首页

  • 分类

  • 归档

  • 标签

原生JS实现图片懒加载(lazyload)

发表于 2017-09-15 | 分类于 JavaScript

前言

 图片懒加载也是比较常见的一种性能优化的方法,最近在用vue做一个新闻列表的客户端时也用到了,这里就简单介绍下实现原理和部分代码。


实现原理

 加载页面的时候,图片一直都是流量大头,针对图片的性能方法也挺多的比如base64、雪碧图等;懒加载也是其中一种,主要原理是将非首屏的图片src设为一个默认值,然后监听窗口滚动,当图片出现在视窗中时再给他赋予真实的图片地址,这样可以保证首屏的加载速度然后按需加载图片。

示例.png

具体代码

 首先在渲染时,图片引用默认图片,然后把真实地址放在data-*属性上面。


 然后是监听滚动,直接用window.onscroll就可以了,但是要注意一点的是类似于window的scroll和resize,还有mousemove这类触发很频繁的事件,最好用节流(throttle)或防抖函数(debounce)来控制一下触发频率。underscore和lodash里面都有封装这两个方法,这里先不多做介绍了。

 接着要判断图片是否出现在了视窗里面,主要是三个高度:1,当前body从顶部滚动了多少距离。2,视窗的高度。3,当前图片距离顶部的距离。offsetTop相关属性可以参考这里,具体代码如下:

window.onscroll =_.throttle(this.watchscroll, 200);
watchscroll () {
var bodyScrollHeight = document.body.scrollTop;// body滚动高度
var windowHeight = window.innerHeight;// 视窗高度
var imgs = document.getElementsByClassName(‘lazyloadimg’);
for (var i =0; i < imgs.length; i++) {
var imgHeight = imgs[i].offsetTop;// 图片距离顶部高度
if (imgHeight < windowHeight + bodyScrollHeight) {
imgs[i].src = imgs[i].getAttribute(‘data-src’);
img[i].className = img[i].className.replace(‘lazyloadimg’,’’)
}
}
}


结语

 大概内容就这么多了,下次可能会补充一下防抖节流源码的实现。最后再补充两个常见的滚动判断:
1.页面滚动离开首屏(这时可显示回到顶部的按钮):document.body.scrollTop > window.innerHeight
2.页面滚动到底部了(这时可去调接口获取更多内容):window.scrollY + window.innerHeight > document.body.offsetHeight

vue源码解析之--工具函数(一)

发表于 2017-09-15 | 分类于 JavaScript , vue

 工具函数是每个框架类库的基本的组成部分,本篇分析的是/shared/util.js文件,从中挑选的部分方法函数,比较常见基础的方法我就跳过了。值得一说的是,编译合成前的源码都用到了flow.js,可以理解为一个javascript的静态类型检查器,有点像typescript,可以对js的变量进行类型定义、检查错误等,然后再通过编译生成正常的js代码,文件开头有/ @flow /注释的都是用到了flow的。

1.makeMap()

 先来看这个makemap,传入一个字符串和是否区分大小写的标示符,然后在函数内部生成一个map对象,传入字符串分割 ‘,’ 生成的数组的值作为map的属性名,值为true,然后返回一个函数用来判断一个变量是否包含在传入字符串里。

function makeMap (
str,
expectsLowerCase
) {
var map = Object.create(null);
var list = str.split(‘,’);
for (var i = 0; i < list.length; i++) {
map[list[i]] = true;
}
return expectsLowerCase
? function (val) { return map[val.toLowerCase()]; }
: function (val) { return map[val]; }
}

 这里也是个典型的闭包的应用,返回的函数调用了内部的map数组,所以map的作用域会一直存在不会被回收掉。这个方法主要用在检查变量命名是否合法之类的。

2.cached()

function cached (fn) {
var cache = Object.create(null);
return (function cachedFn (str) {
var hit = cache[str];
return hit || (cache[str] = fn(str))
})
}

 新建一个cache对象,这个方法传入参数是一个处理字符串的函数,返回这个传入函数的缓存版本。就是每次调用这个处理字符串函数时,会把被传入的字符串存到cache中,下次再传入相同字符串时就直接用cache缓存了(又是巧用闭包…)。

3.camelize、capitalize、hyphenateRE

var camelizeRE = /-(\w)/g;
var camelize = cached(function (str) {
return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ‘’; })
});
var capitalize = cached(function (str) {
return str.charAt(0).toUpperCase() + str.slice(1)
});
var hyphenateRE = /([^-])([A-Z])/g;
var hyphenate = cached(function (str) {
return str
.replace(hyphenateRE, ‘$1-$2’)
.replace(hyphenateRE, ‘$1-$2’)
.toLowerCase()
});

 这三个函数都是处理字符串的,主要是对正则表达式的应用,结合上面提到的cache生成的缓存版函数camelize()用于将中划线命名的变成驼峰法命名,camelize(‘abc-def’) = abcDef;capitalize()用于将首字母大写,hyphenateRE()作用与camelize()相反,用于将驼峰法命名转换成中划线。(这里没看懂为什么要用两次replace方法?)

4.bind()


function bind (fn, ctx) {
function boundFn (a) {
var l = arguments.length;
return l
? l > 1
? fn.apply(ctx, arguments)
: fn.call(ctx, a)
: fn.call(ctx)
}
boundFn._length = fn.length;
return boundFn
}

 封装了一个简单的bind方法,根据传入的对象(作用域)和函数,返回一个绑定函数,即在传入的对象调用传入的函数。源码解释说比原生绑定更快,这里我也不太明白为什么。。。

5.genStaticKeys()


function genStaticKeys (modules) {
return modules.reduce(function (keys, m) {
return keys.concat(m.staticKeys || [])
}, []).join(‘,’)
}

 这个函数主要是把传入的编译模块,生成为静态键值组成的字符串。主要用到了reduce这个方法,即把数组每一项重叠生成一个总和,第二个参数[]表示初始值,生成的总数组用逗号分隔转换为字符串。我看了下似乎这个方法只是在定义baseOptions的staticKeys属性时用到了。

6.looseEqual() looseIndexOf()


function looseEqual (a, b) {
var isObjectA = isObject(a);
var isObjectB = isObject(b);
if (isObjectA && isObjectB) {
try {
return JSON.stringify(a) === JSON.stringify(b)
} catch (e) {
// possible circular reference
return a === b
}
} else if (!isObjectA && !isObjectB) {
return String(a) === String(b)
} else {
return false
}
}
function looseIndexOf (arr, val) {
for (var i = 0; i < arr.length; i++) {
if (looseEqual(arr[i], val)) { return i }
}
return -1
}

 looseEqual()用于判断两个传入参数是否相等,同是对象的情况下先用JSON.stringfiy()转换,报错的时候直接用全等判断,这里顺便提一下什么时候JSON.stringfiy()会报错呢?circular structure的时候,举一个例子var obj = {a:1}; obj.b=obj; 这个时候JSON.stringfiy(obj)就会报错啦~都不是对象的时候转化为string比较,其他情况都是false。
 looseIndexOf()传入一个数组和一个值,如果传入值在数组里则返回对应的index,不在返回-1,这里也是依次用looseEqual来进行比较.ES6里面数组的扩展方法有一个indexOf也是类似的方法,判断数组是否包含某个值。

7.once()

function once (fn) {
var called = false;
return function () {
if (!called) {
called = true;
fn.apply(this, arguments);
}
}
}

 让一个函数只调用一次,也是典型的闭包调用,返回一个由called标示控制的函数,调用此函数时,called标示符改变,下次调用就无效了。

vue服务端渲染(SSR)初探

发表于 2017-09-15 | 分类于 服务端渲染

前言

首先来讲一下服务端渲染,直白的说就是在服务端拿数据进行解析渲染,直接生成html片段返回给前端。具体用法也有很多种比如:

  • 传统的服务端模板引擎渲染整个页面
  • 服务渲染生成htmll代码块, 前端 AJAX 获取然后js动态添加

    服务端渲染的优劣

     首先是seo问题,前端动态渲染的内容是不能被抓取到的,而使用服务端渲染就可以解决这个问题。还有就是首屏加载过慢这种问题,比如在SPA中,打开首页需要初始加载很多资源,这时考虑在首屏使用服务端渲染,也是一种折中的优化方案。但是使用SSR时,势必会增加服务器的压力,还有可能会需要前后端同构,使用同样的模板引擎,这似乎与前后端分离的观点又是矛盾的。废话就说到这里,下面来看一下vue框架中的服务器渲染。

    vue-server-renderer

     vue-server-renderer就是vue中处理服务端加载的一个模块了,官方文档:https://ssr.vuejs.org/en/,暂时没有中文版的,我也只是稍微看了一些,然后写了一个简单的demo。首先新建一个test.js文件,并用npm安装依赖express、vue、vue-server-renderer。引入vue-server-renderer之后,然后新建一个temp.html作为渲染的基本模板,用createRenderer方法新建一个render实例,这里我传入temp.html作为renderer的template的参数,在后面渲染时就会以这个temp.html作为基础模板。
    1
    2
    3
    const renderer = require('vue-server-renderer').createRenderer({
    template: require('fs').readFileSync('./temp.html', 'utf-8')
    })

temp.html:

1
2
3
4
5
<!DOCTYPE html><br><html lang="en"><br><head><title>{{title}}</title></head>
<body>
<!--vue-ssr-outlet-->
</body>
</html>

 接下来随便定义一些渲染用的数据,然后用express新建一个node服务器,再定义一个vue的实例。然后再调用renderer的renderToString方法来渲染生成html,渲染成功后返回给客户端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const Vue = require('vue')
const server = require('express')()
const context = {
title: 'hello'
}
const mocktitle = '我爱吃的水果'
const mockdata = ['香蕉', '苹果', '橘子']
server.get('*', (req, res) => {
const app = new Vue({
data: {
url: req.url,
data: mockdata,
title: mocktitle
},
template: `<div>The visited URL is: {{ url }}
<h3>{{title}}</h3>
<p v-for='item in data'>{{item}}</p>
</div>`
})
renderer.renderToString(app, context, (err, html) => {
if (err) {
res.status(500).end('Internal Server Error')
return
}
res.end(html)
})
})
server.listen(8080)

 注意这里渲染的数据有两种,mockdata是作为vue实例的data来渲染在实例模板中的,而context是作为基础模板的data来渲染temp.html的。可以看到在服务端用vue进行渲染的规则和前端渲染时一样,v-for、v-if等都可以正常使用。最后命令行输入node test.js,然后在浏览器打开http://localhost:8080 查看结果如下:
示例.png
可以看到服务端直接返回了一个渲染完成的Doc,示例demo到此结束。

结语

 服务端渲染还是客户端渲染的问题,个人觉得还是要针对具体业务场景然后再做选择。

使用Javascript做算法题(一)Maximum Subarray

发表于 2017-09-14 | 分类于 leetcode算法题

前言

  • 最近开始在leetcode上接触算法题,印象中的算法题大都是用cpp或者java解的,而这个网站支持javascript解题提交。因为大学也没学过算法,数据结构也只是稍微了解,所以解题目标也是选简单经典的题。下面开始讲解这道题–53.Maximum Subarray.题目地址

    题目描述

  • 给出一个由数字组成(含负数)的数组,找出它的一个每项相加之和最大的子数组,返回这个最大和数字。举例:给出一个数组[-2,1,-3,4,-1,2,1,-5,4], 每项和最大的子数组为[4,-1,2,1],返回最大和数字6。

    解题思路

  • 首先抛出一个名词动态规划算法,概念理解起来较为抽象,我个人的理解是求最优解的题基本都可以用动态规划作为解题思路。
  • 首先可以确定解题的复杂度,这里其实只要遍历一次数组即可,时间复杂度为O(n),而需要维护的状态变量有两个,全局最大值和局部最大值。就是随着从第一项开始往后推进,你需要确定从第一项到第n项时已经出现的元素组成的最大和,这个状态变量就是全局最大值;而局部最大值是遍历到第n项时,包含第n项的情况下的最大值。全局最大值是由局部最大值推导出的,下面是具体代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /**
    * @param {number[]} nums
    * @return {number}
    */
    var maxSubArray = function(nums) {
    if(!nums.length)
    return 0
    let local = nums[0]
    let global = nums[0]
    for (let i = 1; i < nums.length; i++) {
    local = Math.max(nums[i], local + nums[i]) // 包含第n项的情况下的最大值
    global = Math.max(local, global) // 全局最大值
    }
    return global
    };

javascript常用工具函数总结

发表于 2017-09-12 | 分类于 JavaScript , 总结

前言

  • 以下代码来自:自己写的、工作项目框架上用到的、其他框架源码上的、网上看到的。
  • 主要是作为工具函数,服务于框架业务,自身不依赖于其他框架类库,部分使用到es6/es7的语法使用时要注意转码
  • 虽然尽量在函数中做了错误情况的处理,仍有可能出现报错的情况(不定期完善)

    1. 获取url上的参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
    *获取url上的参数
    * @return {object}
    * @example
    * getRequest() getRequest().paramA
    */
    function getRequest() {
    var searchString = window.location.search.substring(1),
    params = searchString.split("&"),
    hash = {};
    if (searchString == "") return {};
    for (var i = 0; i < params.length; i++) {
    var pos = params[i].indexOf('=');
    if (pos == -1) { continue; }
    var paraName = params[i].substring(0, pos),
    paraValue = params[i].substring(pos + 1);
    hash[paraName] = paraValue;
    }
    return hash;
    }
  • 返回一个对象,将url上的参数以键值对的形式存储在返回结果中,如果url上没参数,则返回空对象

    2. 追加url参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    /**
    * 追加url参数
    * @param {string} url url参数
    * @param {string|object} key 名字或者对象
    * @param {string} value 值
    * @return {string} 返回新的url
    * @example
    * appendQuery('lechebang.com', 'id', 3);
    * appendQuery('lechebang.com?key=value', { cityId: 2, cityName: '北京'});
    */
    function appendQuery (url, key, value) {
    var options = key;
    if (typeof options == 'string') {
    options = {};
    options[key] = value;
    }
    options = $.param(options);
    if (url.includes('?')) {
    url += '&' + options
    } else {
    url += '?' + options
    }
    return url;
    }
  • 传入一个url和需要添加的参数键值对,需添加的参数可以直接传对象格式。会判断原url上是否有参数,没有的话就加’?’,返回添加参数后的url。

    3. 计算两个日期的时间差
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    /**
    * 计算两个日期时间的时间差
    * @param {Date, Date} date1 date2
    * @return {object | null}
    * @example
    * getDiff(new Date('2017-09-08'), new Date())
    */
    function getDiff(date1, date2) {
    if (!date1.getTime || !date2.getTime) return null
    var ms = (date1.getTime() - date2.getTime());
    var day1 = Math.round(ms / 24 / 3600 / 1000),
    hh1 = Math.round((ms / 3600 / 1000) % 24),
    mm1 = Math.round((ms / 1000 / 60) % 60),
    ss1 = Math.round((ms / 1000) % 60);
    return {
    day: day1,
    hour: hh1,
    minute: mm1,
    second: ss1
    };
    }
  • 传入两个Date日期对象,返回一个对象,其属性值day、hour、minute、second分别表示相差天数、小时、分钟、秒。结果以Math.round()取整,如果结果为负,则表示第一个日期在第二个日期前面

    4. 将canvas转化为image图片格式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /**
    * 将canvas转化为image格式
    * @param {string} cId
    * @return {object HTMLImageElement}
    * @example
    * canvasToImg('canvas') canvasToImg('#canvarsId')
    */
    function canvasToImg(cId){
    let canvas = document.querySelector(cId)
    if (!canvas || !canvas.toDataURL) return new Image()
    let imgData = canvas.toDataURL('image/png'),
    imgs= new Image();
    imgs.src=imgData;
    return imgs
    }
  • 传入一个css选择器,函数根据选择器查询canvas节点,然后返回该canvas的image格式节点,如果查找不到则返回一个空的image。原理是将canvas转化为base64编码,toDataURL方法貌似是canvas节点独有的,然后新建一个src是这个base64编码的图片。

  • ps:什么情况下需要做这种转换呢?目前我知道的一个就是canvas在移动端无法长按保存到手机。

    5. 生成随机guid
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /**
    * 生成一个唯一的guid
    * @return {string}
    * @example
    * // 7f603b20-17ff-4f47-aeb9-e7996de04939
    * util.guid();
    * @see http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
    */
    function guid () {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
    return v.toString(16);
    });
    }
  • 这个方法用于生成一个随机guid,可以将生成的guid视为全局唯一的(生成两个相同id的情况很少)。guid似乎在前端用的比较少,目前项目用到就是在每次请求后端接口时调用此方法,生成一个guid传过去。

    6. 获取一个月份的天数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    function isLeapYear (year) {
    if (year % 100 === 0) {
    if (year % 400 === 0) {
    return true;
    }
    } else if (year % 4 === 0) {
    return true;
    }
    return false;
    }
    /**
    * 获取某个月份有多少天
    * @return {number}
    * @param {string | number} year month
    * @example
    * getDaysInMonth(2017, 9)
    */
    function getDaysInMonth (year, month) {
    return [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
    }
  • 传入一个年份和月份,返回该月有多少天,其中也包含了一个isLeapYear方法来判断是否是闰年,应该在实现日历或者日期选择组件时用的到

    7. 过滤对象属性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /**
    * 过滤选出一个对象的某些属性
    * @param{object, array} obj key
    * @return{object}
    * @example
    * pick(obj, [key1, key2])
    */
    function pick (obj, keys) {
    let result = {}
    if (!obj || !keys.forEach) {
    return result
    }
    keys.forEach((item) => {
    if (obj.hasOwnProperty(item)) {
    result[item] = obj[item]
    }
    })
    return result
    }
  • 传入一个对象和一个数组,遍历数组,如果传入对象有数组中包含的属性,则提取出来,返回一个过滤提取出来的属性组成的对象。

  • 这个方法用处挺多的,比如从一个接口拿到了结果对象,可能上面很多属性你暂时用不到的,就可以用这个方法进行过滤。比如以前我用vue写项目的时候,就经常把接口返回的一个对象作为vue的data属性去渲染页面,这个时候这个对象是动态绑定在vue实例上面的,但是其中有些属性页面渲染根本用不到,还得去监听这些属性,白白浪费性能,这种时候将接口返回的对象筛选一下再赋给vue的data就显得合理多了。

    8. 判断是否是一个对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /**
    * 判断传入参数是否是一个合法对象
    * @param{object} obj
    * @return{object}
    * @example
    * isObject (null) isObject (() => {} )
    */
    function isObject (obj) {
    var type = typeof obj;
    return type === 'function' || type === 'object' && !!obj;
    }
  • 判断传入参数是否是一个合法对象,function类型均返回true,null返回false(typeof null 返回 object)

    9. 判断一个函数是否是native code
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * 判断传入函数是否在当前环境下得到支持
    * @param{function} Ctor
    * @return{boolean}
    * @example
    * isNative (window.Symbol) isNative (window.Promise)
    */
    function isNative (Ctor) {
    return typeof Ctor === 'function' && /native code/.test(Ctor.toString())
    }
  • javascript原生自带的方法使用toString转成字符串时会包含‘native code’字段,比如Math.max.toString()就返回”function max() { [native code] }”。

  • 这个方法可以判断某些原生特性是否被当前浏览器支持。比如isNative (window.Promise)在chrome中返回true,因为chrome支持原生es6语法,放到ie里面就只能返回false了~

    10. 深度克隆对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    /**
    * 返回传入对象的一个深度克隆的副本对象
    * @param{object} obj
    * @return{object}
    * @example
    * cloneDeep(obj)
    */
    function cloneDeep (obj) {
    if (!isObject(obj)) return obj;
    let result
    if (Array.isArray(obj)) {
    result = []
    obj.forEach((item) => {
    result.push(cloneDeep(item))
    })
    return result
    }
    result = {}
    for (let key in obj) {
    let item = obj[key]
    if (_.isObject(item)) {
    result[key] = cloneDeep(item)
    } else {
    result[key] = item
    }
    }
    return result
    }
  • 首先对传入参数进行判断,不是合法对象则直接返回其本身。然后再遍历其属性,属性中包含对象的会再次递归调用cloneDeep方法,浅克隆的话可以直接用Object.assign()方法。

    11. 获取两个地点的实际距离
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    /**
    * 获取两个高德坐标的距离, 后一个点,不传,默认为用户坐标
    * @return {null|number} 距离多少米,没有定位信息,返回null
    * @example
    * getDistance(31.282055633974, 121.379623888259)
    */
    function getDistance (endLat, endLon, startLat, startLon) {
    if (!startLat) {
    let address = Lizard.state.address
    if (address && address.lat) {
    startLat = address.lat
    startLon = address.lon
    }
    }
    // 没有定位
    if (!startLat) {
    return null
    }
    const PI = Math.PI
    let lon1 = (PI / 180) * startLon
    let lon2 = (PI / 180) * endLon
    let lat1 = (PI / 180) * startLat
    let lat2 = (PI / 180) * endLat
    // 地球半径
    let R = 6378.137;
    // 两点间距离 km,如果想要米的话,结果*1000就可以了
    let d = Math.acos(Math.sin(lat1) * Math.sin(lat2) + Math.cos(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1)) * R;
    return parseInt(d * 1000, 10)
    }
  • 依次传入两个点的经纬度,然后计算得出两个点的距离,单位为km。这个方法可能实际应用的比较少,我这里还是写出来,作为一种前端计算距离的方法(在能拿得到定位的情况下)。

    12. 加载图片(promise封装)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /**
    * 传入图片url,返回一个promise对象,加载成功时resolve
    * @return {Promise}
    * @example
    * loadImg(url).then(console.log('加载完成')).catch(err => {console.log(err)})
    */
    function loadImg (url) {
    return new Promise((resolve, reject) => {
    let img = new Image()
    img.addEventListener('load', function() {
    resolve([img.width, img.height])
    }, false)
    img.addEventListener('error', reject, false)
    img.src = url
    })
    }
  • 这个函数参考自ECMAScript 6 入门,算是promise的一个很经典的应用实例,加载完成后promise对象resolve,同时返回图片的宽高,以便后面调整样式等等。加载失败则返回reject,所以再调用这个方法后需要用catch方法来捕捉异常。

    13. 重复字符串n次
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /**
    * 传入字符串,和重复次数,返回结果字符串
    * @return {string}
    * @param{string, number} str n
    * @example
    * loadImg(url).then(console.log('加载完成')).catch(err => {console.log(err)})
    */
    const repeat = (str, n) => {
    let res = ''
    while (n) {
    if (n % 2 === 1) res += str
    if (n > 1) str += str
    n >>= 1
    }
    return res
    }
  • 这个函数在vue源码里看到的,用到了位运算符,如果让我来实现的话可能就马上想到重复n次,而上面这个函数执行时的时间复杂度差不多只是n/2。

    14. 变量是否以’$’或者’_’开头
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /**
    * 传入字符串,判断是否以'$'或者'_'开头
    * @return {Boolean}
    * @param{string} str
    * @example
    * isReserved (‘$’) isReserved (‘param’)
    */
    function isReserved (str) {
    var c = (str + '').charCodeAt(0);
    return c === 0x24 || c === 0x5F
    }
  • 同样是vue源码里的方法,判断变量是否以’$’或者’_’开头,charCodeAt(index)方法会返回字符串对应index位置的字符的Unicode编码,’$’对应的Unicode编码就是36,这里用的是十六进制表示方法就是0x24。要判断变量名以什么字符开头或结尾,将上面方法略微修改即可实现。

  • ps:vue中哪里用到这个方法了呢,我去看了下就是在初始化vue实例的data对象时会判断,如果你的vue实例data里面命名了以’$’或者’_’的对象,是不会把这个属性重新定义其get/set的。简单点说,如果你在vue的data里定义一个命名为$data的属性,再在模板里渲染,就会报错的。

    15. promise扩展-finally
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /**
    * 在一个promise链调用结尾调用finally,传入一个函数,无论最后promise的状态是什么总会执行该函数
    * @param{function} callback
    * @example
    * server.listen(0).then(function () { // run test }).finally(server.stop);
    */
    Promise.prototype.finally = function (callback) {
    let P = this.constructor;
    return this.then(
    value => P.resolve(callback()).then(() => value).catch(e => {console.error(e)}),
    reason => P.resolve(callback()).then(() => { throw reason }).catch(e => {console.error(e)})
    );
    };
  • 参考自ECMAScript 6 入门,对于promise对象的一个扩展方法,不论前面的promise状态是什么总会调用callback函数。

  • 我在原代码的基础上添加了新的catch,用于兼容callback执行时返回了一个状态为reject的promise的情况。

    结语

  • 再次补充,已搬运到github博客,上面自带导航~~~
123
TokenYangForever

TokenYangForever

放点随笔文章

25 日志
14 分类
18 标签
© 2017 TokenYangForever
由 Hexo 强力驱动
主题 - NexT.Muse