短小精悍的js基础代码

Posted by Qz on December 17, 2017

“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

25个你不得不知道的数组reduce高级用法

数组分割

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 的运算符优先级 、宽松相等(即 ==)的判断过程以及强制类型转换

  1. 等号右边有 ! ,优先级比 == 更高,优先计算右边的结果。 [] 为非假值,所以右边的运算结果为 false,即:

    ![] ==> false // 此处表示转换过程,下同

  2. == 的任意一边有 boolean 类型的值时先把这个值转换成 number 类型,右边转换成了 0 ,即:

    Number(false) ==> 0

  3. == 的两边分别是 number 和 object 类型的值时,把 object 转换成 number 类型,需要对 object 进行 ToNumber 操作,即:

    Number([].valueOf()) ==> 0

  4. 至此,== 两边的值都变成 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']

解析:

  1. str.slice(0, i) + str.slice(i + 1) 除去了循环中的当前元素letter 数组相加变成新的变量
  2. 进行递归直到字符串长度等于2或1
  3. [str, str[1] + str[0]] : [str] 把字符串转化为数组
  4. 最后数组用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的组合静态标记中的patchFlagreact源码中的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改变

  1. 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),
  2. 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 any locationchange 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;
  }
}