关于javascript:年底前端面试题总结上

8次阅读

共计 13557 个字符,预计需要花费 34 分钟才能阅读完成。

对 keep-alive 的了解

HTTP1.0 中默认是在每次申请 / 应答,客户端和服务器都要新建一个连贯,实现之后立刻断开连接,这就是 短连贯 。当应用 Keep-Alive 模式时,Keep-Alive 性能使客户端到服务器端的连贯继续无效,当呈现对服务器的后继申请时,Keep-Alive 性能防止了建设或者从新建设连贯,这就是 长连贯。其应用办法如下:

  • HTTP1.0 版本是默认没有 Keep-alive 的(也就是默认会发送 keep-alive),所以要想连贯失去放弃,必须手动配置发送 Connection: keep-alive 字段。若想断开 keep-alive 连贯,需发送 Connection:close 字段;
  • HTTP1.1 规定了默认放弃长连贯,数据传输实现了放弃 TCP 连接不断开,期待在同域名下持续用这个通道传输数据。如果须要敞开,须要客户端发送 Connection:close 首部字段。

Keep-Alive 的 建设过程

  • 客户端向服务器在发送申请报文同时在首部增加发送 Connection 字段
  • 服务器收到申请并解决 Connection 字段
  • 服务器回送 Connection:Keep-Alive 字段给客户端
  • 客户端接管到 Connection 字段
  • Keep-Alive 连贯建设胜利

服务端主动断开过程(也就是没有 keep-alive)

  • 客户端向服务器只是发送内容报文(不蕴含 Connection 字段)
  • 服务器收到申请并解决
  • 服务器返回客户端申请的资源并敞开连贯
  • 客户端接管资源,发现没有 Connection 字段,断开连接

客户端申请断开连接过程

  • 客户端向服务器发送 Connection:close 字段
  • 服务器收到申请并解决 connection 字段
  • 服务器回送响应资源并断开连接
  • 客户端接管资源并断开连接

开启 Keep-Alive 的 长处:

  • 较少的 CPU 和内存的使⽤(因为同时关上的连贯的缩小了);
  • 容许申请和应答的 HTTP 管线化;
  • 升高拥塞管制(TCP 连贯缩小了);
  • 缩小了后续申请的提早(⽆需再进⾏握⼿);
  • 报告谬误⽆需敞开 TCP 连;

开启 Keep-Alive 的 毛病

  • 长时间的 Tcp 连贯容易导致系统资源有效占用,节约系统资源。

用过 TypeScript 吗?它的作用是什么?

为 JS 增加类型反对,以及提供最新版的 ES 语法的反对,是的利于团队合作和排错,开发大型项目

事件委托的应用场景

场景:给页面的所有的 a 标签增加 click 事件,代码如下:

document.addEventListener("click", function(e) {if (e.target.nodeName == "A")
        console.log("a");
}, false);

然而这些 a 标签可能蕴含一些像 span、img 等元素,如果点击到了这些 a 标签中的元素,就不会触发 click 事件,因为事件绑定上在 a 标签元素上,而触发这些外部的元素时,e.target 指向的是触发 click 事件的元素(span、img 等其余元素)。

这种状况下就能够应用事件委托来解决,将事件绑定在 a 标签的外部元素上,当点击它的时候,就会逐级向上查找,晓得找到 a 标签为止,代码如下:

document.addEventListener("click", function(e) {
    var node = e.target;
    while (node.parentNode.nodeName != "BODY") {if (node.nodeName == "A") {console.log("a");
            break;
        }
        node = node.parentNode;
    }
}, false);

异步任务调度器

形容:实现一个带并发限度的异步调度器 Scheduler,保障同时运行的工作最多有 limit 个。

实现

