# javascript 面试题汇总

# 1.计算出字符串中出现次数最多的字符是什么,出现了多少次?

var str = "adfdageilkjlioafdmyuyuierhk";
var maxLength = 0,
  result = "",
  oldStr;
while (str != "") {
  oldStr = str;
  getStr = str.charAt(0);
  str = str.replace(new RegExp(getStr, "g"), "");
  if (oldStr.length - str.length > maxLength) {
    maxLength = oldStr.length - str.length;
    result = getStr + "=" + maxLength;
  }
}
console.log(result);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 2.点击下载图片

方法一

<a href="url" download="url">点击下载图片</a>
1

方法二

//var img = reference to image
var url = img.src.replace(/^data:image\/[^;]/, "data:application/octet-stream");
window.open(url);
// Or perhaps: location.href = url;
// Or even setting the location of an <iframe> element,
1
2
3
4
5

方法三

var img = document.images[0];
img.onclick = function() {
  // atob to base64_decode the data-URI
  var image_data = atob(img.src.split(",")[1]);
  // Use typed arrays to convert the binary data to a Blob
  var arraybuffer = new ArrayBuffer(image_data.length);
  var view = new Uint8Array(arraybuffer);
  for (var i = 0; i < image_data.length; i++) {
    view[i] = image_data.charCodeAt(i) & 0xff;
  }
  try {
    // This is the recommended method:
    var blob = new Blob([arraybuffer], { type: "application/octet-stream" });
  } catch (e) {
    // The BlobBuilder API has been deprecated in favour of Blob, but older
    // browsers don't know about the Blob constructor
    // IE10 also supports BlobBuilder, but since the `Blob` constructor
    //  also works, there's no need to add `MSBlobBuilder`.
    var bb = new (window.WebKitBlobBuilder || window.MozBlobBuilder)();
    bb.append(arraybuffer);
    var blob = bb.getBlob("application/octet-stream"); // <-- Here's the Blob
  }

  // Use the URL object to create a temporary URL
  var url = (window.webkitURL || window.URL).createObjectURL(blob);
  location.href = url; // <-- Download!
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 3.前端如何读取上传文件

fileReader;
1

# 4.如何读取文件上传进度

$.ajax({
  url: post_url,
  type: "POST",
  data: form_data,
  contentType: false,
  cache: false,
  processData: false,
  xhr: function() {
    //upload Progress
    var xhr = $.ajaxSettings.xhr();
    if (xhr.upload) {
      xhr.upload.addEventListener(
        "progress",
        function(event) {
          var percent = 0;
          var position = event.loaded || event.position;
          var total = event.total;
          if (event.lengthComputable) {
            percent = Math.ceil((position / total) * 100);
          }
          //update progressbar
          $(progress_bar_id + " .progress-bar").css("width", +percent + "%");
          $(progress_bar_id + " .status").text(percent + "%");
        },
        true
      );
    }
    return xhr;
  },
  mimeType: "multipart/form-data",
}).done(function(res) {
  //
  $(my_form_id)[0].reset(); //reset form
  $(result_output).html(res); //output response from server
  submit_btn.val("Upload").prop("disabled", false); //enable submit button once ajax is done
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 5.基础数据类型转换问题

null == undefined  // true
[] == '' // true
[].toString() == '' // true,是上一个的解释
![] == false // true
[] == [] //false
({}) == '' // false
({}).toString() == '[object Object]' // 上一个的解释
1
2
3
4
5
6
7

# 6.for 循环中的 break,continue

for (var i = 0; i < 10; i++) {
  if (i <= 5) {
    i += 2;
    continue;
  }
  i += 3;
  break;
  console.log(i);
}
console.log(i); //9
// 打印几次,值
// 在循环体中出现和continue,break后,这两个关键字后边的代码就都不会执行了,但是continue会继续下一轮循环,break会直接结束循环
1
2
3
4
5
6
7
8
9
10
11
12

# 7.let 的块级作用域

for (let i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i); // 0,1,2,3,4
  }, i * 1000);
}
for (var i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i); // 5,5,5,5,5
  }, i * 1000);
}
1
2
3
4
5
6
7
8
9
10

