【面试题】js各种源码实现

1,325 阅读9分钟

持续更新中。。。

更多资源尽在前端资源合集

判断类型

  • typeof
console.log(typeof 2); // number
console.log(typeof true); // boolean
console.log(typeof "str"); // string
console.log(typeof undefined); // undefined
console.log(typeof []); // object
console.log(typeof {}); // object
console.log(typeof function() {}); // function
console.log(typeof null); // object
console.log(typeof Symbol()); // symbol
  • instanceof
console.log(2 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log("str" instanceof String); // false
console.log([] instanceof Array); // true
console.log(function() {} instanceof Function); // true
console.log({} instanceof Object); // true
  • Object.prototype.toString.call()
var toString = Object.prototype.toString;

console.log(toString.call(2)); //[object Number]
console.log(toString.call(true)); //[object Boolean]
console.log(toString.call("str")); //[object String]
console.log(toString.call([])); //[object Array]
console.log(toString.call(function() {})); //[object Function]
console.log(toString.call({})); //[object Object]
console.log(toString.call(undefined)); //[object Undefined]
console.log(toString.call(null)); //[object Null]

console.log(toString.call(new Map())); //[object Map]
console.log(toString.call(new Set([1, 3]))); //[object Set]
console.log(toString.call(new Date())); //[object Date]
console.log(toString.call(new RegExp("."))); //[object RegExp]
console.log(toString.call(Symbol())); //[object Symbol]

通用方法

var type = function(data) {
  var toString = Object.prototype.toString;
  var dataType =
    data instanceof Element
      ? "element" // 为了统一DOM节点类型输出
      : toString
          .call(data)
          .replace(/\[object\s(.+)\]/, "$1")
          .toLowerCase();
  return dataType;
};

实现 call apply bind

mycall

Function.prototype.mycall = function(thisArg, ...args) {
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  var self = this,
    fn = Symbol("fn");
  thisArg = thisArg || window;
  thisArg[fn] = self;

  var result = thisArg[fn](...args);
  delete thisArg[fn];
  return result;
};

var o = { a: 1 };
function f(x, y) {
  console.log(this.a, x, y);
}
f.mycall(o, 2, 3);

myapply

Function.prototype.myapply = function(thisArg, args) {
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  var self = this,
    fn = Symbol("fn");
  thisArg = thisArg || window;
  thisArg[fn] = self;

  var result = thisArg[fn](...args);
  delete thisArg[fn];
  return result;
};

var o = { a: 1 };
function f(x, y) {
  console.log(this.a, x, y);
}
f.myapply(o, [2, 3]);

mybind

// Function.prototype.mybind = function(thisArg) {
//   if (typeof this !== "function") {
//     throw TypeError("Bind must be called on a function");
//   }
//   // 拿到参数,为了传给调用者
//   const args = Array.prototype.slice.call(arguments, 1),
//     // 保存 this
//     self = this,
//     // 构建一个干净的函数,用于保存原函数的原型
//     nop = function() {},
//     // 绑定的函数
//     bound = function() {
//       // this instanceof nop, 判断是否使用 new 来调用 bound
//       // 如果是 new 来调用的话,this的指向就是其实例,
//       // 如果不是 new 调用的话,就改变 this 指向到指定的对象 o
//       return self.apply(
//         this instanceof nop ? this : thisArg,
//         args.concat(Array.prototype.slice.call(arguments))
//       );
//     };

//   // 箭头函数没有 prototype,箭头函数this永远指向它所在的作用域
//   if (this.prototype) {
//     nop.prototype = this.prototype;
//   }
//   // 修改绑定函数的原型指向
//   bound.prototype = new nop();

//   return bound;
// };
Function.prototype.mybind = function(context = window, ...outerArg) {
  let _this = this;
  return function(...innerArg) {
    return _this.apply(context, outerArg.concat(innerArg));
  };
};

var o = { a: 1 };
function f(x, y) {
  console.log(this.a, x, y);
}
var g = f.mybind(o, 2);
g(3);

组合函数

组合函数指的是将代表各个动作的多个函数合并成一个函数

function componse(...funcs) {
  return function() {
    var result = funcs[0].apply(this, arguments);
    for (var i = 1; i < funcs.length; i++) {
      result = funcs[i].call(this, result);
    }
    return result;
  };
}

function f1(x) {
  return x + 1;
}

function f2(x) {
  return x * 2;
}

var f = componse(f1, f2);
f(1); // 4

柯里化

简单说就是把多形参的函数转换为可以一个一个接收参数的函数,即如下

add(x,y) => curryAdd(x)(y)

function curry(f) {
  var total = f.length;
  var args = [];
  return function() {
    args.push(...arguments);
    if (args.length < total) {
      return arguments.callee;
    } else {
      var res = f.apply(this, args);
      args = []; // 需要清理args
      return res;
    }
  };
}

function add(x, y) {
  return x + y;
}

var curryAdd = curry(add);

curryAdd(1)(2); // 3
curryAdd(1, 2); // 3

防抖

防抖:任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行。

场景: 搜索输入框

function debounce(fn) {
  let timeout = null;
  return function() {
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      fn.call(this, arguments);
    }, 1000);
  };
}

