整理Javascript知识

224 阅读5分钟

闭包:

概念:函数执行后返回结果是一个内部函数, 并被外部变量所引用,如果内部函数持有被执行函数的作用域变量,形成了闭包。

可以在内部函数访问到外部函数作用域。使用闭包,一可以读取函数中的变量,二可以将函数中的变量存储在内存中,保护变量不被污染。正是因闭包可以把函数中的变量存储在内存中,会对内存有消耗,所以不能滥用闭包,否则会造成内存泄露。

function foo(x) {
  let count = 1;
  return function (y) {
    console.log("闭包:", x + y + (++count));  // 5
  }
}
let fun = foo(1);
fun(2);

防抖

概念: 在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时!

场景: 防止按钮多次提交,只执行最后一次;搜索框-防止输入重复发请求。

function fun(a) {
  console.log(a, 'btn click!')
}
function debounce(callback, wait) {
  let timer;
  return function () {
    const that = this, args = arguments;
    clearInterval(timer);
    timer = setTimeout(() => {
      callback.apply(that, args);
    }, wait);
  }
}

const btn = document.getElementById('btn');

btn.onclick = debounce(() => { fun('8888') }, 1000);

节流

概念:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。

场景: 拖拽;缩放场景。

实现方案: 时间戳和定时器实现。

 时间戳
function throttle(callback, time) {
  let pre = 0;
  let that, agrs;
  return function () {
    that = this; agrs = arguments;
    let now = +new Date();
    if (now - pre > time) {
      callback.apply(that, agrs);
      pre = now;
    }
  }
}
定时器
function throttles(callback, time) {
  let timer = null;
  return function () {
    const that = this; const agrs = arguments;
    timer = setInterval(() => {
      callback.apply(that, agrs)
      timer = null;
    }, time);
  }
}
const btn = document.getElementById('btn');

btn.onclick = throttles(() => { fun('9999') }, 1000);

浅拷贝&&深拷贝

浅拷贝:是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝, 如果其中一个对象改变了这个地址,就会影响到另一个对象。

深拷贝:是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。

区别:主要区别是其在内存中的存储类型不同,栈为自动分配的内存空间,它由系统自动释放;而堆则是动态分配的内存,大小不定也不会自动释放。[基本数据类型存放在栈中,引用类型存放在堆中]。

举个🌰

let k = {
  a: 1,
  b: 2,
  c: 3,
  d: {
    e: 4,
  },
  f: {
    g: 5,
  }
};
let h = JSON.parse(JSON.stringify(k)); // 深复制包含子对象- 深拷贝
let l = { ...k }; // 拷贝一层但不包含子对象 // 浅拷贝 
let o = k; // 赋值
解析: 
 o.b = 22; // k、o 相同: { a: 1, b: 22, c:3, d: {e: 5}, f: {g: 6} }
 l.c = 33; // k: 不变; l: { a: 1, b: 22, c:33, d: {e: 5}, f: {g: 6} }
 l.d.e = 55; // k、l 相同: { a: 1, b: 2, c:3, d: {e: 55}, f: {g: 6} }
 h.f.g = 66; // k:不变; h: { a: 1, b: 2, c:3, d: {e: 5}, f: {g: 66} }

结论:

浅拷贝方法

  • es6-解构;
  • Object.assign();
  • lodash-_.clone;
  • Array.prototype-[concat()|slice()].

深拷贝方法

  • JSON.parse(JSON.stringify());
  • lodash-_.cloneDeep;
  • 递归拷贝所有层级属性.

数据类型检测

typeOf : 适合基本类型及function 检测,遇到null失效。

Object.prototype.toString.apply(): 适合内置对象和基元类型,兼容:IE678 null/undefined 失效 【返回[object object]】

instanceof: 适合自定义对象,也可检测原生对象,在不同的iframe和window 间检测失效。

数组

  • 数组去重
  const arr = [1, 2, 3, 4, 5, 3, 2, 5, 5, 4, 3, 6];
  
  1.定义一个新数组,for 循环原数组,判断结果数组是否存在当前元素,如果有相同的值则跳过,不相同则push进数组.
  function repeat1(arr) {
    let newArr = [];
    for (let i = 0; i < arr.length; i++) { // forEach / filter
      if (newArr.indexOf(arr[i]) === -1) { // !newArr.includes(arr[i])
        newArr.push(arr[i]);
      }
    }
    return newArr;
  }
  
  2.先将原数组排序,再与相邻的进行比较,如果不同放入新数组.
  function repeat2(arr) {
    let as = arr.sort();
    let newArr = [arr[0]];
    for (let i = 1; i < arr.length; i++) {
      if (as[i] !== as[i - 1]) {
        newArr.push(as[i]);
      }
    }
    return newArr;
  }
  
  3.使用es6 Set.
  function repeat3(arr) {
    return [...new Set(arr)];
  }
  
  4.与相邻的进行比较,截取相同的元素.
  function repeat4(arr) { // 会改变原数组
    for (let i = 0; i < arr.length; i++) {
      for (let j = i + 1; j < arr.length; j++) {
        if (arr[i] === arr[j]) {
          arr.splice(j, 1);
          j--;
        }
      }
    }
    return arr;
  }
  
  • 数组排序 冒泡排序:比较每相邻的两个数,若前者大于后者,把两个数进行位置交换;第一轮可以选出最大的数放在后面,经过n-1轮,完成每个数的排序。