class Scheduler {queue = [];  // 用队列保留正在执行的工作
    runCount = 0;  // 计数正在执行的工作个数
    constructor(limit) {this.maxCount = limit;  // 容许并发的最大个数}
    add(time, data){const promiseCreator = () => {return new Promise((resolve, reject) => {setTimeout(() => {console.log(data);
                    resolve();}, time);
            });
        }
        this.queue.push(promiseCreator);
        // 每次增加的时候都会尝试去执行工作
        this.request();}
    request() {
        // 队列中还有工作才会被执行
        if(this.queue.length && this.runCount < this.maxCount) {
            this.runCount++;
            // 执行先退出队列的函数
            this.queue.shift()().then(() => {
                this.runCount--;
                // 尝试进行下一次工作
                this.request();});
        }
    }
}

// 测试
const scheduler = new Scheduler(2);
const addTask = (time, data) => {scheduler.add(time, data);
}

addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');
// 输入后果 2 3 1 4

参考前端进阶面试题具体解答

树形构造转成列表

题目形容:

[
    {
        id: 1,
        text: '节点 1',
        parentId: 0,
        children: [
            {
                id:2,
                text: '节点 1_1',
                parentId:1
            }
        ]
    }
]
转成
[
    {
        id: 1,
        text: '节点 1',
        parentId: 0 // 这里用 0 示意为顶级节点
    },
    {
        id: 2,
        text: '节点 1_1',
        parentId: 1 // 通过这个字段来确定子父级
    }
    ...
]

实现代码如下:

function treeToList(data) {let res = [];
  const dfs = (tree) => {tree.forEach((item) => {if (item.children) {dfs(item.children);
        delete item.children;
      }
      res.push(item);
    });
  };
  dfs(data);
  return res;
}

具体阐明 Event loop

家喻户晓 JS 是门非阻塞单线程语言,因为在最后 JS 就是为了和浏览器交互而诞生的。如果 JS 是门多线程的语言话,咱们在多个线程中解决 DOM 就可能会产生问题(一个线程中新加节点,另一个线程中删除节点),当然能够引入读写锁解决这个问题。

JS 在执行的过程中会产生执行环境,这些执行环境会被程序的退出到执行栈中。如果遇到异步的代码,会被挂起并退出到 Task(有多种 task)队列中。一旦执行栈为空,Event Loop 就会从 Task 队列中拿出须要执行的代码并放入执行栈中执行,所以实质上来说 JS 中的异步还是同步行为。

console.log('script start');

setTimeout(function() {console.log('setTimeout');
}, 0);

console.log('script end');

以上代码尽管 setTimeout 延时为 0,其实还是异步。这是因为 HTML5 标准规定这个函数第二个参数不得小于 4 毫秒,有余会主动减少。所以 setTimeout 还是会在 script end 之后打印。

不同的工作源会被调配到不同的 Task 队列中,工作源能够分为 微工作(microtask)和 宏工作(macrotask)。在 ES6 标准中,microtask 称为 jobs,macrotask 称为 task

console.log('script start');

setTimeout(function() {console.log('setTimeout');
}, 0);

new Promise((resolve) => {console.log('Promise')
    resolve()}).then(function() {console.log('promise1');
}).then(function() {console.log('promise2');
});

console.log('script end');
// script start => Promise => script end => promise1 => promise2 => setTimeout

以上代码尽管 setTimeout 写在 Promise 之前,然而因为 Promise 属于微工作而 setTimeout 属于宏工作,所以会有以上的打印。

微工作包含 process.nextTickpromiseObject.observeMutationObserver

宏工作包含 scriptsetTimeoutsetIntervalsetImmediateI/OUI rendering

很多人有个误区,认为微工作快于宏工作,其实是谬误的。因为宏工作中包含了 script,浏览器会先执行一个宏工作,接下来有异步代码的话就先执行微工作。

所以正确的一次 Event loop 程序是这样的

  1. 执行同步代码,这属于宏工作
  2. 执行栈为空,查问是否有微工作须要执行
  3. 执行所有微工作
  4. 必要的话渲染 UI
  5. 而后开始下一轮 Event loop,执行宏工作中的异步代码

通过上述的 Event loop 程序可知,如果宏工作中的异步代码有大量的计算并且须要操作 DOM 的话,为了更快的 界面响应,咱们能够把操作 DOM 放入微工作中。

Node 中的 Event loop

Node 中的 Event loop 和浏览器中的不雷同。

Node 的 Event loop 分为 6 个阶段,它们会依照程序重复运行

┌───────────────────────┐
┌─>│        timers         │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     I/O callbacks     │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     idle, prepare     │
│  └──────────┬────────────┘      ┌───────────────┐
│  ┌──────────┴────────────┐      │   incoming:   │
│  │         poll          │<──connections───     │
│  └──────────┬────────────┘      │   data, etc.  │
│  ┌──────────┴────────────┐      └───────────────┘
│  │        check          │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
└──┤    close callbacks    │
   └───────────────────────┘
timer

timers 阶段会执行 setTimeoutsetInterval

一个 timer 指定的工夫并不是精确工夫,而是在达到这个工夫后尽快执行回调,可能会因为零碎正在执行别的事务而提早。

上限的工夫有一个范畴:[1, 2147483647],如果设定的工夫不在这个范畴,将被设置为 1。

I/O

I/O 阶段会执行除了 close 事件,定时器和 setImmediate 的回调

idle, prepare

idle, prepare 阶段外部实现

poll

poll 阶段很重要,这一阶段中,零碎会做两件事件

