最大的数

64 位浮点型,符号位 1 位, 指数位 11 位, 尾数 52 位。在 JavaScript 中,2^53 是最大的值。

定时器函数

  • setTimeout 表示间隔一段时间之后执行一次调用
  • setInterval 则是每间隔一段时间循环调用
  • setImmediate 立即执行函数
  • requestAnimationFrame 在浏览器重绘之前执行回调函数(类似 setTimeout 延迟执行)

clearTimeout, clearInterval 函数用来结束尚未执行的 setTimeout 和 setInterval。

setTimeout、setInterval、requestAnimationFrame 各有什么特点?

requestAnimationFrame 在浏览器重绘之前执行回调函数(类似 setTimeout 延迟执行)

因为 JS 是单线程执行的,如果前面的代码影响了性能,就会导致 setTimeout 不会按期执行。setIntervalsetTimeout 一样,不能保证在预期的时间执行任务。而 setInterval 可能经过了很多同步代码的阻塞,导致执行时间不正确了。

requestAnimationFrame 自带函数节流功能,基本可以保证在 16.6 毫秒内只执行一次(不掉帧的情况下),并且该函数的延时效果是精确的,没有其他定时器时间不准的问题。

如果有循环定时器的需求,其实完全可以通过 requestAnimationFrame 来实现

function setInterval(callback, interval) {
  let timer;
  const now = Date.now;
  let startTime = now();
  let endTime = startTime;
  const loop = () => {
    // 一直在执行 loop 直到 cancelAnimationFrame 结束
    console.log("loop");
    timer = window.requestAnimationFrame(loop);
    endTime = now();
    if (endTime - startTime >= interval) {
      startTime = endTime = now();
      callback(timer);
    }
  };
  // 一开始会先执行一次,开启 loop
  timer = window.requestAnimationFrame(loop);
  return timer;
}

let a = 0;
setInterval((timer) => {
  console.log(1);
  a++;
  if (a === 3) cancelAnimationFrame(timer);
}, 1000);

数组

判断数组的方式

  1. Object.prototype.toString.call()
  2. instanceof
  3. Array.isArray()

性能方面:判断数据 Array.isArray() 性能最好,instanceof 次之,Object.prototype.toString.call() 第三 功能方面:Object.prototype.toString.call() 所有的类型都可以判断, 可以理解为是 100% 准确。

Object.prototype.toString.call() 能够得到的值, 8 种: number, boolean, string, undefined, symbol, object, function, bigint。

instanceof 只能判断对象原型,原始类型不可以。

[] instanceof Object; // true

Array.isArray 优于 instanceof ,因为 Array.isArray 可以检测出 frames, Array.isArray() 是 ES5 新增的方法,当使用 ie8 的时候就会出现问题。当不存在 Array.isArray() ,可以用 Object.prototype.toString.call() 实现。

如果浏览器支持 Array.isArray()可以直接判断否则需进行必要判断

function isArray(value) {
  if (typeof Array.isArray === "function") {
    return Array.isArray(value);
  } else {
    return Object.prototype.toString.call(value) === "[object Array]";
  }
}

Array(...)和 Array.of(...) 的区别

Array(...)的作用是接受参数返回一个数组,但是有一个陷阱,如果只传入一个参数,并且这个参数是数 字的话,那么不会构造一个值为这个数字的单个元素的数组,而是构造一个空数组

var a = Array(3);
a.length; // 3
a[0]; // undefined

Array.of(..) 解决掉了这个陷阱

var b = Array.of(3);
b.length; // 1
b[0]; // 3
var c = Array.of(1, 2, 3);
c.length; // 3
c; // 1,2,3

['1', '2', '3'].map(parseInt)

map 会给函数传递 3 个参数: (elem, index, array) 而 parseInt 接收两个参数(sting, radix),其中 radix 代表进制。省略 radix 或 radix = 0,则数字将以十进制解析。 因此,map 遍历 ["1", "2", "3"],相应 parseInt 接收参数如下

parseInt("1", 0); // 1
parseInt("2", 1); // NaN
parseInt("3", 2); // NaN