const sort = [1, 2, 10, 8, 3, 6, 7, 5];
function arrSort(arrs) {
  for (let i = 0; i < arrs.length - 1; i++) {
    for (let j = 0; j < arrs.length - i - 1; j++) {
      if (arrs[j] > arrs[j + 1]) {
        const temp = arrs[j];
        arrs[j] = arrs[j + 1];
        arrs[j + 1] = temp;
      }
    }
  }
  return arrs;
}
console.log(arrSort(sort)); // [1, 2, 3, 5, 6, 7, 8, 10]

插入排序:获取当前索引元素, 从右向左搜索放到数组正确位置。

function insertionSort(arr) {
  let preIndex, current;
  for (let i = 1; i < arr.length; i++) {
    preIndex = i - 1; current = arr[i];
    while (preIndex >= 0 && arr[preIndex] > current) {
      arr[preIndex + 1] = arr[preIndex];
      preIndex--;
    }
    arr[preIndex + 1] = current;
  }
  return arr;
}
console.log(insertionSort(sort)); // [1, 2, 3, 5, 6, 7, 8, 10]

快速排序:找到数组的中间数, 并创建两个空数组left&right; 然后遍历数组,数组里每一项与中间数比较,小于中间数放在left,大于中间数放在right, 最后将left和right递归调用拼接完整的数组。

function fastSort(sort) {
  const arr = JSON.parse(JSON.stringify(sort));
  if (arr.length <= 1) return arr;
  const index = Math.floor(arr.length / 2);
  const temp = arr.splice(index, 1);
  let left = [], right = [];
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] < temp[0]) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
  return fastSort(left).concat(temp, fastSort(right));
}
console.log(fastSort(sort)) // [1, 2, 3, 5, 6, 7, 8, 10]

执行结果

  • 执行函数
function side(arr) {
  arr[0] = arr[2];
  return arr;
}
function a(a, b, c = 3) {
  c = 10;
  console.log('arguments', arguments)
  side(arguments);
  return a + b + c;
}

const result = a(1, 1, 1);
console.log(result);  // 12
  • 获取 arguments 值
function getArguments() {
  const args = new Array(arguments.length); // 创建 Array 对象
  console.log('args', args);
  for (let i = 0; i < args.length; i++) {
    args[i] = arguments[i];
  }
  return args;
}

getArguments(1, 2, 3); // [1, 2, 3]
  • 比较大小值
var min = Math.min(), max = Math.max();
console.log(min < max);
解析:Math.min 的参数是 0 个或者多个,如果多个参数很容易理解,返回参数中最小的。如果没有参数,则返回 Infinity,无穷大; 而 Math.max 没有传递参数时返回的是-Infinity.所以输出 false
  • 立即执行的函数
var a = 1;
(function a() {
  a = 2;
  console.log(a);
})();
解析:立即执行的函数表达式(IIFE)的函数名称跟内部变量名称重名后,函数名称优先,因为函数名称是不可改变的,内部会静默失败,在严格模式下会报错。
  • 隐式类型转化
var b = [0]; // "0"
if (b) {
  console.log(b === true);
} else {
  console.log(b)
}
解析:数组从非 primitive 转为 primitive 的时候会先隐式调用 join 变成“0”,string 和 boolean 比较的时候,两个都先转为 number 类型再比较,最后就是 0==1 的比较了
  • delete使用原则
var company = {
  address: 'beijing'
}
var yideng = Object.create(company);
delete yideng.address
console.log(yideng.address); // beijing
解析:delete 操作符用来删除一个对象的属性。
  • 函数表达式
var foo = function bar() { return 12; };
// console.log(typeof bar());  // bar is not defined
解析:这种命名函数表达式函数只能在函数体内有效
  • typeof
var x = 1;
if (function f() { }) {
  //  alert(typeof f) // undefind
  x += typeof f;
}
console.log(x);
  • var | let
for (let i = 0; i < 3; i++) {
  setTimeout(() => {
    // console.log(i)
  }, 1);
}
// 0 1 2

for (var j = 0; j < 3; j++) {
  setTimeout(() => {
    // console.log(j)
  }, 1);
}
// 3 3 3
  • this指向
const num = {
  a: 10,
  add() {
    return this.a + 2;
  },
  reduce: () => this.a -2
};
console.log(num.add());  // 12
console.log(num.reduce()); // NaN
解析:对于箭头函数,this关键字指向是它所在上下文(定义时的位置)的环境,与普通函数不同! 这意味着当我们调用reduce时,它不是指向num对象,而是指其定义时的环境(window)。没有值a属性,返回undefined
  • call | bind
const person = { name: "yideng" };
function sayHi(age) {
  return `${this.name} is ${age}`;
}
console.log(sayHi.call(person, 5));
console.log(sayHi.bind(person, 5));
解析:使用两者,我们可以传递我们想要this关键字引用的对象。 但是,.call方法会立即执行!.bind方法会返回函数的拷贝值,但带有绑定的上下文! 它不会立即执行。

Git 相关

  • git cherry pick:将指定的提交(commit)应用于其他分支,当只需要部分代码变动(某几个提交),可以使用 git cherry pick。

    例如: 在Master上创建功能分支, 在功能分支上提交的 "feature2",应用到Master上。

     git checkout Master -> git cherry pick "feature2"