  1. 执行到点的定时器
  2. 执行 poll 队列中的事件

并且当 poll 中没有定时器的状况下,会发现以下两件事件

  • 如果 poll 队列不为空,会遍历回调队列并同步执行,直到队列为空或者零碎限度
  • 如果 poll 队列为空,会有两件事产生

    • 如果有 setImmediate 须要执行,poll 阶段会进行并且进入到 check 阶段执行 setImmediate
    • 如果没有 setImmediate 须要执行,会期待回调被退出到队列中并立刻执行回调

如果有别的定时器须要被执行,会回到 timer 阶段执行回调。

check

check 阶段执行 setImmediate

close callbacks

close callbacks 阶段执行 close 事件

并且在 Node 中,有些状况下的定时器执行程序是随机的

setTimeout(() => {console.log('setTimeout');
}, 0);
setImmediate(() => {console.log('setImmediate');
})
// 这里可能会输入 setTimeout,setImmediate
// 可能也会相同的输入,这取决于性能
// 因为可能进入 event loop 用了不到 1 毫秒,这时候会执行 setImmediate
// 否则会执行 setTimeout

当然在这种状况下,执行程序是雷同的

var fs = require('fs')

fs.readFile(__filename, () => {setTimeout(() => {console.log('timeout');
    }, 0);
    setImmediate(() => {console.log('immediate');
    });
});
// 因为 readFile 的回调在 poll 中执行
// 发现有 setImmediate,所以会立刻跳到 check 阶段执行回调
// 再去 timer 阶段执行 setTimeout
// 所以以上输入肯定是 setImmediate,setTimeout

下面介绍的都是 macrotask 的执行状况,microtask 会在以上每个阶段实现后立刻执行。

setTimeout(()=>{console.log('timer1')

    Promise.resolve().then(function() {console.log('promise1')
    })
}, 0)

setTimeout(()=>{console.log('timer2')

    Promise.resolve().then(function() {console.log('promise2')
    })
}, 0)

// 以上代码在浏览器和 node 中打印状况是不同的
// 浏览器中打印 timer1, promise1, timer2, promise2
// node 中打印 timer1, timer2, promise1, promise2

Node 中的 process.nextTick 会先于其余 microtask 执行。

setTimeout(() => {console.log("timer1");

  Promise.resolve().then(function() {console.log("promise1");
  });
}, 0);

process.nextTick(() => {console.log("nextTick");
});
// nextTick, timer1, promise1

常见浏览器所用内核

(1)IE 浏览器内核:Trident 内核,也是俗称的 IE 内核;

(2)Chrome 浏览器内核:统称为 Chromium 内核或 Chrome 内核,以前是 Webkit 内核,当初是 Blink 内核;

(3)Firefox 浏览器内核:Gecko 内核,俗称 Firefox 内核;

(4)Safari 浏览器内核:Webkit 内核;

(5)Opera 浏览器内核:最后是本人的 Presto 内核,起初退出谷歌大军,从 Webkit 又到了 Blink 内核;

(6)360 浏览器、猎豹浏览器内核:IE + Chrome 双内核;

(7)搜狗、漫游、QQ 浏览器内核:Trident(兼容模式)+ Webkit(高速模式);

(8)百度浏览器、世界之窗内核:IE 内核;

(9)2345 浏览器内核:如同以前是 IE 内核,当初也是 IE + Chrome 双内核了;

(10)UC 浏览器内核:这个众口不一,UC 说是他们本人研发的 U3 内核,但如同还是基于 Webkit 和 Trident,还有说是基于火狐内核。

instanceof

作用:判断对象的具体类型。能够区别 arrayobjectnullobject 等。

语法A instanceof B

如何判断的?: 如果 B 函数的显式原型对象在 A 对象的原型链上,返回true,否则返回false

留神:如果检测原始值,则始终返回 false

实现:

function myinstanceof(left, right) {
    // 根本数据类型都返回 false,留神 typeof 函数 返回 "function"
    if((typeof left !== "object" && typeof left !== "function") || left === null) return false;
    let leftPro = left.__proto__;  // 取右边的(隐式)原型 __proto__
    // left.__proto__ 等价于 Object.getPrototypeOf(left)
    while(true) {
        // 判断是否到原型链顶端
        if(leftPro === null) return false;
        // 判断左边的显式原型 prototype 对象是否在右边的原型链上
        if(leftPro === right.prototype) return true;
        // 原型链查找
        leftPro = leftPro.__proto__;
    }
}

LRU 算法

实现代码如下:

//  一个 Map 对象在迭代时会依据对象中元素的插入程序来进行
// 新增加的元素会被插入到 map 的开端,整个栈倒序查看
class LRUCache {constructor(capacity) {this.secretKey = new Map();
    this.capacity = capacity;
  }
  get(key) {if (this.secretKey.has(key)) {let tempValue = this.secretKey.get(key);
      this.secretKey.delete(key);
      this.secretKey.set(key, tempValue);
      return tempValue;
    } else return -1;
  }
  put(key, value) {
    // key 存在,仅批改值
    if (this.secretKey.has(key)) {this.secretKey.delete(key);
      this.secretKey.set(key, value);
    }
    // key 不存在,cache 未满
    else if (this.secretKey.size < this.capacity) {this.secretKey.set(key, value);
    }
    // 增加新 key,删除旧 key
    else {this.secretKey.set(key, value);
      // 删除 map 的第一个元素,即为最长未应用的
      this.secretKey.delete(this.secretKey.keys().next().value);
    }
  }
}
// let cache = new LRUCache(2);
// cache.put(1, 1);
// cache.put(2, 2);
// console.log("cache.get(1)", cache.get(1))// 返回  1
// cache.put(3, 3);// 该操作会使得密钥 2 作废
// console.log("cache.get(2)", cache.get(2))// 返回 -1 (未找到)
// cache.put(4, 4);// 该操作会使得密钥 1 作废
// console.log("cache.get(1)", cache.get(1))// 返回 -1 (未找到)
// console.log("cache.get(3)", cache.get(3))// 返回  3
// console.log("cache.get(4)", cache.get(4))// 返回  4

实现模板字符串解析性能

题目形容:

let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {
  name: '姓名',
  age: 18
}
render(template, data); // 我是姓名,年龄 18,性别 undefined

实现代码如下:

function render(template, data) {let computed = template.replace(/\{\{(\w+)\}\}/g, function (match, key) {return data[key];
  });
  return computed;
}

代码输入后果

async function async1 () {await async2();
  console.log('async1');
  return 'async1 success'
}
async function async2 () {return new Promise((resolve, reject) => {console.log('async2')
    reject('error')
  })
}
async1().then(res => console.log(res))

输入后果如下:

async2
Uncaught (in promise) error

能够看到,如果 async 函数中抛出了谬误,就会终止谬误后果,不会持续向下执行。

如果想要让谬误不足之处前面的代码执行,能够应用 catch 来捕捉:

async function async1 () {await Promise.reject('error!!!').catch(e => console.log(e))
  console.log('async1');
  return Promise.resolve('async1 success')
}
async1().then(res => console.log(res))
console.log('script start')

这样的输入后果就是:

script start
error!!!
async1
async1 success

深 / 浅拷贝

首先判断数据类型是否为对象,如果是对象(数组 | 对象),则递归(深 / 浅拷贝),否则间接拷贝。

function isObject(obj) {return typeof obj === "object" && obj !== null;}

这个函数只能判断 obj 是否是对象,无奈判断其具体是数组还是对象。

防抖节流

题目形容: 手写防抖节流

实现代码如下:

// 防抖
function debounce(fn, delay = 300) {
  // 默认 300 毫秒
  let timer;
  return function () {
    const args = arguments;
    if (timer) {clearTimeout(timer);
    }
    timer = setTimeout(() => {fn.apply(this, args); // 扭转 this 指向为调用 debounce 所指的对象
    }, delay);
  };
}

window.addEventListener(
  "scroll",
  debounce(() => {console.log(111);
  }, 1000)
);

// 节流
// 设置一个标记
function throttle(fn, delay) {
  let flag = true;
  return () => {if (!flag) return;
    flag = false;
    timer = setTimeout(() => {fn();
      flag = true;
    }, delay);
  };
}

window.addEventListener(
  "scroll",
  throttle(() => {console.log(111);
  }, 1000)
);

代码输入后果

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)

