“Yeah It’s on. ”
正文
https://mp.weixin.qq.com/s/MMUQWlDOqpLLWDy9F-RZ7A https://www.30secondsofcode.org/list
Object
对象浅复制
function extend(dst, obj) {
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
dst[i] = obj[i]
}
}
}
对象浅对比
function shallowEqual(object1, object2) {
const keys1 = Object.keys(object1);
const keys2 = Object.keys(object2);
if (keys1.length !== keys2.length) {
return false;
}
for (let key of keys1) {
if (object1[key] !== object2[key]) {
return false;
}
}
return true;
}
去除对象中某些键值对
const {pwd,...data} = obj //去除密码这一项
剩下的data就是去除了pwd的数据
创建一个纯(pure)对象
你可以创建一个100%的纯对象,他不从Object中继承任何属性或则方法(比如,constructor,toString()等等)。
const pureObject = Object.create(null);
console.log(pureObject); //=> {}
console.log(pureObject.constructor); //=> undefined
console.log(pureObject.toString); //=> undefined
console.log(pureObject.hasOwnProperty); //=> undefined
Array
数组分割
function Chunk(arr = [], size = 1) {
return arr.length
? arr.reduce(
(t, v) => (t[t.length - 1].length === size ? t.push([v]) : t[t.length - 1].push(v), t),
[[]],
)
: [];
}
const arr = [1, 2, 3, 4, 5];
Chunk(arr, 2); // [[1, 2], [3, 4], [5]]
数组过滤
function Difference(arr = [], oarr = []) {
return arr.reduce((t, v) => (!oarr.includes(v) && t.push(v), t), []);
}
const arr1 = [1, 2, 3, 4, 5];
const arr2 = [2, 3, 6]
Difference(arr1, arr2); // [1, 4, 5]
数组扁平
function Flat(arr = []) {
return arr.reduce((t, v) => t.concat(Array.isArray(v) ? Flat(v) : v), [])
}
const arr = [0, 1, [2, 3], [4, 5, [6, 7]], [8, [9, 10, [11, 12]]]];
Flat(arr); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
数组去重
function Uniq(arr = []) {
return arr.reduce((t, v) => t.includes(v) ? t : [...t, v], []);
}
const arr = [2, 1, 0, 3, 2, 1, 2];
Uniq(arr); // [2, 1, 0, 3]
Set去除数组的重复元素
注意:只能用于拥有基本类型的数组
let arr = [1,2,3,3];
let unique = [...new Set(arr)];
将数组拆成一个对象
(键:id 值:数组item)
products.reduce((obj, product) => {
obj[product.id] = product
return obj
}, {})
Array.prototype.slice.call
我们知道,Array.prototype.slice.call(arguments)能将具有length属性的对象转成数组,除了IE下的节点集合(因为ie下的dom对象是以com对象的形式实现的,js对象与com对象不能进行转换)
var a={length:2,0:'first',1:'second'};
Array.prototype.slice.call(a);// ["first", "second"]
var a={length:2};
Array.prototype.slice.call(a);// [undefined, undefined]
slice有两个用法,一个是String.slice,一个是Array.slice,第一个返回的是字符串,第二个返回的是数组,这里我们看第2个
数组的对象解构
数组也可以对象解构,可以方便的获取数组的第n个值
const csvFileLine = '1997,John Doe,US,john@doe.com,New York';
const { 2: country, 4: state } = csvFileLine.split(',');
country // US
state // New Yourk
从后向前遍历数组
let deps = [1,2,3,4,5,6,7]
let i = deps.length
while (i--) {
console.log(deps[i])
}
拥有复杂数据类型的数组去重
let arr = [1, 2, 3, 3, true, true, [2, 2], [2, 2], [1, 2, [3, 4]], [1, 2, [3, 4]], {name: 'aaa'}, {father: 'fff'}, {name: 'aaa'}];
function unique(arr) {
var hash = {};
return arr.filter(function (element) {
var key = JSON.stringify(element);
return hash.hasOwnProperty(key) ? false : (hash[key] = true)
});
}
console.log(unique(arr))
is 系列
isJson
function isJson(str) {
const start = str.match(/^\s*(\[|\{)/);
const end = {'[': /]\s*$/, '{': /}\s*$/};
return start && end[start[1]].test(str);
}
没有g全局标志,那么start[0]保存的是完整的匹配,start[1]保存的是第一个括号里捕获的字串
isObject
注意此方法没有仔细区分是数组还是对象
function isObject (obj) {
return obj !== null && typeof obj === 'object'
}
Check for plain object (这个仔细区分数组还是对象)
function isObject(val) {
return Object == val.constructor;
}
isFunction
function isFunction(source) {
return '[object Function]' === Object.prototype.toString.call(source);
};
isFirefox
const isFirefox = typeof navigator !== 'undefined' && navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
isPromise
判断对象是否为Promise
function isPromise (val) {
return val && typeof val.then === 'function'
}
这个方法不适用于async函数 这个方法不适用于async函数 这个方法不适用于async函数
重要的事情说三遍,由于错用了这个方法,导致框架的执行流程出现偏差,查找了两个小时才发现。
这个方法也不适用返回Promise对象的函数
例子:
function test() {
return new Promise((resolve, reject) => {
resolve("asdasd")
})
}
console.log(test.then) //undefined (很明显就存在then)
isAsyncFunction
判断函数是否是async函数
function isAsyncFunction(fn) {
return Object.prototype.toString.call(fn) === "[object AsyncFunction]"
}
另外一种方法:
根据
fn.constructor.name === "AsyncFunction"
来判断
isElement
https://www.w3school.com.cn/jsref/prop_node_nodetype.asp
function isElement(value) {
return (
typeof HTMLElement === 'object' ? value instanceof HTMLElement : //DOM2
value && typeof value === "object" && value !== null && value.nodeType === 1 && typeof value.nodeName==="string"
);
}
nodeType 属性返回以数字值返回指定节点的节点类型。
- 如果节点是元素节点,则 nodeType 属性将返回 1。
- 如果节点是属性节点,则 nodeType 属性将返回 2。
Other
统计字符串中相同字符出现的次数
var arr = 'abcdaabc';
var info = arr
.split('')
.reduce((p, k) => (p[k]++ || (p[k] = 1), p), {});
console.log(info); //{ a: 3, b: 2, c: 2, d: 1 }
统计次数代码的详解
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
- function(total,currentValue, index,arr) 必需。用于执行每个数组元素的函数。
函数参数:
- total 必需。初始值, 或者计算结束后的返回值。
- currentValue 必需。当前元素
- currentIndex 可选。当前元素的索引
- arr 可选。当前元素所属的数组对象。
- initialValue 可选。传递给函数的初始值
举个栗子:
const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue;
// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// expected output: 10
// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5));
// expected output: 15
应用在Redux中
...action.products.reduce((obj,product)=>{
obj[product.id] = product
return obj
},{})
逗号运算符 它将先计算左边的参数,再计算右边的参数值。然后返回最右边参数的值。
var a = 10, b = 20;
function CommaTest(){
return a++, b++, 10;
}
var c = CommaTest();
alert(a); // 返回11
alert(b); // 返回21
alert(c); // 返回10
在JavaScript中,逗号运算符的优先级比赋值运算符还要底
var a = 20;
var b = ++a,10;
alert(b); //不能运行
下面代码才可以运行
var a = 20;
var b = (++a,10);
alert(b);
之前不能执行的代码可以看成如下代码:
var a = 20;
(var b = ++a),10;
alert(b);
逗号运算符最普通的用途是在 for 循环的递增表达式中使用
for (i = 0; i < 10; i++, j++)
{
k = i + j;
}
每次通过循环的末端时, for 语句只允许单个表达式被执行。逗号 运算符被用来允许多个表达式被当作单个表达式,从而规避该限制。
评级组件
单行写一个评级组件:”★★★★★☆☆☆☆☆”.slice(5 - rate, 10 - rate); 变量rate是1到5的值
[] == ![]
[] == ![] 结果为 true
[] == ![]解释
我们都知道 JavaScript 中唯一一个非自反(non-reflexive)的值是 NaN,而在这里乍看之下,普通的字面量空数组居然也是“非自反”,岂不矛盾?
这个问题在某些人看来应该算是 JavaScript 的 Bad Part,但是搞懂这个问题对 JS 的强制类型转换的理解还是有帮助的,也可以避免在自己的代码中出现类似的问题
解释这个“等式”至少要四句话,涉及到了 JavaScript 的运算符优先级 、宽松相等(即 ==)的判断过程以及强制类型转换
- 等号右边有 ! ,优先级比 == 更高,优先计算右边的结果。 [] 为非假值,所以右边的运算结果为 false,即:
![] ==> false // 此处表示转换过程,下同
- == 的任意一边有 boolean 类型的值时先把这个值转换成 number 类型,右边转换成了 0 ,即:
Number(false) ==> 0
- == 的两边分别是 number 和 object 类型的值时,把 object 转换成 number 类型,需要对 object 进行 ToNumber 操作,即:
Number([].valueOf()) ==> 0
- 至此,== 两边的值都变成 0 了,显然是成立的
宽松相等还有一个坑,就是大部分 object 对象包括空字面量对象 {} 在跟强制类型转换过程中会出现的 number 类型的值比较时,object 的值会转换成 NaN,跟任何值比较都是不相等的。而在跟字符串比较的时候又会转化成 “[object Object]”
Number({}) ==> NaN // 这里表示转换的过程,这个等式并不成立
Number.isNaN(Number({})) // true
{} == "[object Object]" // true
{} == 0 // false 看起来好像显然,但实际是 NaN != 0
Math.min(最大值,Math.max(0,function))
将范围固定在 [0 - 最大值] 之间 例子:
Math.min(255, Math.max(0, this.touch.left + deltaX))
Anagrams of string(带有重复项)
为字母创建字谜
使用递归。对于给定字符串中的每个字母,为字母创建字谜。使用map()将字母与每部分字谜组合,然后使用reduce()将所有字谜组合到一个数组中,最基本情况是字符串长度等于2或1。
const anagrams = str => {
if (str.length <= 2) return str.length === 2 ? [str, str[1] + str[0]] : [str];
return str.split('').reduce((acc, letter, i) =>
acc.concat(anagrams(str.slice(0, i) + str.slice(i + 1)).map(val => letter + val)), []);
};
// anagrams('abc') -> ['abc','acb','bac','bca','cab','cba']
解析:
- str.slice(0, i) + str.slice(i + 1) 除去了循环中的当前元素letter 数组相加变成新的变量
- 进行递归直到字符串长度等于2或1
- [str, str[1] + str[0]] : [str] 把字符串转化为数组
- 最后数组用map 把当前letter和其他部分拼接起来扔进叠加器acc里
自定义callback函数
const TIMEOUT = 100
let result = "result" //result可以是任意类型的数据
let getProducts = (cb, timeout) => setTimeout(() => cb(result), timeout || TIMEOUT)
getProducts(result => console.log(result)) //console.log可以是任意处理result的函数
柯里化
// compose(fn1,fn2,fn3) 变为
// fn1(fn2(fn3))
export function compose(...funcs){
if (funcs.length==0) {
return arg=>arg
}
if (funcs.length==1) {
return funcs[0]
}
return funcs.reduce((ret,item)=> (...args)=>ret(item(...args)))
}
判断当前的浏览器设备
来自vue源码
const inBrowser = typeof window !== 'undefined'
const UA = inBrowser && window.navigator.userAgent.toLowerCase()
const isIE = UA && /msie|trident/.test(UA)
const isIE9 = UA && UA.indexOf('msie 9.0') > 0
const isEdge = UA && UA.indexOf('edge/') > 0
const isAndroid = UA && UA.indexOf('android') > 0
const isIOS = UA && /iphone|ipad|ipod|ios/.test(UA)
const isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge
对className的一些操作
hasClass
function hasClass(el, className) {
let reg = new RegExp('(^|\\s)' + className + '(\\s|$)')
return reg.test(el.className)
}
\s在正则里就表示空白符
用\\s
是因为所使用的工具\
本身就具有转义的功能,比如\b
表示退格符,在正则中\b(不在中括号中)表示单词边界,要将字符串\b传给正则就得首先对\转义 ,用\\
表示 \
, \\b
表示\b
同理在这些\本身具有转义作用的工具中,要将字符串\s传给正则用要\\s
addClass
function addClass(el, className) {
if (hasClass(el, className)) {
return
}
let newClass = el.className.split(' ')
newClass.push(className)
el.className = newClass.join(' ')
}
removeClass
function removeClass (obj, cls) {
if (hasClass(obj, cls)) {
const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
obj.className = obj.className.replace(reg, ' ');
}
};
toggleClass
function toggleClass(obj, cls) {
if (hasClass(obj, cls)) {
removeClass(obj, cls);
} else {
addClass(obj, cls);
}
};
判断是否为移动端
/AppleWebKit.*Mobile/i.test(navigator.userAgent) || /Android|iPhone|Windows Phone|webOS|iPod|BlackBerry/i.test(navigator.userAgent)
判断是否滑动到底部
https://juejin.cn/post/7101271753255485448
// element 为可滑动元素
element.scrollHeight - element.clientHeight - element.scrollTop < 1
pipeAsyncFunctions
Performs left-to-right function composition for asynchronous functions.
为异步函数执行从左到右的函数组合。
const pipeAsyncFunctions = (...fns) => arg => fns.reduce((p, f) => p.then(f), Promise.resolve(arg))
const sum = pipeAsyncFunctions(
x => x + 1,
x => new Promise(resolve => setTimeout(() => resolve(x + 2), 1000)),
x => x + 3,
async x => (await x) + 4
)
sum(5).then(res => {
console.log("111", res)
})
promisify
Converts an asynchronous function to return a promise.
In Node 8+, you can use util.promisify
将异步函数转换为返回承诺。
const promisify = func => (...args) =>
new Promise((resolve, reject) =>
func(...args, (err, result) => (err ? reject(err) : resolve(result)))
);
const delay = promisify((d, cb) => setTimeout(cb, d));
delay(2000).then(() => console.log('Hi!')); // // Promise resolves after 2s
如何判定一个脚本已经执行完毕
暂无很好方法 有空在寻找
抽取单例逻辑
var getSingle = function (fn) {
var result
return function () {
return result || (result = fn.apply(this, arguments))
}
}
用法:
var createLoginLayer = function () {
var div = document.createElement('div')
div.innerHTML = 'aaaa'
document.body.appendChild(div)
return div
}
var createSingleLoginLayer = getSingle(createLoginLayer)
createSingleLoginLayer()
createSingleLoginLayer()
createSingleLoginLayer()
await多个async函数
await Promise.all([anAsyncCall(), thisIsAlsoAsync(), oneMore()])
使用对象解构来处理数组
可以使用对象解构的语法来获取数组的元素
const csvFileLine = '1997,John Doe,US,john@doe.com,New York';
const {2: country, 4: state} = csvFileLine.split(',');
console.log(country, ' --- ', state) //US --- New York
或者
const csvFileLine = '1997,John Doe,US,john@doe.com,New York,New York1,New York2';
const [, , country, , state] = csvFileLine.split(',');
console.log(country, ' --- ', state) //US --- New York
const ADDRESS = 'One Infinite Loop, Cupertino 95014';
const CITY_ZIP_CODE_REGEX = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
const [, city, zipCode] = ADDRESS.match(CITY_ZIP_CODE_REGEX) || [];
saveCityZipCode(city, zipCode);
P标签段首空两格
p{text-indent:2em}
&&优先级高于||
碰到与或运算最好是写上括号
var a = true, b = false, c = false;
console.log( a || b && c ) // true
console.log( (a || b) && c ) // false
console.log( a || (b && c) ) // true
»0
相当于
mround = function (r) {
return parseInt(Number(r)||0);
}
作用:就是取整
因为 位移运算只用于整数,如果带小数的数据参与位移运算会被取整 移动0位其实并没有移动,只是取整了
检测HTML字符串和ID字符串
以下是一个正则表达式,意在快速地检测字符串是不是HTML string or ID string
var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/
时间格式化
function parseTime(time, cFormat) {
if (arguments.length === 0) {
return null
}
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
let date
if (typeof time === 'object') {
date = time
} else {
if (('' + time).length === 10) time = parseInt(time) * 1000
date = new Date(time)
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
}
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
// result 匹配模式的字符串 如:{y}
// key 模式中的子表达式匹配的字符串 如:y 可以有 0 个或多个这样的参数
// 接下来的参数是一个整数,声明了匹配在 stringObject 中出现的位置。 stringObject.replace(regexp/substr,replacement)
// 最后一个参数是 stringObject 本身。
let value = formatObj[key]
if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1]
if (result.length > 0 && value < 10) {
value = '0' + value
}
return value || 0
})
return time_str
}
let res = parseTime(new Date())
console.log(res) //2018-07-22 22:08:23
深克隆deepclone
https://stackoverflow.com/questions/4459928/how-to-deep-clone-in-javascript
/**
* This is just a simple version of deep copy
* Has a lot of edge cases bug
* If you want to use a perfect deep copy, use lodash's _.cloneDeep
*/
export function deepClone(source) {
if (!source && typeof source !== 'object') {
throw new Error('error arguments', 'shallowClone')
}
let targetObj = source.constructor === Array ? [] : {}
Object.keys(source).forEach((key) => {
if (source[key] && typeof source[key] === 'object') {
targetObj[key] = deepClone(source[key])
} else {
targetObj[key] = source[key]
}
})
return targetObj
}
封装广播 broadcast
var broadcast = {
on: function(name, fn) {
var data = broadcast.data
if (data.hasOwnProperty(name)) {
data[name].push(fn)
} else {
data[name] = [fn]
}
return this
},
fire: function(name, data, thisArg) {
var fnList = broadcast.data[name] || []
for (i = 0, len = fnList.length; i < len; i++) {
fnList[i].apply(thisArg || null, [data, name])
}
return this
},
data: {}
}
// test
broadcast.on('showRiding', ()=>{console.log('showRiding')})
broadcast.fire('showRiding') // showRiding
使用Boolean过滤数组中的所有假值
我们知道JS中有一些假值:false,null,0,”“,undefined,NaN,怎样把数组中的假值快速过滤呢,可以使用Boolean构造函数来进行一次转换
const compact = arr => arr.filter(Boolean)
compact([0, 1, false, 2, '', 3, 'a', 'e' * 23, NaN, 's', 34]) // [ 1, 2, 3, 'a', 's', 34 ]
双位运算符 ~~
可以使用双位操作符来替代 Math.floor( )。双否定位操作符的优势在于它执行相同的操作运行速度更快。
Math.floor(4.9) === 4 //true
// 简写为:
~~4.9 === 4 //true
不过要注意,对整数来说 ~~ 运算结果与 Math.floor( ) 运算结果相同,而对于负数来说不相同:
~~4.5 // 4
Math.floor(4.5) // 4
~~-4.5 // -4
Math.floor(-4.5) // -5
取整 | 0
对一个数字| 0可以取整,负数也同样适用,num | 0
1.3 | 0 // 1
-1.9 | 0 // -1
~~ 转换成数字类型
其实是一种利用符号进行的类型转换,转换成数字类型
~~true == 1
~~false == 0
~~"" == 0
~~[] == 0
~~undefined ==0
~~!undefined == 1
~~null == 0
~~!null == 1
~是按位非,就是每一位取反, ~~常用来取整
比如 ~~10.2323=10
~~(10/3) = 3
数组形式接受Promise.all返回值
在下面的代码中,我们从/post中获取一个帖子,然后在/comments中获取相关评论。由于我们使用的是async/await,函数把返回值放在一个数组中。而我们使用数组解构后就可以把返回值直接赋给相应的变量。
async function getFullPost(){
return await Promise.all([
fetch('/post'),
fetch('/comments')
]);
}
const [post, comments] = getFullPost();
将url中query转为一个对象
实现效果
https://www.aaa.com?aaa=111&bbb=222 =》 {aaa: "111", bbb: "222"}
function param2Obj(url) {
const search = url.split('?')[1]
if (!search) {
return {}
}
// 将字符串转成json对象
return JSON.parse('{"' + decodeURIComponent(search).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"') + '"}')
}
console.log(param2Obj('https://www.aaa.com?aaa=111&bbb=222'))
// {aaa: "111", bbb: "222"}
判断是否dom对象
/**
* Checks if the passed element is dom object or not
*/
export const isDomElement = function (element) {
return element && typeof element === 'object' && 'nodeType' in element;
};
将string转化为Node节点
/**
* Turn a string into a node
* @param {String} htmlString to convert
* @return {Node} Converted node element
*/
const createNodeFromString = (htmlString) => {
const div = document.createElement('div');
div.innerHTML = htmlString.trim();
// Change this to div.childNodes to support multiple top-level nodes
return div.firstChild;
};
反转字符串
先将字符串转换为数组,待处理完后再将结果转换回字符串
function reverseStr(str) {
return str.split('').reverse().join('')
}
上述方法对 于包含复杂字符(Unicode,如星号、多字节字符等)的字符串并不适用
我们无法“借用”数组的可变更成员函数,因为字符串是不可变的:
var a = 'foo'
Array.prototype.reverse.call( a );
// 返回值仍然是字符串"foo"的一个封装对象 (这种办法是行不通的)
检查string是否中文
[\u4e00-\u9fa5 ] 这两个unicode值正好是Unicode表中的汉字的头和尾
let str = 'xxx'
/^[\u4E00-\u9FA5]+$/.test(str)
格式化数据生成key,value形式
将数组或对象数据转换成 [{key: “xxx”, val: “xxx”},{key: “xxx”, val: “xxx”}] 形式
function normalizeMap (map) {
return Array.isArray(map)
? map.map(key => ({key, val: key}))
: Object.keys(map).map(key => ({key, val: map[key]}))
}
normalizeMap(['a', 'b']) //[{key: "a", val: "a"},{key: "b", val: "b"}]
normalizeMap({a: 'aaa', b: 'bbb'}) //[{key: "a", val: "aaa"},{key: "b", val: "bbb"}]
new操作符和apply,bind同时使用
https://qinzhen001.github.io/2017/08/09/JS%E4%B8%ADcall%E5%92%8Capply%E5%92%8Cbind-myblog/
function newApply(Fn, argsAry) {
argsAry.unshift(null);
return new (Fn.bind.apply(Fn, argsAry));
}
// 调用
newApply(Cls.func, [1, 2, 3]) // well done !!
cached:记忆函数:缓存函数的运算结果
function cached(fn) {
let cache = Object.create(null);
return function cachedFn(str) {
let hit = cache[str];
return hit || (cache[str] = fn(str))
}
}
camelize:横线转驼峰命名
let camelizeRE = /-(w)/g;
function camelize(str) {
return str.replace(camelizeRE, function(_, c) {
return c ? c.toUpperCase() : '';
})
}
//ab-cd-ef ==> abCdEf
//使用记忆函数
let _camelize = cached(camelize)
hyphenate:驼峰命名转横线命名:拆分字符串,使用 - 相连,并且转换为小写
let hyphenateRE = /B([A-Z])/g;
function hyphenate(str){
return str.replace(hyphenateRE, '-$1').toLowerCase()
}
//abCd ==> ab-cd
//使用记忆函数
let _hyphenate = cached(hyphenate);
capitalize:字符串首位大写
function capitalize(str){
return str.charAt(0).toUpperCase() + str.slice(1)
}
// abc ==> Abc
//使用记忆函数
let _capitalize = cached(capitalize)
extend:将属性混合到目标对象中
function extend(to, _from) {
for(let key in _from) {
to[key] = _from[key];
}
return to
}
禁止右键、选择、复制
['contextmenu', 'selectstart', 'copy'].forEach(function(ev){
document.addEventListener(ev, function(event){
return event.returnValue = false
})
});
禁止某些键盘事件
document.addEventListener('keydown', function(event){
return !(
112 == event.keyCode || //F1
123 == event.keyCode || //F12
event.ctrlKey && 82 == event.keyCode || //ctrl + R
event.ctrlKey && 78 == event.keyCode || //ctrl + N
event.shiftKey && 121 == event.keyCode || //shift + F10
event.altKey && 115 == event.keyCode || //alt + F4
"A" == event.srcElement.tagName && event.shiftKey //shift + 点击a标签
) || (event.returnValue = false)
});
按循序执行 Promise 任务
(结合reduce)
/**
* 按循序执行 Promise 任务
* @param {Array} options.tasks 要执行的任务队列
* @param {Host} options.thisIns 宿主实例
* @param {*} options.arg 透传的参数
*/
function sequenceTasks({ tasks, thisIns, arg }) {
function recordValue(results, value) {
results.push(value);
return results;
}
const pushValue = recordValue.bind(null, []);
return tasks.reduce(
(promise, task) => promise
.then(() => task.apply(thisIns, arg))
.then(pushValue),
Promise.resolve(),
);
}
利用bind预设函数参数
function recordValue(results, value) {
results.push(value);
return results;
}
const pushValue = recordValue.bind(null, []);
pushValue('111')
pushValue('222')
pushValue('333')
let res = pushValue('444')
console.log(res) //["111", "222", "333", "444"]
bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。
语法:
function.bind(thisArg[, arg1[, arg2[, ...]]])
thisArg:
调用绑定函数时作为this参数传递给目标函数的值。 如果使用new运算符构造绑定函数,则忽略该值。当使用bind在setTimeout中创建一个函数(作为回调提供)时,作为thisArg传递的任何原始值都将转换为object。如果bind函数的参数列表为空,执行作用域的this将被视为新函数的thisArg。
arg1, arg2, …:
当目标函数被调用时,预先添加到绑定函数的参数列表中的参数。
bind() 函数会创建一个新绑定函数(bound function,BF)。绑定函数是一个exotic function object(怪异函数对象,ECMAScript 2015中的术语),它包装了原函数对象。调用绑定函数通常会导致执行包装函数。
利用Promise控制异步并发流程
这个流程非常流弊,和我之前写的缓存resolve差不多。
class Login {
static _loginSingleton = null; //正在进行的登录流程
static async _login(){
//登录流程...
}
//封装了免并发逻辑的登录函数
static async login(){
if (Login._loginSingleton) //若当前有登录流程正在进行,则直接使用其结果作为本次登录结果
return Login._loginSingleton;
//否则触发登录流程
Login._loginSingleton = Login._login();
//并在登录结束时释放并发限制
Login._loginSingleton.then(()=>{Login._loginSingleton = null}).catch(()=>{Login._loginSingleton = null});
//返回登录结果
return Login._loginSingleton;
}
}
这里的亮点在Login._loginSingleton.then 相当于监听了这个Promise的then
如以上代码所示,利用Promise可以被多次then/catch的特性(亦即,一个async函数调用结果可以被await多次),可以使用一个Promise来记录当前登录流程,后续调用直接对该Promise进行监听。
这样,就可以实现登录流程免并发了。
缓存resolve控制异步并发流程
global.singleton[AD_LIST_KEY] 将resolve缓存起来,这样后面的并法流程就不用发起网络请求,直接采用第一次发起网络请求拿到的响应结果。
async function getAdListByRequest(data = {}) {
return new Promise(async (resolve, reject) => {
global.singleton = global.singleton || {};
global.singleton[AD_LIST_KEY + 'lock'] = global.singleton[AD_LIST_KEY + 'lock'] || false;
if (global.singleton[AD_LIST_KEY + 'lock']) {
global.singleton[AD_LIST_KEY].push(resolve);
} else {
global.singleton[AD_LIST_KEY + 'lock'] = true;
// console.log('singleton start', '1');
global.singleton[AD_LIST_KEY] = [resolve];
_getAdListForPure(data).then((output) => {
// ....
let func = {};
// eslint-disable-next-line
while (func = global.singleton[AD_LIST_KEY].shift()) {
func(output.data.data);
// console.log('singleton end', output);
}
global.singleton[AD_LIST_KEY + 'lock'] = false;
}, rej => {
reject(rej);
});
}
});
}
利用Object.defineProperty函数赋值
let obj = {
aaa: 'aaa'
}
obj.aaa = Object.defineProperty(function () {
console.log('this', this)
console.log('aaaa')
},
'xxxx',
{value: true}
)
console.log(obj.aaa()) //this {aaa: ƒ} aaaa
console.log(obj.aaa)
/**
* () => {
* console.log('this', this)
* console.log('aaaa')
* }
*/
console.log(obj.aaa.xxxx) //true
theHost[funName] = Object.defineProperty(
function pluggableHookFun(...arg) {
const _theHost = this;
if (typeof beforeCall === 'function') beforeCall({ theHost: _theHost });
const funQueue = _theHost.getHookFunQueue(funName);
// 如果不存在,则 resolve
if (!funQueue || !Array.isArray(funQueue)) return Promise.resolve();
// 以 「先进先出」 的形式按顺序执行 Promise 链,未捕捉的错误,扔到 onError 去。
return sequenceTasks({
tasks: funQueue,
thisIns: _theHost,
arg,
}).catch((err) => {
if (typeof _theHost.onError === 'function') {
_theHost.onError(err);
}
throw err;
});
},
'isPluggableHookFun', { value: true },
);
html转纯文本 (去掉标签)
const htmlToString = (str) => {
let divEle = document.createElement('div')
divEle.innerHTML = str
return divEle.innerText
}
let str = "<p>qwe<span>asd</span>qwe</p>"
console.log(htmlToString(str)) //qweasdqwe
利用innerText这个属性,非常秀
hasOwnProperty
不要用点.或者方括号 [ ] 判断对象中是否有某属性
举一个错误的例子
let obj = {
aaa: ''
}
if (obj.aaa) {
console.log("含有此属性") //无法打印
}
正确的做法时使用hasOwnProperty
let obj = {
// aaa: null
// aaa:""
aaa: undefined
}
if (obj.hasOwnProperty("aaa")) {
// 上面三种情况 均可以console到
console.log("含有此属性")
}
获取元素节点中的文字
<p id="intro">Hello World!</p>
<script type="text/javascript">
x = document.getElementById("intro");
console.log(x.firstChild.nodeValue); // Hello World!
</script>
可以看到,只有自身存在该属性时,才会返回true。适用于只判断自身属性的场景。
(a&3)==(a%4)
表达式(a&3)==(a%4)的值是1
因为无论a的值为多少a&3的结果只保留a的最后2位
最多接受n个参数的函数
https://www.30secondsofcode.org/snippet/ary
Creates a function that accepts up to n arguments, ignoring any additional arguments.
创建一个最多接受n个参数的函数,忽略任何其他参数。
const ary = (fn, n) => (...args) => fn(...args.slice(0, n));
EXAMPLES
const firstTwoMax = ary(Math.max, 2);
[[2, 6, 'a'], [6, 4, 8], [10]].map(x => firstTwoMax(...x)); // [6, 6, 10]
extend
https://github.com/binnng/wx-component/blob/master/src/extend.js
最近在研究小程序时发现了一个extend方法,函数用于将一个或多个对象的内容合并到目标对象,它其实是jQuery.extend(),这里简单分析一下。
语法
$.extend( target [, object1 ] [, objectN ] )
指示是否深度合并
$.extend( [deep ], target, object1 [, objectN ] )
参数 | 描述 |
---|---|
deep | 可选。 Boolean类型 指示是否深度合并对象,默认为false。如果该值为true,且多个对象的某个同名属性也都是对象,则该”属性对象”的属性也将进行合并。 |
target | Object类型 目标对象,其他对象的成员属性将被附加到该对象上。 |
object1 | 可选。 Object类型 第一个被合并的对象。 |
objectN |
var extend = function extend() {
var target = arguments[0] || {};
var i = 1;
var length = arguments.length;
var deep = false;
var options, name, src, copy, copyIsArray, clone;
// Handle a deep copy situation
if (typeof target === 'boolean') {
deep = target;
target = arguments[1] || {};
// skip the boolean and the target
i = 2;
}
// Handle case when target is a string or something (possible in deep copy)
if (typeof target !== 'object' && !is.fn(target)) {
target = {};
}
for (; i < length; i++) {
// Only deal with non-null/undefined values
options = arguments[i];
if (options != null) {
if (typeof options === 'string') {
options = options.split('');
}
// Extend the base object
for (name in options) {
src = target[name];
copy = options[name];
// Prevent never-ending loop
if (target === copy) {
continue;
}
// Recurse if we're merging plain objects or arrays
if (deep && copy && (is.hash(copy) || (copyIsArray = is.array(copy)))) {
if (copyIsArray) {
copyIsArray = false;
clone = src && is.array(src) ? src : [];
} else {
clone = src && is.hash(src) ? src : {};
}
// Never move original objects, clone them
target[name] = extend(deep, clone, copy);
// Don't bring in undefined values
} else if (typeof copy !== 'undefined') {
target[name] = copy;
}
}
}
}
// Return the modified object
return target;
};
获取函数名称
Function.prototype.getName = function(){
return this.name || this.toString().match(/function\s*([^(]*)\(/)[1]
}
+new Date()
https://www.cnblogs.com/zhengziheng/p/9734196.html
+相当于把这个时间对象做了隐式的类型转换啊
console.log(new Date())
// Fri Nov 15 2019 14:19:36 GMT+0800 (中国标准时间)
console.log(+new Date())
// 1573798776557
一个同步流只调用一次函数
/**
* 节流,一个同步流中只调用一次该函数
*/
const waitFuncSet = new Set()
function throttle(func) {
return () => {
if (waitFuncSet.has(func)) return
waitFuncSet.add(func)
Promise.resolve().then(() => {
if (waitFuncSet.has(func)) {
waitFuncSet.delete(func)
func()
}
}).catch(() => {
// ignore
})
}
}
获取不到值时初始化
let E = function(){}
E.prototype = {
on:function(){
// this.e不存在时 初始化this.e
let e = this.e || (this.e = {})
}
}
简单开始造一个库
class ClipboardAction {
constructor(options) {
// 解析 options
this.resolveOptions(options);
// 初始化的相关操作
this.initSelection();
}
/**
* Defines base properties passed from constructor.
* @param {Object} options
*/
resolveOptions(options = {}) {
this.action = options.action;
this.container = options.container;
this.emitter = options.emitter;
this.target = options.target;
this.text = options.text;
this.trigger = options.trigger;
this.selectedText = '';
}
}
判断 -0
如何判断一个元素是不是-0,使用Object.is()
let num = 0
let res = Object.is(num, -0);
console.log("res",res) // false
htmlEncode
转码html 防止xss攻击
function htmlEncode(text) {
return document.createElement('a').appendChild( document.createTextNode(text) ).parentNode.innerHTML;
}
位运算控制权限
位运算关系到vue3的组合静态标记中的patchFlag和react源码中的EventFlag,一定要搞明白,而且位运算本身就是做组合权限教研的最佳实践,按位或授权,按位与校验权限,除了理解vue3和react源码外,我们做组件开发的时候也用的到
let TEXT = 1
let CLASS = 1 << 2
let PROPS = 1 << 3
// 授权 TEXT和CLASS
let permisson = TEXT | CLASS
let hasTextPermisson = !!(permisson & TEXT)
console.log('是否有TEXT权限',hasTextPermisson)
let hasClassPermisson = !!(permisson & CLASS)
console.log('是否有CLASS权限',hasClassPermisson)
let hasPropsPermisson = !!(permisson & PROPS)
console.log('是否有PROPS权限',hasPropsPermisson)
删除字符串中的某一个字符
如果我们只想删除字符串中一个指定字符,该如何做?
let str = "aabbccddeetgbnghbb";
str = str.replace("b", ""); // 'aabccddeetgbnghbb'
有一个缺点:
- 只能删除第一次遇到指定的字符
- 如果要删除第二次遇到指定的字符,可以把string转array,两次indexOf找到下标,再删除
获取字符串中某字符第二次出现的下标
var res = "a-b-c-d";
var index = find(res,'-',1); //字符串res中第二个‘-’的下标
var ress = res.substring(index); //ress的值为 "-c-d"
function find(str,cha,num){
var x=str.indexOf(cha);
for(var i=0;i<num;i++){
x=str.indexOf(cha,x+1);
}
return x;
}
判断元素是否在视窗之内
首先,我们监听滚动事件
window.addEventListener('scroll', xxx)
// 在xxx函数中判断元素是否在视窗之中
function isInViewPort(element) {
const viewWidth = window.innerWidth || document.documentElement.clientWidth;
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
const {
top,
right,
bottom,
left,
} = element.getBoundingClientRect();
return (
top >= 0 &&
left >= 0 &&
right <= viewWidth &&
bottom <= viewHeight
);
}
getBoundingClientRect
https://developer.mozilla.org/zh-CN/search?q=getBoundingClientRect
getBoundingClientRect是会返回元素的大小和它相对于视窗的位置,即,除了大小(width和height)其余的属性都是相对于视窗的左上角位置而言的,所以当存在滚动时,位置有可能是负值,还有一点,这些都是只读属性。
暂停一会再执行
const start = performance.now()
// 暂停 8ms
while (performance.now() - start < 8) {
console.log(performance.now(), start)
}
console.log('继续执行')
cacheFunction 缓存函数结果
https://juejin.cn/post/6994976281053888519
const cacheStringFunction = (fn) => {
const cache = Object.create(null);
return ((str) => {
const hit = cache[str];
return hit || (cache[str] = fn(str));
});
};
例子:
// 首字母转大写
const capitalize = cacheStringFunction((str) => str.charAt(0).toUpperCase() + str.slice(1));
hasChanged 判断是不是有变化
// compare whether a value has changed, accounting for NaN.
const hasChanged = (value, oldValue) => value !== oldValue && (value === value || oldValue === oldValue);
例子:
// 认为 NaN 是不变的
hasChanged(NaN, NaN); // false
hasChanged(1, 1); // false
hasChanged(1, 2); // true
// 场景
// watch 监测值是不是变化了
def 定义对象属性
const def = (obj, key, value) => {
Object.defineProperty(obj, key, {
configurable: true,
enumerable: false,
value
});
};
精度收拢
const eps = 1e-4 // 0.0001
number = Math.floor(number + eps);
网络图片转换为 Blob 对象
//将图片 URL 转换为 Blob 对象,此示例代码暂不支持在 Internet Explorer 上运行
function canvasToDataURL(canvas, format, quality) {
return canvas.toDataURL(format || "image/jpeg", quality || 1.0);
}
function dataURLToBlob(dataurl) {
var arr = dataurl.split(",");
var mime = arr[0].match(/:(.*?);/)[1];
var bstr = atob(arr[1]);
var n = bstr.length;
var u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
}
function imageToCanvas(src, cb) {
var canvas = document.createElement("CANVAS");
var ctx = canvas.getContext("2d");
var img = new Image();
img.src = src;
img.onload = function () {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
cb(canvas);
};
}
function imageToBlob(src, cb) {
imageToCanvas(src, function (canvas) {
cb(dataURLToBlob(canvasToDataURL(canvas)));
});
}
Blob 对象转换为图片url
//将 Blob 对象转换为图片url
function fileOrBlobToDataURL(obj, cb) {
var a = new FileReader();
a.readAsDataURL(obj);
a.onload = function (e) {
cb(e.target.result);
};
}
function blobToImageUrl(blob, cb) {
fileOrBlobToDataURL(blob, function (dataurl) {
cb(dataurl);
});
}
Location change
on location changed event / on url changed event
监听浏览器的url改变
- when URL is changed in the browser address bar by the user, the whole page is reloaded and there are executed two operations: unload and load page - it is just opening a new page - we can use
onload
event to do some stuff (check this article), - when
location
is changed by History API (pushState
,replaceState
,popState
) in the source code, the page is not reloaded. In this case, API doesn’t provide anylocationchange
event - it is necessary to trigger some events manually after the operation
(function () {
var pushState = history.pushState;
var replaceState = history.replaceState;
history.pushState = function () {
pushState.apply(history, arguments);
window.dispatchEvent(new Event("pushstate"));
window.dispatchEvent(new Event("locationchange"));
};
history.replaceState = function () {
// 这里 arguments 和别的不一致
replaceState.apply(history, arguments);
window.dispatchEvent(new Event("replacestate"));
window.dispatchEvent(new Event("locationchange"));
};
window.addEventListener("popstate", function () {
window.dispatchEvent(new Event("locationchange",{detail:"detail"}));
});
})();
使用:
// Usage example:
window.addEventListener("locationchange", function () {
// 没法办法获取一致的参数 我们可以在这里获取 url
var url = window.location.href;
console.log("onlocationchange event occurred! ",url);
});
全局唯一config
class SceneBuilderConfig {
private static _config: SceneBuilderConfig | null;
constructor(
public companyId: string,
public accessToken: string,
public refreshToken: string
) {
//@ts-ignore
window.SceneBuilderConfig = this;
}
static setConfig(config: SceneBuilderConfig | null) {
this._config = config;
}
static get shared() {
if (!this._config) {
throw new Error("config not ready");
}
return this._config;
}
}