# 8.预解析

if (!("a" in window)) {
  var a = "猜猜我有没有???";
}
alert(a); // undefined
// a会预解析,所以 'a' in window 为 true,a的赋值则得不到执行。
1
2
3
4
5

# 9.预解析闭包

function fo() {
  var i = 0;
  return function(n) {
    return n + i++;
  };
}

var f = fo();
var a = f(15); // 15
var b = fo()(15); // 15
var c = fo()(20); // 20
var d = f(20); // 21
1
2
3
4
5
6
7
8
9
10
11
12

# 10.预解析,闭包,this,作用域

var number = 2;
var obj = {
  number: 4,
  fn1: (function() {
    this.number *= 2; // 立即执行的时候,这里this是window
    number = number * 2;
    console.log(number); // NaN
    var number = 3;
    return function() {
      this.number *= 2;
      number *= 3;
      alert(number);
    };
  })(),
  db2: function() {
    this.number *= 2;
  },
};

var fn1 = obj.fn1;
alert(number); //4
fn1(); //9
obj.fn1(); //27

alert(window.number); //8
alert(obj.number); //8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 11.括号表达式

(1, 2, 3) + 3; //6
//括号表达式:一个括号中放多项内容,用逗号隔开,获取到最后一项

function fn() {
  console.log(this);
}
var obj = { fn: fn };
(fn, obj.fn)(); // 执行的是obj.fn(),但是注意this为window,并不是obj。这里有多项的时候,会将最后一项的函数体内容复制出来执行,指向window,和自执行方法一样

obj.fn(); // obj,只有一项时是正常表现
1
2
3
4
5
6
7
8
9
10

# 12. setTimeout 参数

for (var i = 1; i <= 4; i++) {
  var time = setTimeout(
    function(i) {
      clearTimeout(time);
      console.log(i);
    },
    1000,
    i
  );
}
console.log(i); // 5
console.log(time); // 可能为 4
// 1, 2, 3

