晴明的博客园 GitHub      CodePen      CodeWars     

[js] 积累 (2)

js中的值对比

js中的值对比

Date处理

获取当前Unix时间,三种写法等效

new Date().getTime()

Date.now()

+new Date();

es5、es6边界

es5

bind
every
Date.now()
trim
filter

es6

find

数字处理

num类型可为number 或 string :

~num      num取反并减1 。

!!num      如果num为0或者falsy则为false,其他数值或truthy为true。

+num       实现Number(num)效果,也可用于Date处理。

~~num      可以将不是number类型的数据转成0,正数实现Math.floor(num)的效果,负数实现Math.ceil(num)的效果。

num | 0    可以将不是number类型的数据转成0,正数实现Math.floor(num)的效果,负数实现Math.ceil(num)的效果。

>>> num   得到一个 32-bit unsigned ints。

不过js的位运算性能很差,不要用在频繁计算中。

数组处理

快速浅复制

let a = [1,2,3];

let b = a.concat();

let c = [].concat(a);

数组找值

let arr = [2, 5, 8, 1, 4]

// filter 实现
arr.filter(item => {
    return item > 10
}) 
// []
arr.filter(item => {
    return item > 5
}) 
// [8]

// find 实现
arr.find(item => {
    return item > 10
})  
// undefined
arr.find(item => {
    return item > 5
})  
// 8

// some 实现
arr.some(item => {
    return item > 10
}) 
// false
arr.some(item => {
    return item > 5
}) 
// true

复制

浅复制只复制一层对象的属性,而深复制则递归复制了所有层级。

浅复制

array

let a = [1,2,3];

let b = a.concat();
//或
let b = [].concat(a);

//或
let b = a.slice(0);

//或
let b = [...a];

object

let a = { a: 'a', b: 'b', c: 'c' };

let b = Object.assign({}, a);

//或
let b = { ...a };

array/object

  function shallowCopy(value) {
    var temp;
    if (Object.prototype.toString.call(value) == '[object Object]') {
      temp = {};
    } else if (Object.prototype.toString.call(value) == '[object Array]') {
      temp = [];
    } else {
      return value;
    }
    for (var k in value) {
      temp[k] = value[k];
    }
    return temp;
  }

深复制

最简单的深复制

b = JSON.parse( JSON.stringify(a) )

缺点是

  1. 无法复制函数
  2. 原型链没了,对象就是object,所属的类没了。

array/object

  function deepCopy(value) {
    //不考虑循环引用问题
    var temp;
    var isArray = function (v) {
      return Object.prototype.toString.call(v) == '[object Array]'
    }
    var isObject = function (v) {
      return Object.prototype.toString.call(v) == '[object Object]'
    }
    if (isObject(value)) {
      temp = {};
    } else if (isArray(value)) {
      temp = [];
    } else {
      return value;
    }
    for (var k in value) {
      temp[k] = deepCopy(value[k]);
    }
    return temp;
  }

布尔

JavaScript Boolean 对象是一个布尔值的对象包装器

new Boolean([value])结果为 false 的 value 有:

0、-0、null、false、NaN、undefined、"" 

其余的结果为 true

当 Boolean 对象直接应用于条件语句,任何不是 undefined 和 null 的对象,包括值为 false 的 Boolean 对象,都会被当做 true 来对待
所以 if (new Boolean(false)) { .... } 里面的代码是会执行的...

IE检测

const detectIE = () => {
    let ua = window.navigator.userAgent;
    let msie = ua.indexOf('MSIE ');
    let trident = ua.indexOf('Trident/');
    let edge = ua.indexOf('Edge/');
    if (msie > 0) {
        if (ua.indexOf('MSIE 1') > 0) {
            // IE 10
            return 10;
        } else {
            // older
            return 9;
        }
    } else if (trident > 0) {
        // IE 11
        return 11;
    } else if (edge > 0) {
        // Edge
        return 'edge';
    } else
        // other browser
        return false;
};

document.documentMode 可用于检测IE的版本,非IE为undefined .

获取url上的所有参数

  let ourl = req.originalUrl;
  let url = '';
  let index = ourl.indexOf('?');
  if (index > 0) {
    url += ourl.substr(index);
  }
  res.redirect(`/example/list${url}`);

柯里化

function currying(fn, n) {
  return function (m) {
    return fn.call(this, m, n);
  };
}

function tailFactorial(n, total) {
  if (n === 1) return total;
  return tailFactorial(n - 1, n * total);
}

const factorial = currying(tailFactorial, 1);

factorial(5) // 120

modal弹框的滚动条处理问题

  1. 弹框时将body的overflow设为hidden
  2. 阻止滚动事件,这个处理起来会比较复杂一些

