JS常用snippet

跨域处理

// 第一步创建script标签
var scriptStart = document.createElement('script');
// 第二步创建指定script标签的src路径
scriptStart.src = 'http://www.baidu.com';
// 将script标签插入到页面中
document.body.insertBefore(scriptStart, document.body.firstChild);

px 和 rem 的转换

<script>
(function () {
document.addEventListener('DOMContentLoaded', function () {
var html = document.documentElement;
var windowWidth = html.clientWidth;
     if(windowWidth > 640) windowWidth = 640;
if(windowWidth < 320) windowWidth = 320;
html.style.fontSize = windowWidth / 7.5 + 'px';
}, false);
})();
</script>

检测屏幕宽度自动刷新

function hengshuping() {  
  window.location.reload();
}
window.addEventListener("onorientationchange" in window ? "orientationchange" : "resize", hengshuping, false);

手机浏览器控制台

//手机浏览器控制台
(function () {
  var script = document.createElement('script');
  script.src="http://eruda.liriliri.io/eruda.min.js";
  document.body.appendChild(script);
  script.onload = function () { eruda.init()}
})();

bind 兼容

if (!Function.prototype.bind) {
  Function.prototype.bind = function() {
   var self = this, // 保存原函数
   context = [].shift.call(arguments), // 需要绑定的this上下文
   args = [].slice.call(arguments); // 剩余的参数转成数组
   return function() { // 返回一个新函数
      // 执行新函数时,将传入的上下文context作为新函数的this
      // 并且组合两次分别传入的参数,作为新函数的参数
       return self.apply(context, [].concat.call(args, [].slice.call(arguments)));
     }
   };
}

apply/call

apply/call都能改变函数运行时的上下文(context),即改变this的指向(换句话说一个对象可以调用其他对象的方法),如果call、apply第一个参数传递null或者undefined,那么函数中this不改变。

  • 用法
    apply相较之call,其第二个参数是一个数组
    fn.call(obj,arg1,arg2,[...arg])
    fn.apply(obj,[arg1,arg2,...arg])
  • 简单case
const foo = {
  value: 2,
};
function bar(num) {
  console.log(this.value + num);
}
// => NaN
bar(2)
// => 4
bar.bind(foo, 2);
// => 4
bar.bind(foo)(2);
// => 4
bar.call(foo, 2);
// => 4
bar.apply(foo, [2]);
  • 复杂case
function Stu(){} // 学生类
Stu.prototype = {
    greeting: "nice to meet you",
    say: function(a,b){
        console.log(a+b);
        console.log(this)   //这里的this指向的是 new Stu()
        console.log("hello,"+this.greeting);
    }
}
var s = new Stu();  // {}
s.say(1,2); // 3  hello, nice to meet you
var s1 = {
    greeting: "nice to meet you,too"
}
s.say.apply(s1,[2,3]);  //5  hello,nice to meet you,too    此时this指向s1
s.say.call(s1,4,27);   // 31 hello,nice to meet you,too

bind

和call/apply类似,bind返回的是一个方法

取整

~~ 双非按位取反运算符,正数,它向下取整;负数,向上取整;非数字取值为0

~~null; // 0
~~undefined; // 0
~~2.9; // 2
~~-2.9; // -2
~~[]; // 0
~~{}; // 0
~~false; // 0
~~true; // 1

数字前补0

// '009'
Array(3).join('0')+9

数组常用方法