因为二进制里面,没有数字 3,导致出现超范围的 radix 赋值和不合法的进制解析,才会返回 NaN 所以["1", "2", "3"].map(parseInt) 答案也就是:[1, NaN, NaN]

使用 sort() 对数组 [3, 15, 8, 29, 102, 22] 进行排序,输出结果

[102, 15, 22, 29, 3, 8]

根据 MDN 上对 Array.sort() 的解释,默认的排序方法会将数组元素转换为字符串,然后比较字符串中字符的 UTF-16 编码顺序来进行排序。所以'102' 会排在 '15' 前面。

reduce

reduce 如果没有指定 initialValue,则 accumulator 初始化为数组中的第一个值,并且 callbackFn 从数组中的第二个值作为 currentValue 开始执行。

对象

语法糖

var a = {} 其实是 var a = new Object() 的语法糖 var a = [] 其实是 var a = new Array() 的语法糖 function Foo(){} 其实是 var Foo = new Function(){} 的语法糖

使用 instanceof 判断一个函数是否是一个变量的构造函数

new String

JS 与其他面向对象语言一样有相应的构造器,比如说,可以通过以下两种方式来创建一个字符串:

var a = "woot";
var b = new String("woot");
a + b; // => 'wootwoot'

然而要是对这两个变量使用 typeof 和 instanceof 操作符,事情就变得有意思了:

typeof a; // string
typeof b; // object
a instanceof String; // false
b instanceof String; // true

而事实上,这两个变量值绝对都是货真价实的字符串:

a.substr == b.substr;

而且使用 == 操作符判定两者相等,而使用 === 操作符判定时并不相同:

a == b; // true
a === b; // false

Object.create(null) 原理

将原型链最末端的值替换为 null, 普通声明的对象或者函数,原型链的末端是 Object, 有可能原型链上被挂载了其他的属性和函数,这是不想要的。所以这么操作是为了排除影响。

Object.assign() 的模拟实现

  1. 判断原生 Object 是否支持该函数,如果不存在的话创建一个函数 assign,并使用 Object.defineProperty 将该函数绑定到 Object 上。
  2. 判断参数是否正确(目标对象不能为空,我们可以直接设置{}传递进去,但必须设置值)。
  3. 使用 Object() 转成对象,并保存为 to,最后返回这个对象 to。
  4. 使用 for..in 循环遍历出所有可枚举的自有属性。并复制给新的目标对象(使用 hasOwnProperty 获取自有属性,即非原型链上的属性)。

实现代码如下,这里为了验证方便,使用 assign2 代替 assign。注意此模拟实现不支持 symbol 属性,因为 ES5 中根本没有 symbol 。

if (typeof Object.assign2 != "function") {
  // Attention 1
  Object.defineProperty(Object, "assign2", {
    value: function (target) {
      "use strict";
      if (target == null) {
        // Attention 2
        throw new TypeError("Cannot convert undefined or null to object");
      }

      // Attention 3
      var to = Object(target);

      for (var index = 1; index < arguments.length; index++) {
        var nextSource = arguments[index];

        if (nextSource != null) {
          // Attention 2
          // Attention 4
          for (var nextKey in nextSource) {
            if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
              to[nextKey] = nextSource[nextKey];
            }
          }
        }
      }
      return to;
    },
    writable: true,
    configurable: true,
  });
}

合并两个对象

const merge1 = (toObj, fromObj) => Object.assign(toObj, fromObj);
function merge2(toObj, fromObj) {
  if (typeof toObj === "object" && typeof fromObj === "object") {
    for (var pro in fromObj) {
      if (fromObj.hasOwnProperty(pro)) {
        toObj[pro] = fromObj[pro];
      }
    }
  } else {
    throw "Merge function can apply only on object";
  }
}

如何判断一个对象是空的?

  1. const isEmptyObject = obj => Object.getOwnPropertyNames(obj).length === 0
  2. Object.keys(obj).length == 0

如何防止在 JavaScript 中修改对象 ?

var employee = {
  name: "yiliang",
};