getComputedStyle

IE9+
不同于xx.style只能获取style属性上的属性,getComputedStyle可以获取到(style/css)最终的属性.

let elem1 = document.getElementById("elemId");
let style = window.getComputedStyle(elem1, null);
let visible = style.getPropertyValue('visibility');

// 它等价于
// let style = document.defaultView.getComputedStyle(elem1, null);
// 一般情况下 document.defaultView === window //true
// document.defaultView是为了兼容极小情况下的某些浏览器(如Firefox 3.6)

函数队列

一般用在重复触发但是需要顺序执行的场景,如动画

      class Queue {
        constructor() {
          this.data = [];
        }
        runner() {
          if (this.data.length > 0 && this.data[0].state != 'running') {
            this.data[0].state = 'running';
            this.data[0].fn();
          }
        }
        inqueue(fn) {
          this.data.push({ state: 'waiting', fn: fn });
          this.runner();
        }
        dequeue() {
          this.data.shift();
          this.runner();
        }
      }

进出场动画

transition: 
hidden -> visible 有效, 
visible -> hidden 无效,需要js修正.
none -> block 无效,需要js修正, 
block -> none 无效,需要js修正.

animation: 
hidden -> visible 有效, 
visible -> hidden 无效,需要js修正.
none -> block 有效, 
block -> none 无效,需要js修正.

DOM事件

focus 是选中控件本身.
select 是选中控件中的内容,比如文本框中的文本.

requestIdleCallback

IE不支持
var handle = window.requestIdleCallback(callback[, options])
会在浏览器空闲时期依次调用函数,可以让开发者在主事件循环中执行后台或低优先级的任务,而且不会对像动画和用户交互这样延迟触发而且关键的事件产生影响。函数一般会按先进先调用的顺序执行,除非函数在浏览器调用它之前就到了它的超时时间。

当关注用户体验,不希望因为一些不重要的任务(如统计上报)导致用户感觉到卡顿的话,就应该考虑使用requestIdleCallback。因为requestIdleCallback回调的执行的前提条件是当前浏览器处于空闲状态。

requestAnimationFrame的回调会在每一帧确定执行,属于高优先级任务,而requestIdleCallback的回调则不一定,属于低优先级任务。

不推荐在requestIdleCallback里进行DOM修改操作,Promise的resolve(reject)操作也不建议放在里面,因为Promise的回调会在idle的回调执行完成后立刻执行,会拉长当前帧的耗时,所以不推荐。

推荐的做法是在requestAnimationFrame里面做dom的修改,可以在requestIdleCallback里面构建Document Fragment,然后在下一帧的requestAnimationFrame里面应用Fragment。

推荐放在requestIdleCallback里面的应该是小块的(microTask)并且可预测时间的任务。

requestIdleCallback(myNonEssentialWork, { timeout: 2000 });

function myNonEssentialWork (deadline) {
  // deadline.timeRemaining()可以获取到当前帧剩余时间
  // 当回调函数是由于超时才得以执行的话,deadline.didTimeout为true
  while ((deadline.timeRemaining() > 0 || deadline.didTimeout) &&
         tasks.length > 0) {
       doWorkIfNeeded();
    }
  if (tasks.length > 0) {
    requestIdleCallback(myNonEssentialWork);
  }
}

history

history.length历史记录数.

history.state返回当前页面的state对象。

history.back()移动到上一个访问页面,等同于浏览器的后退键。

history.forward()移动到下一个访问页面,等同于浏览器的前进键。

history.go()接受一个整数作为参数,移动到该整数指定的页面,比如go(1)相当于forward(),go(-1)相当于back(),go(0)相当于刷新当前页面。。
返回上一页时,页面通常是从浏览器缓存之中加载,而不是重新要求服务器发送新的网页。

history.pushState(state,title,url)
pushState方法不会触发页面刷新,只是导致history对象发生变化,地址栏会有反应。
如果pushState的url参数,设置了一个新的锚点值(即hash),并不会触发hashchange事件。如果设置了一个跨域网址,则会报错。

var stateObj = { foo: 'bar' };
history.pushState(stateObj, 'page 2', '2.html');

history.replaceState()
参数与pushState方法一模一样,区别是它修改浏览历史中当前纪录。
被replace前的历史记录不会被保存.

popstate
每当同一个文档的浏览历史(即history对象)出现变化时,就会触发popstate事件。
但仅仅调用pushState方法或replaceState方法 ,并不会触发该事件,只有用户点击浏览器倒退按钮和前进按钮,或者使用 JavaScript 调用back、forward、go方法时才会触发。另外,该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件也不会触发。
页面第一次加载的时候,浏览器不会触发popstate事件。