Array.prototype.forEach

  • 用法 arr.forEach((currentValue,index,array[,thisArg])=>{ //your iterator })
  • 说明
    currentValue表示当前元素,index表示当前元素的索引,array表示原数组(被调用数组),thisArg执行callback的对象,可选参数,即this指向的对象,返回值是undefined
    案例:
[2, 5, ,9].forEach((currentValue,index,array)=>{
    console.log("a["+index+"]="+currentValue)
});
// output a[0]=2,a[1]=5,a[3]=9 a[2]没有值,忽略

Array.prototype.map

  • 用法
var new_array = arr.map(
  (currentValue,index,array[,thisArg])=>{ 
      //your iterator 
   }
)
  • 说明
    callback的参数说明同forEach,但返回值是一个新数组,其每个元素都是回调函数的结果;
    案例:
let numbers=[1, 5, 10, 15];
let roots = numbers.map((x) => {
  return x * 2;
});
// output roots=[2,10,20,30]
// numbers is still [1, 5, 10, 15]

Array.prototype.filter

  • 用法
var new_array = arr.filter(
  (currentValue,index,array[,thisArg])=>{ 
      //your iterator 
  }
)
  • 说明
    callback的参数说明同forEach,callback的返回值的boolean,若为true,则保留该元素,否则,不保留,返回的值是一个新的的数组,其每个元素都是原数组中通过测试的元素
    案例:
// => filtered [12,130.44]
let filtered = [12,5,8,130,44].filter((value)=>{
    return value>=10;
});

Array.prototype.reduce

  • 用法
arr.reduce((accumulator,currentValue[, index[, array]])=>{
  //your iterator
 }[,initialValue]
)
  • 说明
    accumulator表示上一次调用回掉的值,或者是提供的初始值(initialValue),currentValue数组中正在处理的元素,index表示正常处理元素的索引,若提供initialValue,则从0开始,否则,从1开始,array表示原数组,initialValue是可选值,该值用于第一次调用callback的第一个参数,返回值是函数累计处理的结果
  • 注意
    回调第一次执行时,accumulator,currentValue的取值有两种情况:调用reduce时提供initialValue,accumulator的取值为initialValue,currentValue取数组中的第一个(即arr[0]);没有提供initialValue,则accumulator取数组中的第一个值arr[0]currentValue取数组中的第二个值(即arr[1])
  • 案例
let sum = [0, 1, 2, 3].reduce((acc, val) => {
   return acc + val;
}, 0);
// => 6 
console.log(sum);
// 执行第一次回调时initiaValue = 0,accumulator =0,currentValue=0

Array.prototype.reduceRight

  • 说明
    该方法类似reduce,只不过取值是从右边开始

Array.prototype.find

  • 说明
    该方法返回的是第一个通过测试的元素
  • Case
// => 5
[1, 2, 5].find((v) => v > 3)

树形结构转换

function formatTree(arr) {
  const map = {};
  const tree = [];

  arr.forEach(item => {
    map[item.departmentId] = item;
  });

  arr.forEach(item => {
    const parent = map[item.parentDepartmentId];
    if (parent) {
      (parent.children || (parent.children = [])).push(item);
    } else {
      tree.push(item);
    }
  });

  return tree;
}
function treeData(data){   
    const cloneData = JSON.parse(JSON.stringify(data))
    return cloneData.filter(father=>{
        const branchArr = cloneData.filter(child => father.id == child.pid);
        if(){
           Object.assign(father, { children: branchArr });
        }
        return father.pid == 0 ;
    })
}

图片压缩

function _imgCompress(image, type) {
    // 默认按比例压缩
    const imageWidth = image.width;
    const imageHeight = image.height;
    const scaleRatio = imageWidth / imageHeight;
    const quality = 0.7;  // 默认图片质量为0.7
    // 修改图片宽度,改变尺寸
    const cvsWidth = Math.min(1000, imageWidth)
    const cvsHeight = cvsWidth / scaleRatio;
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    canvas.setAttribute('width', cvsWidth);
    canvas.setAttribute('height', cvsHeight);
    ctx.drawImage(image, 0, 0, cvsWidth, cvsHeight);
    const base64 = canvas.toDataURL(image, quality);
}

base64转换成blob

function _convertBase64UrlToBlob(urlData) {
    const arr = urlData.split(','), mime = arr[0].match(/:(.*?);/)[1];
    const bstr = atob(arr[1]);
    let n = bstr.length;
    const u8arr = new Uint8Array(n);
    while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
    }
    return new Blob([u8arr], { type: mime });
}

双向绑定实现

  • Proxy
import "./global";
const app = document.getElementById("app");

app.innerHTML = `
<p>简单mvvm的实现</p>
<input placeholder='请输入' id='txtInput'/>
<p>输入的是:<span id='txtResult'></span></p>
`;

const txtInput = document.getElementById("txtInput") as HTMLInputElement;
const txtResult = document.getElementById("txtResult") as HTMLSpanElement;

const data = {
  txt: ""
};

// proxy实现数据劫持
const handler: ProxyHandler<{
  txt: string;
}> = {
  get(target, p) {
    return Reflect.get(target, p);
  },
  set(target, p, value) {
    if (p === "txt") {
      txtResult.innerText = value;
      txtInput.value = value;
    }
    return Reflect.set(target, p, value);
  }
};

const newData = new Proxy(data, handler);

txtInput.oninput = function(e: any) {
  const value = e.target.value;
  newData.txt = value;
};

window.newData = newData;
  • Object.defineProperty
import "./global";
const app = document.getElementById("app");

app.innerHTML = `
<p>简单mvvm的实现</p>
<input placeholder='请输入' id='txtInput'/>
<p>输入的是:<span id='txtResult'></span></p>
`;

const txtInput = document.getElementById("txtInput") as HTMLInputElement;
const txtResult = document.getElementById("txtResult") as HTMLSpanElement;

const data = {} as {
  txt: string;
};
let defaultTxt = "";

Object.defineProperty(data, "txt", {
  get() {
    return defaultTxt;
  },
  set(value: string) {
    defaultTxt = value;
    txtResult.innerText = value;
    txtInput.value = value;
  }
});

txtInput.oninput = function(e: any) {
  const value = e.target.value;
  data.txt = value;
};

window.newData = data;

滚动加载

TODO

虚拟滚动

TODO

时间分片

<ul id="container"></ul>
let ul = document.getElementById("container");
let total = 100000;
let once = 50;
let index = 0;

// setTimeout
function loop1(curTotal, curIndex) {
  if (curTotal <= 0) {
    return false;
  }
  
  //每页多少条
  let pageCount = Math.min(curTotal, once);

  setTimeout(() => {
    for (let i = 0; i < pageCount; i++) {
      let li = document.createElement("li");
      li.innerText = curIndex + i + " : " + ~~(Math.random() * total);
      ul.appendChild(li);
    }

    loop(curTotal - pageCount, curIndex + pageCount);
  }, 0);
}

// rAf
function loop(curTotal, curIndex) {
  if (curTotal <= 0) {
    return false;
  }
  //每页多少条
  let pageCount = Math.min(curTotal, once);

  let fragment = document.createDocumentFragment();
  window.requestAnimationFrame(function () {
    for (let i = 0; i < pageCount; i++) {
      let li = document.createElement("li");
      li.innerText = curIndex + i + " : " + ~~(Math.random() * total);
      fragment.appendChild(li);
    }
    ul.appendChild(fragment);
    loop(curTotal - pageCount, curIndex + pageCount);
  });
}

loop(total, index);

节流(throttle)

  • 基本定义
    当持续的触发事件时,保证一定的时间间隔内只执行一次事件处理函数

  • 简单实现

function throttle(fn,interval){
    // 记录上一次执行时间 
    let last = 0;
    return ()=>{
        // 本次执行时间
        let now = +new Date();
        // 判断两次执行差是否小于时间间隔的阈值
        if(now-last>=interval){
            last = now;
            fn.apply(this,arguments)
        }
    }
}
  • 总结
    它能减少函数执行的频率,常用于按钮点击滚动事件拖拽事件计算鼠标移动的距离

防抖(debounce)

  • 基本定义
    当持续触发的事件,指定的时间段内只会执行一次,若在指定的时间再次触发,则重新计算时间

  • 简单实现

function debounce(fn,delay){
    let timer = null;
    return ()=>{
        if(timer) {
            clearTimeout(timer);
        }
        
        timer = setTimeout(()=>{
            fn.apply(this,arguments)
        },delay)
    }
}
  • 总结
    它是将多次操作合并为一次操作执行,只要是维护一个定时器,规定在指定的时间(delay)内触发事件,若指定的时间内再次触发,则清除定时器并重新设置定时器,相当于只有最后一次操作能被触发。常用于浏览器窗口缩放resize事件,搜索框输入查询表单验证

函数柯里化

function curry(fn, ...args) {
  return (...arr) => {
    // 合并参数
    const merge = [...args, ...arr];
    // 判断函数的参数小于原函数的参数,则递归调用,继续合并参数
    if (merge.length < fn.length) {
      return curry(fn, ...merge);
    }
    return fn(...merge);
  };
}

function _add(a, b) {
  return a + b;
}
const add = curry(_add)
// => 3
add(1,2)
// => 3
add(1)(2)

参考文档