// 对象不可扩展, 即不可以新增属性或方法, 但可以修改/删除
Object.preventExtensions(employee);

// 在上面的基础上,对象属性不可删除, 但可以修改
// 调用 Object.preventExtensions 并且配置 configurable:false;
Object.seal(employee);

// 在上面的基础上,对象所有属性只读, 不可修改
// 调用 Object.seal(..) 并且配置 writable:false。
Object.freeze(employee);

const foo = Object.freeze({});
// 不起作用
foo.prop = 123;

对象的值判断

foo.x的值是什么?

var foo = { n: 1 };
var bar = foo;
foo.x = foo = { n: 2 };
// foo.x = (foo = {n:2}); =>  {n:1}.x = ( {n:1} = {n:2} );  R.H.S. foo={n:2}
// console.log(foo.x); // Result undefined
// console.log(foo); // Result {n:2}
// console.log(bar); // Result {n:1, x: {n:2}}
// Result = undefined

console.log(foo.x); //  undefined
console.log(bar.x); //  {n: 2}

获取对象深度

空对象当作一级,属性值为数组则不向下查找

// 递归,深度优先遍历
function getObjectDeep(obj, arr = []) {
  var count = 0;
  obj && arr.indexOf(obj) === -1 && arr.push(obj);
  if (obj && !Array.isArray(obj) && typeof obj === "object") {
    count = 1;
    var result = Object.keys(obj).map(function (item) {
      if (typeof obj[item] === "object" && arr.indexOf(obj[item]) === -1) {
        // 防止循环引用
        arr.push(obj[item]);
        return 1 + getObjectDeep(obj[item], arr);
      } else {
        return 1;
      }
    });
    for (var i = 0; i < result.length; i++) {
      if (count < result[i]) {
        count = result[i];
      }
    }
  }
  return count;
}

// 非递归,宽度优先遍历
function getObjectDeep(obj) {
  var count = 0;
  var queueObj = [obj];
  var arrs = [obj]; // 防止循环引用
  var last = obj; // 当前层级最后一个

  var checkObj = function (param) {
    return (
      param !== null &&
      param !== undefined &&
      !Array.isArray(param) &&
      typeof param === "object"
    );
  };

  if (checkObj(obj)) {
    while (queueObj.length) {
      var currentObj = queueObj.shift();
      Object.keys(currentObj).forEach(function (item) {
        if (
          checkObj(currentObj[item]) &&
          arrs.indexOf(currentObj[item]) === -1
        ) {
          arrs.push(currentObj[item]);
          queueObj.push(currentObj[item]);
        }
      });
      if (currentObj === last) {
        count += 1;
        last = queueObj.length ? queueObj[queueObj.length - 1] : null;
      }
    }
  }

  return count;
}

对象属性名

var a = {},
  b = "123",
  c = 123;
a[b] = "b";
a[c] = "c";
// 输出 c
console.log(a[b]);
var a = {},
  b = Symbol("123"),
  c = Symbol("123");
a[b] = "b";
a[c] = "c";
console.log(a[b]);
// b
var a = {},
  b = { key: "123" },
  c = { key: "456" };
a[b] = "b";
a[c] = "c";
console.log(a[b]);
// c

toString

[].toString(); // ''
({}).toString(); // "[object Object]"

函数的执行题

函数组合运行 compose