节流

节流:指定时间间隔内只会执行一次任务。

场景:1. 监听滚动条 2. 监听点击按钮

function throttle(fn) {
  let canRun = true;
  return function() {
    if (!canRun) {
      return;
    }
    canRun = false;
    setTimeout(() => {
      fn.call(this, arguments);
      canRun = true;
    }, 1000);
  };
}

深拷贝

乞丐版

const target = {
  field1: 1,
  field2: undefined,
  field3: {
    child: "child"
  },
  field4: [2, 4, 8]
};
target.target = target;

function clone(obj, map = new Map()) {
  if (typeof obj === "object") {
    let result = obj instanceof Array ? [] : {};
    if (map.get(obj)) {
      return map.get(obj);
    }
    map.set(obj, result);
    for (let key in obj) {
      result[key] = clone(obj[key], map);
    }
    return result;
  } else {
    return obj;
  }
}
clone(target);

这个方法的问题是:

  1. 不能拷贝 null Date 等对象
  2. 不能拷贝 Symbol 属性
  3. 会拷贝原型链上的属性
  4. 不会拷贝不可枚举的属性

豪华版

目标可以完全深拷贝以下对象(不包括原型链上的属性)

var oo = {
  oa: 1
};

var f = function() {};

var o = {
  a: 1,
  b: {
    a: 1
  },
  c: undefined,
  d: new Date(),
  e: null,
  [f]: 1,
  r: RegExp("."),
  [Symbol()]: "1",
  g: Symbol("g")
};

o.__proto__ = oo;
o.o = o;
Object.defineProperty(o, "p", {
  value: 1
});

难点:

  1. 拷贝 Map Set 等复杂对象
  2. 如何拷贝 Symbol 属性
  3. 如何拷贝不可枚举的属性
  4. 如果对象有个属性是循环引用,如上述的 o.o
  5. 如何保留正则 日期对象等属性值
  6. 如何保留 undefined
// 类型整合
const mapTag = "[object Map]";
const setTag = "[object Set]";
const arrayTag = "[object Array]";
const objectTag = "[object Object]";

const boolTag = "[object Boolean]";
const dateTag = "[object Date]";
const errorTag = "[object Error]";
const numberTag = "[object Number]";
const regexpTag = "[object RegExp]";
const stringTag = "[object String]";
const symbolTag = "[object Symbol]";

const deepTag = [mapTag, setTag, arrayTag, objectTag];
// 判断原始类型和引用类型
function isObject(target) {
  const type = typeof target;
  return target !== null && (type === "object" || type === "function");
}
// 获取克隆对象的类型
function getType(target) {
  return Object.prototype.toString.call(target);
}
// 初始化要克隆的对象
function getInit(target) {
  const Ctor = target.constructor;
  return new Ctor();
}
// loadsh使用的遍历迭代器
function arrayEach(array, iteratee) {
  let index = -1;
  const length = array.length;

  while (++index < length) {
    if (iteratee(array[index], index, array) === false) {
      break;
    }
  }
  return array;
}
// 克隆其他不可遍历类型
function cloneOtherType(targe, type) {
  const Ctor = targe.constructor;
  switch (type) {
    case boolTag:
    case numberTag:
    case stringTag:
    case errorTag:
    case dateTag:
      return new Ctor(targe);
    case regexpTag:
      return cloneReg(targe);
    case symbolTag:
      return cloneSymbol(targe);
    default:
      return null;
  }
}
// 克隆Symbol
function cloneSymbol(targe) {
  return Symbol.prototype.valueOf.call(targe);
}
// 克隆正则
function cloneReg(targe) {
  const reFlags = /\w*$/;
  // const result = new targe.constructor(targe.source, reFlags.exec(targe))
  const result = new targe.constructor(targe);
  result.lastIndex = targe.lastIndex;
  return result;
}
function deepClone(value, map = new WeakMap()) {
  let cloneValue, type;
  // 判断引用数据类型
  if (!isObject(value)) {
    return value;
  }

  // 初始化
  type = getType(value);
  if (deepTag.includes(type)) {
    cloneValue = getInit(value);
  } else {
    return cloneOtherType(value, type);
  }
  // 防止循环引用
  if (map.get(value)) {
    return map.get(value);
  }
  map.set(value, cloneValue);

  // 克隆set
  if (type === setTag) {
    value.forEach(value => {
      cloneValue.add(deepClone(value, map));
    });
    return cloneValue;
  }

  // 克隆map
  if (type === mapTag) {
    value.forEach((value, key) => {
      cloneValue.set(key, deepClone(value, map));
    });
    return cloneValue;
  }

  // 克隆对象和数组
  const props = type === arrayTag ? undefined : Reflect.ownKeys(value);
  arrayEach(props || value, (val, key) => {
    if (props) {
      key = val;
    }
    cloneValue[key] = deepClone(value[key], map);
  });
  return cloneValue;
}