/*
核心考察点
1. setTimeout 会返回一个正整数,表示定时器的编号。这个值可以传递给clearTimeout()来取消该定时。
2. 多次声明 time 会造成 time 的值的覆盖,最后 time 的值为 4,但是,因为 clearTimeout 在计时器内部,所以在循环刚结束后并没有执行,四个计时器并没有因为 time 的覆盖而丢失
3. setTimeout 从第三个开始后的参数,都将作为作为参数传递给 setTimeout 内部,形成闭包

所以,循环结束后,启动了 4 个计时器,time 的值为最后一次声明计时器返回的序号(4,但不一定),1秒后依次执行计时器,每次执行到 clearTimeout 时,都将最后的一个计时器即 time 的值所代表的编号给清除掉。最终打印 1, 2, 3
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 13. 函数节流

document.addEventListener(
  "scroll",
  throttle(function() {
    console.log(123);
  })
);
// 第一种,每隔一段时间执行一次 节流
// ---1-2-3-4-5-6-7-8-9
// ---1-----------7----
function throttle(func, delay = 6000) {
  let lock = false;
  return (...args) => {
    if (lock) return;
    func(...args);
    lock = true;
    setTimeout(() => {
      lock = false;
    }, delay);
  };
}
// 完善,注意this指向
function throttle(fn, wait) {
  let lock = false;
  return function(...args) {
    if (lock) return;
    func.apply(this, args);
    lock = true;
    setTimeout(() => {
      lock = false;
    }, delay);
  };
}

// 第二种,停止操作后一段时间执行一次 防抖
// ---1---2--3----------4---5--6------
// -----------------3----------------6
function debounce(func, delay = 600) {
  let I = null;
  return (...args) => {
    clearTimeout(I);
    // I = setTimeout(func.bind(null, ...args), delay)
    I = setTimeout((...args) => func(...args), delay);
  };
}
// 完善的版本,考虑this绑定
function debounce(fn, wait, immediate) {
  let timer = null;
  //  返回一个函数
  return function(...args) {
    // 每次触发事件时都取消之前的定时器
    clearTimeout(timer);
    // 判断是否要立即执行一次
    if (immediate && !timer) {
      fn.apply(this, args);
    }
    // setTimeout中使用箭头函数,就是让 this指向 返回的该闭包函数
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, wait);
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

# 14. 柯里化

const curry = (func) => {
  console.log(func.length);
  const g = (...allArgs) =>
    allArgs.length >= func.length
      ? func(...allArgs)
      : (...args) => g(...allArgs, ...args);

  return g;
};

const foo = curry((a, b, c, d) => {
  console.log(a, b, c, d);
});
foo(1)(2)(3)(4); // 1 2 3 4
foo(1)(2)(3); // 不返回
const f = foo(1)(2)(3);
f(5); // 1 2 3 5

// 对于 curry(foo),g 函数参数足够4个,就调用 foo(a,b,c,d),如果小于4个就返回一个可以继续积累参数的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 15. 迭代器

function strAdd(n, cb) {
  let str = "";
  (function it(i) {
    if (i >= n) {
      str += i;
      cb(str);
      return;
    }
    setTimeout(() => {
      str += i;
      it(i + 1);
    }, 500);
  })(0);
}

strAdd(10, function(res) {
  console.log(res);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 16 document.elementFromPoint(x, y)

返回当前文档上处于指定坐标位置最顶层的元素, 坐标是相对于包含该文档的浏览器窗口的左上角为原点来计算的, 通常 x 和 y 坐标都应为正数.

let target = document.elementFromPoint(x, y);
1

# 17 阻止事件冒泡

function doSomething(e) {
  e = window.event || e; // window.event 为 IE
  e.cancelBubble = true; // IE
  if (e.stopPropagation) {
    e.stopPropagation(); // 标准
  }
}
1
2
3
4
5
6
7

# 18 按数组中数字出现次数输出

let sortByCount = function(arr) {
  let arrUni = [];
  let arrCnt = [];
  arr.forEach((val) => {
    let idx = arrUni.indexOf(val);
    if (idx < 0) {
      arrUni.push(val);
      arrCnt.push(1);
    } else {
      arrCnt[idx]++;
    }
  });
  let arrTmp = arrUni.slice();
  arrUni.sort((a, b) => {
    let idxa = arrTmp.indexOf(a);
    let idxb = arrTmp.indexOf(b);
    return arrCnt[idxb] - arrCnt[idxa];
  });
  return arrUni;
};

let res = sortByCount([
  2,
  2,
  2,
  3,
  4,
  2,
  3,
  4,
  5,
  4,
  2,
  3,
  5,
  6,
  7,
  8,
  5,
  4,
  3,
  2,
  4,
  56,
  6,
]);
console.log(res);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# 19 面试题(原型,this 等)

Function.prototype.a = () => alert(1);
Object.prototype.b = () => alert(2);
function A() {}
var a = new A();
a.a(); // TypeError: a.a is not a function
a.b(); // alert(2)
// 解释 a.__proto__ -> A.prototype -> A.prototype.__proto__ -> Object.prototype -> Object.prototype.__proto__(null)
// 这里要注意这个过程的另一条线,函数 A 的原型链
// A.__proto__ -> Function.proptotype -> Object.prototype
// 所以 A.a() 和 A.b() 都可以正确执行
1
2
3
4
5
6
7
8
9
10

# 20 nodejs 定时器时间循环

console.log(1);

setTimeout(() => {
  console.log(2);
});

process.nextTick(() => {
  console.log(3);
});

setImmediate(() => {
  console.log(4);
});

new Promise((resolve) => {
  console.log(5);
  resolve();
  console.log(6);
}).then(() => {
  console.log(7);
});

Promise.resolve().then(() => {
  console.log(8);
  process.nextTick(() => {
    console.log(9);
  });
});
// 1
// 5
// 6
// 3
// 7
// 8
// 9
// 2
// 4
// http://www.ruanyifeng.com/blog/2018/02/node-event-loop.html
// 同步任务 -> process.nextTick -> Promise.resolve().then() -> setTimeout -> setImmediate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 21 parseInt

[1, 2, 3, 4].map(parseInt);
// [1, NaN, NaN, NaN]
// parseInt([value]) 把value转换为数字,(内核机制,需要把value先转为字符串,然后从字符串左侧第一个字符查找,把找到的有效数字字符转换为数字,直到遇到一个非有效数字字符为止)
// parseInt([value], [n]) 第二个参数不写默认为10,特殊情况,如果字符串是以 0X 开头,默认值是 16 进制
// 解释:parseInt 第二个参数是介于2-36的基数(0和10一样,剩下基数都是NaN),表示第一个参数是多少进制的数,然后返回一个十进制的整数
// map 会将 index 作为第二个参数传给 parseInt
// 当取出 1 的时候,index 为0,parseInt(1, 0),返回1,经过测试, parseInt(n, 0) 返回的是 n
// 后边的几个,都是出现错误比如 parseInt(2, 1),parseInt(3, 2),第一个参数都超出了它们的进制数
[10.18, 0, 10, 25, 23].map(parseInt);
// [10, NaN, 2, 2, 11]

parseInt(310, 2); // NaN

const unary = (fn) => (fn.length === 1 ? fn : (arg) => fn(arg));
// 利用 unary 实现上边的问题:
["1", "2", "3"].map(unary(parseInt));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 22 数据类型转换(隐式转换)

let result = 100 + true + 21.2 + null + undefined + 'test' + [] + null + 9 + false
// 'NaNtestnull9false'
// 注意: undefined + 123 => NaN
// 1 + null => 1
// 1 + [] => '1'
// 'a' + [] => 'a'
// Number(null) => 0
// Number(undefined) => NaN
// 解析,当加号左右两边出现只要有一边出现字符串或者对象,就会按照字符串拼接来处理,对于对象需要先转字符串(toString,如果对象有原始值,会先调用 valueOf),然后转数字。否则按数学运算计算,对于非数字的比如 false 这种,会进行隐式数字转化,调用的是 Number()


[] == false // true: 转为字符串比较?感觉像是转数字
![] == false // true: 相当于 false == false。!操作符优先级高
[] == 0 // true: 转数字比较
![] == 0 // true: 相当于 false == 0 => 0 == 0

// 解释,当以 {} 开头时,{}会被认为是一个语句块
{} + 0 // 0
{} + [] // 0,相当于 +[]
({}) + 0 // "[object Object]0"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

== 比较过程中,数据转换的规则

  • 类型一样
    • {} == {} => false,对象比较的是堆内存地址
    • [] == [] => false
    • NaN == NaN => false
  • 数据类型不一样
    • null == undefined => true,但是 === 结果为 false,剩下 null/undefined 和其他任何数据类型都不相等
    • 字符串 == 对象,要把对象转为字符串
    • 剩下如果 == 两边数据类型不一致,都是需要转换为数字在进行比较
// 对象的隐士转换原理
var a = ?
if (a == 1 && a == 2 & a == 3) {
  console.log(1)
}

// Symbol.toPrimitive -> valueOf -> toString

// 另一种使用拦截器 拦截 get 操作
var i = 0
Object.defineProperty(window, 'a', {
  get() {
    return ++i
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 23 堆栈内存

  • ECStack 执行环境栈
  • EC(G)全局执行上下文
  • EC(BLOCK)块级上下文
  • VO(G)全局变量对象
  • AO(BLOCK)私有变量对象
var a = { x: 1 };
var b = a;
a.x = a = { n: 1 }; // 正常赋值的话,是从右往左,带成员访问的优先集会提高
console.log(a); // {n: 1}
console.log(b); // {x: {n: 1}}
1
2
3
4
5

# 24 变量提升(坑爹的函数提升)

  • 初始时 {} 中的 function,在全局下只声明不定义(赋值)
  • {} 中出现 function/const/let 会创建一个块级上下文
var a = 0;
if (true) {
  console.log(window.a, a); // 0, func a
  a = 1;
  console.log(window.a, a); // 0, 1
  function a() {}
  console.log(window.a, a); // 1, 1
  a = 21;
  console.log(window.a, a); // 1, 21
}
console.log(a); // 1

// 1. 全局声明变量 a: var a 和 function a
// 2. 全局 a = 0
// 3. 进入私有作用域,私有作用域内函数声明加定义,所以第一个 console 输出 func a
// 4. 私有块级作用域内 a = 1
// 5. 函数处理,在块级作用域内已经执行过,但是这里会有一个特殊处理:因为发现该变量在全局一上来声明过,所以这里会把这行代码之前所有对a的操作映射给全局一份,后面的则不会在处理了,认为后面的都是私有的,所以此时全局变量 a 会被赋值为1
// 6. a = 21,此时只会修改块级作用域内 a 的值为 21
// 7. 输出全局 a = 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
  function foo() {}
  foo = 1;
  // foo => 1
}
console.log(foo); // foo => func

{
  function foo() {}
  foo = 1;
  function foo() {}
  // foo => 1
}
console.log(foo); // foo => 1

{
  function foo() {}
  foo = 1;
  function foo() {}
  foo = 2;
  // foo => 2
}
console.log(foo); // foo => 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 25 函数参数上下文

var x = 1;
function func(
  x,
  y = function anonymousl() {
    x = 2;
    console.log(x); // 2
  }
) {
  x = 3;
  y();
  console.log(x); // 2
}
func(5);
console.log(x); // 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14

ES6 中存在块级作用域(只要除对象之外的大括号 {} 出现 let/const/function),另外有一种情况也会产生

  1. 函数有形参赋值了默认值
  2. 函数体中又单独声明过某个变量

这样在函数运行的时候,会产生两个上下文,可通过 debugger 调试(分别是 local, block 两个作用域)

  1. 第一个: 函数执行形成的私有上下文 EC(func) => 作用域链/形参赋值/...
  2. 第二个: 函数体大括号包起来的是一个块级上下文 EC(block),该上下文是特殊的
var x = 1;
function func(
  x,
  y = function anonymousl() {
    x = 2;
    console.log(x); // 2
  }
) {
  var x = 3; // 括号块级作用域的
  y(); // 修改的是 func 执行上下文中的 x
  console.log(x); // 3
}
func(5);
console.log(x); // 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 26 compose

const add1 = (x) => x + 1;
const mult3 = (x) => x * 3;
const div2 = (x) => x / 2;

// 实现 compose 简化 div2(mult3(add1(add1(2))))
// 从左往右执行传入的参数
function compose(...funcs) {
  return function anonymous(...args) {
    if (funcs.length === 0) {
      return args;
    }
    if (funcs.length === 1) {
      return funcs[0](...args);
    }
    let n = 0;
    return funcs.reduce((a, b) => {
      n++;
      if (n === 1) {
        // 首次进来 a 和 b 都是函数,后续 a 是上个函数执行返回的结果,b是函数
        return b(a(...args));
      }
      return b(a);
    });
  };
}

let result = compose(add1, add1, mult3, div2);
console.log(result(0));

// 改写上边的 compose,注意和上边的区别,这里根据不同场景返回不同函数
function compose(...funcs) {
  if (funcs.length === 0) {
    return (args) => args;
  }
  if (funcs.length === 1) {
    return funcs[0];
  }
  return funcs.reduce((a, b) => {
    return (...args) => b(a(...args));
  });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 27 面向对象 模拟 new

function Dog(name) {
  this.name = name;
}
Dog.prototype.bark = function() {
  console.log("wangwang");
};
Dog.prototype.sayName = function() {
  console.log("my name is " + this.name);
};

function _new(Func, ...args) {
  // 实现你的代码
  // 第一步创建实例对象
  // let obj = {}
  // obj.__proto__  = Func.prototype
  let obj = Object.create(Func.prototype);

  // 第二步 执行方法,让里边的this是实例对象
  let result = Func.call(obj, ...args);

  // 分析返回结果
  if (result !== undefined && /^(object|function)$/.test(typeof result)) {
    return result;
  } else {
    return obj;
  }
}

let sanmao = _new(Dog, "三毛");
sanmao.bark(); // 'wangwang'
sanmao.sayName(); // 'my name is wangwang'
console.log(sanmao instanceof Dog); // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 28 重写 call

~(function() {
  function change(context, ...args) {
    // 实现你的代码
    // this 是调用 change 的函数
    context = context == undefined ? window : context;
    let type = typeof context;
    if (!/^(object|function)$/.test(type)) {
      // 如果传入的参数不是一个 object
      if (/^(symbol|bigint)$/.test(type)) {
        // 如果是 symblo 或者 bigint
        context = Object(context);
      } else {
        context = new context.constructor(context);
      }
    }

    let key = Symbol("key");
    let result;
    context[key] = this;
    result = context[key](...args);
    delete context[key];
    return result;
  }
  Function.prototype.change = change;
})();

let obj = { name: "test" };
function func(x, y) {
  this.total = x + y;
  return this;
}

let res = func.change(obj, 100, 200);
// res => {name: 'test', total: 300}
let res1 = func.change("string", 100, 200);
let res2 = func.change(Symbol("123"), 100, 200);

// call 的另外一个问题
// https://stackoverflow.com/questions/34916477/a-is-a-function-then-what-a-call-call-really-do
// 结论, .call....call(context) 相当于 context()
function fn1() {
  console.log("fn1");
}
function fn2() {
  console.log("fn2");
}
fn1.call(fn2); // fn1
fn1.call.call(fn2); // fn2
fn1.call.call.call(fn2); // fn2
Function.prototype.call(fn1); // undefined
Function.prototype.call.call.call(fn1); // fn1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 29 重写 bind

~(function() {
  function bind(context, ...args) {
    // this => func
    let _this = this;
    // 实现你的代码
    // this 是调用 change 的函数
    context = context == undefined ? window : context;
    let type = typeof context;
    if (!/^(object|function)$/.test(type)) {
      // 如果传入的参数不是一个 object
      if (/^(symbol|bigint)$/.test(type)) {
        // 如果是 symblo 或者 bigint
        context = Object(context);
      } else {
        context = new context.constructor(context);
      }
    }

    return function anonymous(...innerArgs) {
      _this.call(context, ...args.concat(innerArgs));
    };
  }
  Function.prototype.bind = bind;
})();

var obj = {
  name: "test",
};

function func() {
  console.log(this, arguments);
}
document.body.onclick = func.bind(obj, 100, 200);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 30 实现 instanceof

参考文章

function new_instance_of(leftVaule, rightVaule) {
  let rightProto = rightVaule.prototype; // 取右表达式的 prototype 值
  leftVaule = leftVaule.__proto__; // 取左表达式的__proto__值
  while (true) {
    if (leftVaule === null) {
      return false;
    }
    if (leftVaule === rightProto) {
      return true;
    }
    leftVaule = leftVaule.__proto__;
  }
}

new_instance_of([12, 23], Array); // true
new_instance_of(Object, Object); // true
new_instance_of(Object, Function); // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 31 大数相加

function bigNumAdd(num1, num2) {
  // 首先检查传来的大数是否是字符串类型,如果传Number类型的大数,在传入的时候已经丢失精度了,
  // 就如 如果传入11111111111111111,处理的时候已经是丢失精度的11111111111111112了,则需要传入
  // 字符串类型的数字 '11111111111111111'
  const checkNum = (num) => typeof num === "string" && !isNaN(Number(num));
  if (checkNum(num1) && checkNum(num2)) {
    // 将传入的数据进行反转,从前向后依次加和,模拟个,十,百依次向上加和
    const tmp1 = num1.split("").reverse();
    const tmp2 = num2.split("").reverse();
    const result = [];
    // 格式化函数,主要针对两个大数长度不一致时,超长的数字的格式化为0
    const format = (val) => {
      if (typeof val === "number") return val;
      if (!isNaN(Number(val))) return Number(val);
      return 0;
    };
    let temp = 0;
    // 以较长的数字为基准进行从前往后逐个加和,为避免两个数相加最高位进位后,导
    // 致结果长度大于两个数字中的长度,for循环加和长度为最长数字长度加一
    for (let i = 0; i <= Math.max(tmp1.length, tmp2.length); i++) {
      const addTmp = format(tmp1[i]) + format(tmp2[i]) + temp;
      // 当加和的数字大于10的情况下,进行进位操作,将要进位的数字赋值给temp,在下一轮使用
      result[i] = addTmp % 10;
      temp = addTmp > 9 ? 1 : 0;
    }
    // 计算完成,反转回来
    result.reverse();
    // 将数组for中多加的一位进行处理,如果最高位没有进位则结果第一个数位0,
    // 如果第一个数位1,则发生了进位。 如99+3,最大数字长度位2,结果数长度位3
    // 此时结果的第一位为1,发生了进位,第一位保留,如果是2+94,第一位为0,则不保留第一位
    const resultNum =
      result[0] > 0 ? result.join("") : result.join("").slice(1);
    console.log("result", resultNum);
    return resultNum;
  } else {
    return "big number type error";
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 33 大数相减

// https://blog.csdn.net/yuzhongchun/article/details/39646073
1

# 34 对象模拟类数组

var obj = {
  "2": 3,
  "3": 4,
  length: 2,
  splice: Array.prototype.splice,
  push: Array.prototype.push,
};
obj.push(1);
obj.push(2);
console.log(obj);
1
2
3
4
5
6
7
8
9
10

# sum 函数实现

function sum(...args) {
  let res = [...args];

  function calc(...args1) {
    res = [...res, ...args1];
    return calc;
  }
  calc.valueOf = function() {
    return res.reduce((prev, next) => prev + next, 0);
  };

  return calc;
}

sum(1, 2)(3, 4).valueOf(); // => 10
sum(1, 2, 3).valueOf(); // => 6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 数组扁平化

let arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10];
/*方案1:使用 Array.prototype.flat 处理*/
arr = arr.flat(Infinity);