window.onpopstate = function (event) {
  console.log('location: ' + document.location);
  console.log('state: ' + JSON.stringify(event.state));
};

// 或者
window.addEventListener('popstate', function(event) {
  console.log('location: ' + document.location);
  console.log('state: ' + JSON.stringify(event.state));
});

URLSearchParams可用于查询url的参数,但兼容性较差.

location

location的兼容性比history好很多

window.history.pushState(),window.location.assign(),
window.location.href='xx',window.location='xx' 四者功能相近

window.history.replaceState()类似window.location.replace()

window.location.reload(true)刷新

window.location.search会获取到url参数,包括?
取url参数某个键值

function loadPageVar (sVar) {
  return decodeURI(window.location.search.replace(new RegExp("^(?:.*[&\\?]" + encodeURI(sVar).replace(/[\.\+\*]/g, "\\$&") + "(?:\\=([^&]*))?)?.*$", "i"), "$1"));
}
//console.log(loadPageVar("name"));

window.location.hash获取#后的值包括#.

encodeURI decodeURI

encodeURIdecodeURI 函数操作的是完整的 URI;
这两函数假定 URI 中的任何保留字符都有特殊意义,所有不会编码它们。

encodeURIComponentdecodeURIComponent 函数操作的是组成 URI 的个别组件;
这两函数假定任何保留字符都代表普通文本,所以必须编码它们,所以它们(保留字符)出现在一个完整 URI 的组件里面时不会被解释成保留字符了。

更详细可以参考这张图

小数精度问题

在采用 IEEE754 浮点数标准的语言中都有IEEE754 浮点数精度丢失的问题.
并不是所有的十进制小数能被二进制表示.

var sum = 0;
for(var i = 0; i < 10; i++) {
  sum += 0.1;
}
console.log(sum); //0.9999999999999999

0.1+0.2 //0.30000000000000004

0.2+0.3 //0.5

简单的手势事件模拟

      function touchEvent(type, Selector, func) {
				var startX;
				var startY;
				var offset = 20;
				var hanlder = function() {
					Selector.removeEventListener('touchmove', touchMove);
					func();
				};
				var touchMove = function(e) {
					var distanceX = startX - e.changedTouches[0].clientX;
					var distanceY = startY - e.changedTouches[0].clientY;
					var absX = Math.abs(distanceX);
					if ((absX > Math.abs(distanceY)) && absX > offset) {
						if (type == 'left' && distanceX > 0) {
							hanlder();
						} else if (type == 'right' && distanceX < 0) {
							hanlder();
						}
					}
				};
				var touchStart = function(e) {
					if (e && e.changedTouches && e.changedTouches[0]) {
						startX = e.changedTouches[0].clientX;
						startY = e.changedTouches[0].clientY;
						Selector.addEventListener('touchmove', touchMove);
					} else {
						console.error('该设备不支持touch事件')
					}
				};
				Selector.addEventListener('touchstart', touchStart);
			}

js更改伪元素样式

//获取
var div=document.querySelector('div');
var fontSize=window.getComputedStyle(div,'::before').getPropertyValue('font-size');//获取before伪元素的字号大小

//更改
document.styleSheets[0].insertRule('.test::before{color:green}',0)//chrome,firefox等非IE浏览器使用
document.styleSheets[0].addRule('.test::before{color:green}',0)//IE系列浏览器使用
/* 虽然部分浏览器也可以通过id来指定,'document.styleSheets.id.insertRule()'这种写法在chrome和IE下都行得通,但是firefox会返回'undefined',所以建议还是使用index值来获取stylesheet */

//或
var style=document.createElement('style');
style.innerHTML=".test::before{color:green}";
document.head.appendChild(style);

奇葩

parseInt(0.1+0.2-0.3) //5

console.log([1]+[2]) //12

console.log([1]+[1]-[1]) //10

parseInt(0.000001) 的过程为 0.000001.toString()(结果为 "0.000001")然后再 parseInt("0.000001")(结果为 0)

parseInt(0.000001) //0

0.0000001.toString() 的结果是 1e-7

parseInt(0.0000001)

console里的动画

      let x = 1, up = !1;
      setInterval(() => {
        (75 === x || 1 === x) && (up = !up);
        up ? x++ : x--;
        console.log(`%c${`■`.repeat(x)}`, `color: hsl(${2 * x}, 100%, 50%)`);
      }, 300);

可以用setProperty设置自定义属性

el.style.驼峰属性名
el.style.setProperty('css属性', '值')
el.style.getProperty('css属性')
el.style.removeProperty('css属性')