var oo = {
  oa: 1
};

var f = function() {};

var o = {
  a: 1,
  b: {
    a: 1
  },
  c: undefined,
  d: new Date(),
  e: null,
  [f]: 1,
  r: RegExp("."),
  [Symbol()]: "1",
  g: Symbol("g")
};

o.__proto__ = oo;
o.o = o;
Object.defineProperty(o, "p", {
  value: 1
});

deepClone(o);

实现 new

new 操作会执行以下操作

1. 创建一个全新的对象。
2. 这个新对象会被执行 [[Prototype]] 连接。
3. 这个新对象会绑定到函数调用的 this。
4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。
function _new() {
  let target = {}; //创建的新对象
  //第一个参数是构造函数
  let [constructor, ...args] = [...arguments];
  //执行[[原型]]连接;target 是 constructor 的实例
  target.__proto__ = constructor.prototype;
  //执行构造函数,将属性或方法添加到创建的空对象上
  let result = constructor.apply(target, args);
  if (result && (typeof result == "object" || typeof result == "function")) {
    //如果构造函数执行的结构返回的是一个对象,那么返回这个对象
    return result;
  }
  //如果构造函数返回的不是一个对象,返回创建的新对象
  return target;
}

数组去重

不同的方法 得到的结果不大一样,这个看需求了

var array = [
  1,
  1,
  "1",
  "1",
  null,
  null,
  undefined,
  undefined,
  new String("1"),
  new String("1"),
  /a/,
  /a/,
  NaN,
  NaN
];

// function unique(arr) {
//   var ret = []
//   for (var i = 0; i < arr.length; i++) {
//     for (var j = 0, l = ret.length; j < l;j++) {
//       if (ret[j] === arr[i]) {
//         break;
//       }
//     }
//     if (j === ret.length) {
//       ret.push(arr[i])
//     }
//   }
//   return ret
// } // [1, "1", null, undefined, String, String, /a/, /a/, NaN, NaN]

// function unique(arr) {
//   var ret = []
//   for (var i = 0; i < arr.length; i++) {
//     var index = ret.indexOf(arr[i])
//     if (index === -1) {
//       ret.push(arr[i])
//     }
//   }
//   return ret
// } //  [1, "1", null, undefined, String, String, /a/, /a/, NaN, NaN]

// function unique(arr) {
//   var sortedArr = arr.sort()
//   var seen
//   var ret = []
//   for (var i = 0; i < sortedArr.length; i++) {
//     if (!i || seen !== arr[i]) ret.push(sortedArr[i])
//     seen = sortedArr[i]
//   }
//   return ret
// } // [/a/, /a/, 1, "1", String, String, NaN, NaN, null, undefined]

// function unique(arr) {
//   var ret = []
//   return arr.filter(item => {
//     if (ret.indexOf(item) > -1) return false
//     return ret.push(item)
//   })
// } //  [1, "1", null, undefined, String, String, /a/, /a/, NaN, NaN]

// function unique(arr) {
//   var sortedArr = arr.sort()
//   return sortedArr.filter((item, index, array) => {
//     if (!index || item !== array[index - 1]) return true
//   })
// } // [/a/, /a/, 1, "1", String, String, NaN, NaN, null, undefined]