/*方案2:把数组直接变为字符串即可*/
arr = arr
  .toString()
  .split(",")
  .map((item) => {
    return Number(item);
  });

/*方案3:JSON.stringify*/
arr = JSON.stringify(arr)
  .replace(/(\[|\])/g, "")
  .split(",")
  .map((item) => Number(item));

/*方案4:基于数组的some方法进行判断检测*/
while (arr.some((item) => Array.isArray(item))) {
  arr = [].concat(...arr);
}

/*方案5:基于递归深度遍历*/
Array.prototype.myFlat = function myFlat() {
  let result = [];
  // =>循环数组中的每一项,把不是数组的存储到新数组中
  let fn = (arr) => {
    for (let i = 0; i < arr.length; i++) {
      let item = arr[i];
      if (Array.isArray(item)) {
        fn(item);
        continue;
      }
      result.push(item);
    }
  };
  fn(this);
  return result;
};
// 方案6: MDN给出reduce方法
// to enable deep level flatten use recursion with reduce and concat
function flatDeep(arr, d = 1) {
  return d > 0
    ? arr.reduce(
        (acc, val) =>
          acc.concat(Array.isArray(val) ? flatDeep(val, d - 1) : val),
        []
      )
    : arr.slice();
}
flatDeep(arr, Infinity);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