输入后果如下:

1

看到这个题目,好多的 then,实际上只须要记住一个准则:.then.catch 的参数冀望是函数,传入非函数则会产生 值透传

第一个 then 和第二个 then 中传入的都不是函数,一个是数字,一个是对象,因而产生了透传,将resolve(1) 的值间接传到最初一个 then 里,间接打印出 1。

代码输入后果

var a, b
(function () {console.log(a);
   console.log(b);
   var a = (b = 3);
   console.log(a);
   console.log(b);   
})()
console.log(a);
console.log(b);

输入后果:

undefined 
undefined 
3 
3 
undefined 
3

这个题目和下面题目考查的知识点相似,b 赋值为 3,b 此时是一个全局变量,而将 3 赋值给 a,a 是一个局部变量,所以最初打印的时候,a 仍旧是 undefined。

浏览器的次要组成部分

  • ⽤户界⾯ 包含地址栏、后退 / 后退按钮、书签菜单等。除了浏览器主窗⼝显示的您申请的⻚⾯外,其余显示的各个局部都属于⽤户界⾯。
  • 浏览器引擎 在⽤户界⾯和出现引擎之间传送指令。
  • 出现引擎 负责显示申请的内容。如果申请的内容是 HTML,它就负责解析 HTML 和 CSS 内容,并将解析后的内容显示在屏幕上。
  • ⽹络 ⽤于⽹络调⽤,⽐如 HTTP 申请。其接⼝与平台⽆关,并为所有平台提供底层实现。
  • ⽤户界⾯后端 ⽤于绘制根本的窗⼝⼩部件,⽐如组合框和窗⼝。其公开了与平台⽆关的通⽤接⼝,⽽在底层使⽤操作系统的⽤户界⾯⽅法。
  • JavaScript 解释器。⽤于解析和执⾏ JavaScript 代码。
  • 数据存储 这是长久层。浏览器须要在硬盘上保留各种数据,例如 Cookie。新的 HTML 标准 (HTML5) 定义了“⽹络数据库”,这是⼀个残缺(然而轻便)的浏览器内数据库。