// function unique(arr) {
//   return Array.from(new Set(arr))
// } // [1, "1", null, undefined, String, String, /a/, /a/, NaN]

// function unique(arr) {
//   return [...new Set(arr)]
// } //  [1, "1", null, undefined, String, String, /a/, /a/, NaN]

// function unique (arr) {
//   const seen = new Map()
//   return arr.filter((a) => !seen.has(a) && seen.set(a, 1))
// } // [1, "1", null, undefined, String, String, /a/, /a/, NaN]

// function unique(arr) {
//   var map ={}
//   var ret = []
//   for(var i = 0; i< arr.length; i++) {
//     if (!map[typeof arr[i] + arr[i]]) {
//       map[typeof arr[i] + arr[i]] = true
//       ret.push(arr[i])
//     }
//   }
//   return ret
// } //  [1, "1", null, undefined, String, /a/, NaN]

function unique(array) {
  var obj = {};
  return array.filter(function(item, index, array) {
    return obj.hasOwnProperty(typeof item + item)
      ? false
      : (obj[typeof item + item] = true);
  });
} // [1, "1", null, undefined, String, /a/, NaN]

unique(array);

展平数组

// 递归思想

var arr = [1, 2, [3, 4], [5, [6, 7], 8], 9];

function flat(arr) {
  var ans = [];
  for (var i = 0; i < arr.length; i++) {
    if (!Array.isArray(arr[i])) ans.push(arr[i]);
    else {
      let res = flat(arr[i]);
      ans = ans.concat(res);
    }
  }
  return ans;
}

console.log(flat(arr));
function flat(arr, reuslt = []) {
  arr.forEach(item => {
    if (item instanceof Array) flat(item, reuslt);
    else reuslt.push(item);
  });
  return reuslt;
}

flat([1, 2, 3, [1, 2, [1.4], [1, 2, 3]]]);
var arr = [1, [[2, 3], 4], [5, 6]];

var flat = function*(a) {
  var length = a.length;
  for (var i = 0; i < length; i++) {
    var item = a[i];
    if (typeof item !== "number") {
      yield* flat(item);
    } else {
      yield item;
    }
  }
};

for (var f of flat(arr)) {
  console.log(f);
}
// 1, 2, 3, 4, 5, 6
function* iterTree(tree) {
  if (Array.isArray(tree)) {
    for (let i = 0; i < tree.length; i++) {
      yield* iterTree(tree[i]);
    }
  } else {
    yield tree;
  }
}

const tree = ["a", ["b", "c"], ["d", "e"]];
[...iterTree(tree)];

// es6

[1, [2, [3]]].flat(Infinity);

实现斐波那契数列

function* fibonacci() {
  let [prev, curr] = [0, 1];
  for (;;) {
    yield curr;
    [prev, curr] = [curr, prev + curr];
  }
}

for (let n of fibonacci()) {
  if (n > 1000) break;
  console.log(n);
}

实现延迟器

目标是实现一个延迟多少时间再执行之后操作的函数

function timeout(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  });
}

// 或
// async function timeout(ms) {
//   await new Promise((resolve) => {
//     setTimeout(resolve, ms);
//   });
// }

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}

asyncPrint("hello world", 50); // 50 毫秒以后,输出hello world

限制构造函数只能通过 new 调用

方法一:通过 new.target 限制

function F(name) {
  if (new.target !== undefined) {
    this.name = name;
  } else {
    throw new Error("必须使用 new 命令生成实例");
  }
}

方法二:通过 class

class F {
  constructor(name) {
    this.name = name;
  }
}

不能独立使用、必须继承后才能使用的类

class Shape {
  constructor() {
    if (new.target === Shape) {
      throw new Error("本类不能实例化");
    }
  }
}

class Rectangle extends Shape {
  constructor(length, width) {
    super();
    // ...
  }
}

var x = new Shape(); // 报错
var y = new Rectangle(3, 4); // 正确

实现对象继承

ES5

function A() {
  this.name = "Father";
}
function B() {
  this.age = 32;
}
B.prototype = new A(); // Father函数改变自己的prototype指向

var b = new B();
function A() {
  this.name = "Father";
}

function B() {}

// B 的实例继承 A 的实例
Object.setPrototypeOf(B.prototype, A.prototype);

// B 继承 A 的静态属性
Object.setPrototypeOf(B, A);

const b = new B();

ES6

class A {
  name = "Father";
}