说明:实现一个方法,可将多个函数方法按从左到右的方式组合运行。 如 composeFunctions(fn1,fn2,fn3,fn4) 等价于 fn4(fn3(fn2(fn1))。实例:

const add = (x) => x + 1;
const multiply = (x, y) => x * y;
const multiplyAdd = composeFunctions(multiply, add);
multiplyAdd(3, 4); // 返回 13
function composeFunctions() {
  var slice = Array.prototype.slice;
  var fnArgs = slice.call(arguments, 0);
  return function () {
    var args = slice.call(arguments, 0);
    if (fnArgs.length === 1) {
      return fnArgs[0].apply(this, args);
    }
    return fnArgs.reduce(function (fn1, fn2) {
      return fn2(fn1.apply(this, args));
    });
  };
}
function compose(funcs) {
  var len = funcs.length;
  var index = len - 1;

  for (let i = 0; i < len; i++) {
    if (typeof funcs[i] !== "function") {
      throw new TypeError("Expected a function");
    }
  }

  return function (...args) {
    let result = funcs[index](...args); // 第一次
    while (--index >= 0) {
      result = funcs[index](result);
    }
    return result;
  };
}
// example 1
var a = {},
  b = "123",
  c = 123;
a[b] = "b";
a[c] = "c";
console.log(a[b]);

// example 2
var a = {},
  b = Symbol("123"),
  c = Symbol("123");
a[b] = "b";
a[c] = "c";
console.log(a[b]);

// example 3
var a = {},
  b = { key: "123" },
  c = { key: "456" };
a[b] = "b";
a[c] = "c";
console.log(a[b]);

对象键名的转换:

  • 对象的键名只能是字符串和 Symbol 类型。
  • 其他类型的键名会被转换成字符串类型。
  • 对象转字符串默认会调用 toString 方法。 example 1,c 的键名转换成字符串将 b 键覆盖输出 c example 2,任何一个 Symbol 类型的值都是不相等的,所以 b 键和 c 键都不会被覆盖,输出 b example 3,对象都会被转换为字符串 [object Object],因此 c 键会覆盖 b 键,输出 c
function Foo() {
  Foo.a = function () {
    console.log(1);
  };
  this.a = function () {
    console.log(2);
  };
}
Foo.prototype.a = function () {
  console.log(3);
};
Foo.a = function () {
  console.log(4);
};
Foo.a();
let obj = new Foo();
obj.a();
Foo.a();
var sinaNews = {
  name: "sinNewsName",
  test: function () {
    console.log("this.name:", this.name, "//");
  },
};
setTimeout(sinaNews.test, 500); //
function foo() {
  "use strict";
  console.log(this.a);
}

function bar() {
  console.log(this.a);
}

var a = "this is a 'a'";

bar(); // ?
foo(); // ?
let arr = [1, 2, 3, 4];
let it1 = arr[Symbol.iterator](); // 遍历器接口
let res = it1.next();
console.log(res);

结果是:

{
  done: false,
  value: 1
}

name 的值是多少?

function A(name) {
  this.name = name || "Tom";
  this.msg = "use 'this.' set in function";
}

function B() {}
B.prototype = A;
// B.prototype = new A();

var b = new B();
console.log(b.name);
console.log(b.msg);
// A
// undefined

每一个函数都有一个属性为 name,其值是函数的名字。

function abc() {
  /* 这是一个名为'abc'的函数 */
}
abc.name; // -> 'abc'

进程、线程、协程

https://coffe1891.gitbook.io/frontend-hard-mode-interview/1/1.3.4

进程

进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。

线程

在早期的操作系统中并没有线程的概念,进程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。

线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程 ID、当前指令指针(PC)、寄存器和堆栈组成。而进程由内存空间(代码、数据、进程空间、打开的文件)和一个或多个线程组成。

协程

​ 协程,英文 Coroutines,是一种基于线程之上,但又比线程更加轻量级的存在,这种由程序员自己写程序来管理的轻量级线程叫做『用户空间线程』,具有对内核来说不可见的特性。 ​ 因为是自主开辟的异步任务,所以很多人也更喜欢叫它们纤程(Fiber),或者绿色线程(GreenThread)。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。

它们之间的区别

相对线程和协程,进程更独立,有自己的内存空间,所以进程间通信比较困难。线程比进程轻量级,属于同一进程的多个线程间可以共享全部资源。协程与线程类似,不同点在于,线程由系统控制切换,协程是由用户控制切换。

基本数据类型存储在栈中还是堆中?

对于原始类型,数据本身是存在栈内,对于对象类型,在栈中存的只是一个堆内地址的引用。

讲讲函数式编程的特点?

复用性好、无状态、幂等、延时执行(比如柯里化)

Last Updated:
Contributors: yiliang114