btn.style.setProperty('--x', x + 'px')

el.style.cssText = "width: 200px; height: 200px; background: red;"

babel-register模块改写require命令,为它加上一个钩子。
此后,每当使用require加载.js、.jsx、.es和.es6后缀名的文件,就会先用 Babel 进行转码。
需要注意的是,babel-register只会对require命令加载的文件转码,而不会对当前文件转码。
另外,由于它是实时转码,所以只适合在开发环境使用。


一个元素的 scrollTop 值是这个元素的顶部到它的最顶部可见内容(的顶部)的距离的度量。当一个元素的内容没有产生垂直方向的滚动条,那么它的 scrollTop 值为0。(可用于判断是否有滚动条)


查看view-source会重新请求页面


属性[disabled] :disabled会阻止点击事件


contains 可用于判断是否包含某个DOM元素


'z'.charCodeAt(0) - 'a'.charCodeAt(0)//25
可用于获取a-z字母的索引.


可利用Set 天然去重


快速寻找数组中的最大值

      Math.max.apply(null, arr);
      Math.max(...arr);

try {
    tryCode - 尝试执行代码块
}
catch(err) {
    catchCode - 捕获错误的代码块
}
finally {
    finallyCode - 无论 try / catch 结果如何都会执行的代码块
}

(chrome)html里js直接使用一个未声明的变量,似乎会命中dom中的id元素...很奇怪的行为....


方法链

class Car {
  constructor() {
    this.make = 'Honda';
    this.model = 'Accord';
    this.color = 'white';
  }

  setMake(make) {
    this.name = name;
    // NOTE: Returning this for chaining
    return this;
  }

  setModel(model) {
    this.model = model;
    // NOTE: Returning this for chaining
    return this;
  }

  setColor(color) {
    this.color = color;
    // NOTE: Returning this for chaining
    return this;
  }

  save() {
    console.log(this.make, this.model, this.color);
  }
}

let car = new Car()
  .setColor('pink')
  .setMake('Ford')
  .setModel('F-150')
  .save();

pc上单个资源域名并发是6个请求,2个不同的域名加起来是12个请求。
手机上单个资源域名并发是4个, 2个不同的域名就是 8个请求。


switch case 里使用正则的3种写法
需要注意的是case 里直接值的比较似乎是===比较的

 let a = '1';
      switch (true) {
        case /^1|2$/.test(a):
          console.log('1')
          break;
        case /^3$/.test(a):
          console.log('2')
          break;
      }
      switch (a) {
        case (a.match(/^1|2$/) || {}).input:
          console.log('11')
          break;
        case (a.match(/^3$/) || {}).input:
          console.log('22')
          break;
      }
      switch (a) {
        case '1':
          console.log('111')
          break;
        case /^2$/.test(a) && a:
          console.log('222')
          break;
        case (a.match(/^3$/) || {}).input:
          console.log('333')
          break;
      }

event.stopImmediatePropagation()
如果有多个相同类型事件的事件监听函数绑定到同一个元素,当该类型的事件触发时,它们会按照被添加的顺序执行。
如果其中某个监听函数执行了 event.stopImmediatePropagation() 方法,则当前元素剩下的监听函数将不会被执行。

在事件代理里,当某个元素绑定了事件,如果还想再在他的子元素里绑定相同的事件,并且触发子元素时只执行子元素的事件,判断会变得比较麻烦,使用event.stopPropagation()也是无效的,
可以通过event.stopImmediatePropagation()来较简单的实现,但是需要注意的是绑定事件的顺序,必须是子元素先执行,谁先执行,就不会再执行相同事件了(其实也可能有一定的隐患).


document.body 返回html dom中的body节点 即<body>

document.documentElement 返回html dom中的root 节点 即<html>

document.body.clientHeight获取包括滚动条的整体高度
document.documentElement.clientHeight视口高度

对获取滚动条高度的兼容处理

var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
let ele = document.documentElement || document.body.parentNode;
ele = (ele && typeof ele.scrollTop == 'number') ? ele : document.body;
let sTop = ele.scrollTop;

css样式
pointer-events:none //可以禁止点击操作,包括不触发js点击事件

自定义选择器的使用

'.xx[data-key="1"]'//注意数字必须用引号
'.yy[data-key=abc]'

click()在手机上主动触发无效,因为触发的是touch

jq delegate 直接实现事件代理


base64加密/解密

let encodedData = window.btoa("Hello, world"); // base64加密
let decodedData = window.atob(encodedData);    // base64解密

posted @ 2018-02-08 22:27  晴明桑  阅读(217)  评论(0编辑  收藏  举报