class B extends A {
  constructor(props) {
    super(props);
  }
}

var b = new B();

设置原型的方法

function A() {
  this.name = "Father";
}

function B() {}

B.prototype = Object.create(A.prototype);
// 等同于
B.prototype.__proto__ = A.prototype;
// 等同于
Object.setPrototypeOf(B.prototype, A.prototype);

实现 enum

// enum color { Red, Green, Orange }
// 等价于
var color;
(function(color) {
  color[(color["Red"] = 0)] = "Red";
  color[(color["Green"] = 1)] = "Green";
  color[(color["Orange"] = 2)] = "Orange";
})(color || (color = {}));

let a = color.Red;
console.log(color[0]); // Red
console.log(a); // 0

对象格式化

格式化对象 大写变为小写

// 格式化对象 大写变为小写
let o = {
  a: 1,
  b: {
    c: 2,
    D: {
      E: 3
    }
  }
};
function keysLower(obj) {
  if (obj instanceof Object === false) return obj;
  let result = {};
  Object.keys(obj).forEach(key => {
    result[key.toLowerCase()] = keysLower(obj[key]);
  });
  return result;
}

keysLower(o);

为对象实现 for...of...

function* objectEntries() {
  let propKeys = Object.keys(this);

  for (let propKey of propKeys) {
    yield [propKey, this[propKey]];
  }
}

let jane = { first: "Jane", last: "Doe" };

jane[Symbol.iterator] = objectEntries;

for (let [key, value] of jane) {
  console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe

实现 thunkify

thunkify 将多参数函数替换成一个只接受回调函数作为参数的单参数函数

用于 Generator 函数的自动流程管理

function thunkify(fn) {
  return function() {
    var args = new Array(arguments.length);
    var ctx = this;

    for (var i = 0; i < args.length; ++i) {
      args[i] = arguments[i];
    }

    return function(done) {
      var called;

      args.push(function() {
        if (called) return;
        called = true;
        done.apply(null, arguments);
      });

      try {
        fn.apply(ctx, args);
      } catch (err) {
        done(err);
      }
    };
  };
}
function f(a, b, callback) {
  var sum = a + b;
  callback(sum);
  callback(sum);
}

var ft = thunkify(f);
var print = console.log.bind(console);
ft(1, 2)(print);
// 3

写一个网络请求

发送 http 请求

const getJSON = function(url) {
  const promise = new Promise(function(resolve, reject) {
    const handler = function() {
      if (this.readyState !== 4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    const client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();
  });

  return promise;
};

getJSON("/posts.json").then(
  function(json) {
    console.log("Contents: " + json);
  },
  function(error) {
    console.error("出错了", error);
  }
);

实现请求 5 秒未返回,就超时

const p = Promise.race([
  fetch("/resource-that-may-take-a-while"),
  new Promise(function(resolve, reject) {
    setTimeout(() => reject(new Error("request timeout")), 5000);
  })
]);

p.then(console.log).catch(console.error);

实现并发、串发请求

串发,可以看到依次打印 A,B,C

function request(filename) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(filename);
      resolve(filename);
    }, Math.random() * 500);
  });
}
// 方法一
async function a() {
  let docs = ["A", "B", "C"];
  for (var filename of docs) {
    await request(filename);
  }
}
// 方法二
// async function a() {
//   let docs = ['A','B','C']
//   await docs.reduce(async (pre, cur)=> {
//     await pre
//     await request(cur)
//   }, undefined)
// }

a().then(res => res);

并发,同时发出请求,ABC 随机打印

function request(filename) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(filename);
      resolve(filename);
    }, Math.random() * 500);
  });
}
// 方法一
// async function a() {
//   let docs = ['A','B','C']
//   let promises = docs.map(filename=>request(filename))
//   for(var promise of promises) {
//     await promise
//   }
// }
// 方法二
async function a() {
  let docs = ["A", "B", "C"];
  let promises = docs.map(filename => request(filename));
  await Promise.all(promises);
}

a().then(res => res);

实现 jsonp

function addScriptTag(src) {
  var script = document.createElement("script");
  script.setAttribute("type", "text/javascript");
  script.src = src;
  document.body.appendChild(script);
}

window.onload = function() {
  addScriptTag("http://example.com/ip?callback=foo");
};

function foo(data) {
  console.log("Your public IP address is: " + data.ip);
}