# 数组的随机排序

// 方法一
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
function randSort1(arr) {
  for (var i = 0, len = arr.length; i < len; i++) {
    var rand = parseInt(Math.random() * len);
    var temp = arr[rand];
    arr[rand] = arr[i];
    arr[i] = temp;
  }
  return arr;
}
console.log(randSort1(arr));
// 方法二
function randSort2(arr) {
  var mixedArray = [];
  while (arr.length > 0) {
    var randomIndex = parseInt(Math.random() * arr.length);
    mixedArray.push(arr[randomIndex]);
    arr.splice(randomIndex, 1);
  }
  return mixedArray;
}
// 方法三
arr.sort(function() {
  return Math.random() - 0.5;
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 使用 reduce 实现 map

const reduceMap = (arr, cb) => {
  return arr.reduce((prev, cur, index, allArr) => {
    prev.push(cb(cur, index, allArr));
    return prev;
  }, []);
};
1
2
3
4
5
6

# 箭头函数和普通函数的区别

  1. 箭头函数没有自己的 this 对象
  2. 不可以当作构造函数,也就是说,不可以对箭头函数使用 new 命令,否则会抛出一个错误
  3. 不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替
  4. 不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数

# ES6 模块与 CommonJS 模块的差异

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
  • CommonJS 模块的 require()是同步加载模块,ES6 模块的 import 命令是异步加载,有一个独立的模块依赖的解析阶段。

# 各种模块规范

  • IIFE: immediately-invoked function expression
  • ES6 Module: ES6 语法支持
  • CommonJs: Nodejs
  • AMD: Asynchronous Module Definition 用于 require.js
  • CMD: sea.js
  • UMD: Universal Module Definition 一种通用的写法,支持 AMD, CommonJs 规范

# 千分位分隔

function toFixed(num, { precise = 2, toThousand = false } = {}) {
  if (Number.isNaN(num) || num === Infinity || num === undefined || num === 0) {
    return (0).toFixed(precise);
  }

  return toThousand
    ? toThousand(parseFloat(num).toFixed(precise))
    : parseFloat(num).toFixed(precise);
}

function toThousand(num: number | string) {
  const numStr = `${num}`;
  let isAlreadyInt = false;
  let intNumber = numStr.split(".")[0];
  if (intNumber === numStr) {
    isAlreadyInt = true;
  }
  let intLength = intNumber.length;
  while (intLength > 3) {
    intLength -= 3;
    intNumber = `${intNumber.slice(0, intLength)},${intNumber.slice(
      intLength
    )}`;
  }
  return isAlreadyInt ? intNumber : numStr.replace(/^.*\./g, `${intNumber}`);
}

// 使用正则
//这种方法虽然简单便捷,但是不容易懂
function format(v) {
  if (/^[0-9]+$/.test(v)) {
    const reg = /\d{1,3}(?=(\d{3})+$)/g;
    return `${v}`.replace(reg, "$&,");
  } else {
    return "-";
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# async 和 defer 的差异

  • 都是异步加载
  • async 后续执行顺序不确定,defer 按照书写顺序执行
  • 区别在于执行时机。async 会在加载好之后执行,defer 要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成

# DOMContentLoaded 事件和 Load 事件的区别

当开始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的加载完成。Load 事件是当所有资源加载完成后触发的

# 权限校验

  • 登陆态校验
  • 接口权限校验
  • 菜单/按钮/功能权限
  • 数据权限