值得注意的是,和⼤少数浏览器不同,Chrome 浏览器的每个标签⻚都别离对应⼀个出现引擎实例。每个标签⻚都是⼀个独⽴的过程。

代码输入后果

async function async1() {console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {console.log("async2");
}
async1();
console.log('start')

输入后果如下:

async1 start
async2
start
async1 end

代码的执行过程如下:

  1. 首先执行函数中的同步代码 async1 start,之后遇到了await,它会阻塞async1 前面代码的执行,因而会先去执行 async2 中的同步代码async2,而后跳出async1
  2. 跳出 async1 函数后,执行同步代码start
  3. 在一轮宏工作全副执行完之后,再来执行 await 前面的内容async1 end

这里能够了解为 await 前面的语句相当于放到了 new Promise 中,下一行及之后的语句相当于放在 Promise.then 中。

数组扁平化

ES5 递归写法 —— isArray()、concat()

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

如果想实现第二个参数(指定“拉平”的层数),能够这样实现,前面的几种能够本人相似实现:

function flat(arr, level = 1) {var res = [];
    for(var i = 0; i < arr.length; i++) {if(Array.isArray(arr[i]) || level >= 1) {res = res.concat(flat(arr[i]), level - 1);
        }
        else {res.push(arr[i]);
        }
    }
    return res;
}

ES6 递归写法 — reduce()、concat()、isArray()

function flat(arr) {
    return arr.reduce((pre, cur) => pre.concat(Array.isArray(cur) ? flat(cur) : cur), []);
}

ES6 迭代写法 — 扩大运算符(…)、some()、concat()、isArray()

ES6 的扩大运算符(…) 只能扁平化一层

function flat(arr) {return [].concat(...arr);
}

全副扁平化 :遍历原数组,若arr 中含有数组则应用一次扩大运算符,直至没有为止。

function flat(arr) {while(arr.some(item => Array.isArray(item))) {arr = [].concat(...arr);
    }
    return arr;
}

toString/join & split

调用数组的 toString()/join() 办法(它会主动扁平化解决),将数组变为字符串而后再用 split 宰割还原为数组。因为 split 宰割后造成的数组的每一项值为字符串,所以须要用一个 map 办法遍历数组将其每一项转换为数值型。

function flat(arr){return arr.toString().split(',').map(item => Number(item));
    // return arr.join().split(',').map(item => Number(item));
}

应用正则

JSON.stringify(arr).replace(/[|]/g, '') 会先将数组 arr 序列化为字符串,而后应用 replace() 办法将字符串中所有的[] 替换成空字符,从而达到扁平化解决,此时的后果为 arr 不蕴含 [] 的字符串。最初通过JSON.parse() 解析字符串。

function flat(arr) {return JSON.parse("[" + JSON.stringify(arr).replace(/\[|\]/g,'') +"]");
}

类数组转化为数组

类数组是具备 length 属性,但不具备数组原型上的办法。常见的类数组有 arguments、DOM 操作方法返回的后果 (如document.querySelectorAll('div')) 等。

扩大运算符(…)

留神:扩大运算符只能作用于 iterable 对象,即领有 Symbol(Symbol.iterator) 属性值。

let arr = [...arrayLike]

Array.from()

let arr = Array.from(arrayLike);

Array.prototype.slice.call()

let arr = Array.prototype.slice.call(arrayLike);

Array.apply()

let arr = Array.apply(null, arrayLike);

concat + apply

let arr = Array.prototype.concat.apply([], arrayLike);

代码输入问题

function A(){}
function B(a){this.a = a;}
function C(a){if(a){this.a = a;}
}
A.prototype.a = 1;
B.prototype.a = 1;
C.prototype.a = 1;

console.log(new A().a);
console.log(new B().a);
console.log(new C(2).a);

输入后果:1 undefined 2

解析:

  1. console.log(new A().a),new A()为构造函数创立的对象,自身没有 a 属性,所以向它的原型去找,发现原型的 a 属性的属性值为 1,故该输入值为 1;
  2. console.log(new B().a),ew B()为构造函数创立的对象,该构造函数有参数 a,但该对象没有传参,故该输入值为 undefined;
  3. console.log(new C(2).a),new C()为构造函数创立的对象,该构造函数有参数 a,且传的实参为 2,执行函数外部,发现 if 为真,执行 this.a = 2, 故属性 a 的值为 2。

箭头函数和一般函数有啥区别?箭头函数能当构造函数吗?

  • 一般函数通过 function 关键字定义,this 无奈联合词法作用域应用,在运行时绑定,只取决于函数的调用形式,在哪里被调用,调用地位。(取决于调用者,和是否独立运行)
  • 箭头函数应用被称为“胖箭头”的操作 => 定义,箭头函数不利用一般函数 this 绑定的四种规定,而是依据外层(函数或全局)的作用域来决定 this,且箭头函数的绑定无奈被批改(new 也不行)。

    • 箭头函数罕用于回调函数中,包含事件处理器或定时器
    • 箭头函数和 var self = this,都试图取代传统的 this 运行机制,将 this 的绑定拉回到词法作用域
    • 没有原型、没有 this、没有 super,没有 arguments,没有 new.target
    • 不能通过 new 关键字调用

      • 一个函数外部有两个办法:[[Call]] 和 [[Construct]],在通过 new 进行函数调用时,会执行 [[construct]] 办法,创立一个实例对象,而后再执行这个函数体,将函数的 this 绑定在这个实例对象上
      • 当间接调用时,执行 [[Call]] 办法,间接执行函数体
      • 箭头函数没有 [[Construct]] 办法,不能被用作结构函数调用,当应用 new 进行函数调用时会报错。
function foo() {return (a) => {console.log(this.a);
  }
}

var obj1 = {a: 2}

var obj2 = {a: 3}

var bar = foo.call(obj1);
bar.call(obj2);
正文完
 0