实现图片懒加载

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .container {
        width: 600px;
        margin: 0 auto;
      }
      .imgbox {
        width: 600px;
        height: 300px;
        background-color: #bbb;
        /* margin-top: 1000px; */
        margin-bottom: 20px;
      }
      .imgbox img {
        width: 100%;
        height: 100%;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="imgbox">
        <img
          src=""
          data-src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/1/14/16fa30af939c81c4~tplv-t2oaga2asx-image.image"
          alt=""
        />
      </div>
      <div class="imgbox">
        <img
          src=""
          data-src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/1/14/16fa30af939c81c4~tplv-t2oaga2asx-image.image"
          alt=""
        />
      </div>
      <div class="imgbox">
        <img
          src=""
          data-src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/1/14/16fa30af939c81c4~tplv-t2oaga2asx-image.image"
          alt=""
        />
      </div>
      <div class="imgbox">
        <img
          src=""
          data-src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/1/14/16fa30af939c81c4~tplv-t2oaga2asx-image.image"
          alt=""
        />
      </div>
      <div class="imgbox">
        <img
          src=""
          data-src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/1/14/16fa30af939c81c4~tplv-t2oaga2asx-image.image"
          alt=""
        />
      </div>
      <div class="imgbox">
        <img
          src=""
          data-src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/1/14/16fa30af939c81c4~tplv-t2oaga2asx-image.image"
          alt=""
        />
      </div>
      <div class="imgbox">
        <img
          src=""
          data-src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/1/14/16fa30af939c81c4~tplv-t2oaga2asx-image.image"
          alt=""
        />
      </div>
      <div class="imgbox">
        <img
          src=""
          data-src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/1/14/16fa30af939c81c4~tplv-t2oaga2asx-image.image"
          alt=""
        />
      </div>
    </div>
    <script>
      var $window = window,
        $container = document.querySelector(".container");
      $window.onload = $window.onscroll = function() {
        var $imgboxs = $container.children;
        [...$imgboxs].forEach($item => {
          if ($item.classList.contains("loaded")) return;
          var $img = $item.querySelector("img"),
            clientRect = $item.getBoundingClientRect(), // NOTE: 这里用item的高度,因为img还没有宽高
            $A = clientRect.top + clientRect.height,
            $B = window.innerHeight;
          if ($A <= $B) {
            console.log("loaded");
            $img.src = $img.dataset["src"];
            $item.classList.add("loaded");
          }
        });
      };
    </script>
  </body>
</html>

打印 0-9

错误解法

for (var i = 0; i < 10; i++) {
  setTimeout(() => {
    console.log(i);
  }, 500);
}

以下是正确解法

setTimeout 传参

for (var i = 0; i < 10; i++) {
  setTimeout(
    i => {
      console.log(i);
    },
    500,
    i
  );
}

let 块作用域

for (let i = 0; i < 10; i++) {
  setTimeout(() => {
    console.log(i);
  }, 500);
}

闭包

for (let i = 0; i < 10; i++) {
  (() => {
    setTimeout(() => {
      console.log(i);
    }, 500);
  })();
}

闭包

for (let i = 0; i < 10; i++) {
  setTimeout(
    (i => {
      return () => console.log(i);
    })(i),
    500
  );
}

bind

for (let i = 0; i < 10; i++) {
  var fn = i => console.log(i);
  setTimeout(fn.bind(this, i), 500);
}

实现 a==1&&a==2&&a==3 为 true

方法一

var a = {
  n: 0,
  toString() {
    this.n++;
    return this.n;
  }
};

if (a == 1 && a == 2 && a == 3) {
  console.log("ok");
}

方法二

var a = [1, 2, 3];
a.toString = a.shift;

if (a == 1 && a == 2 && a == 3) {
  console.log("ok");
}

方法三

只有这种三等才为 true,a === 1 && a === 2 && a === 3

Object.defineProperty(window, "a", {
  get() {
    this.value ? this.value++ : (this.value = 1);
    return this.value;
  }
});

if (a === 1 && a === 2 && a === 3) {
  console.log("ok");
}

输出什么

let obj = {
  2: 3,
  3: 4,
  length: 2,
  push: Array.prototype.push
};

obj.push(5);
console.log(obj); // { '2': 5, '3': 4, length: 3, push: [Function: push] }
obj.push(6);
console.log(obj); // { '2': 5, '3': 6, length: 4, push: [Function: push] }
obj.push(7);
console.log(obj); // { '2': 5, '3': 6, '4': 7, length: 5, push: [Function: push] }

手写 promise