面试题-4 2020-07-03 前端,Javascript 暂无评论 186 次阅读 ### 36. 简单介绍一下 V8 引擎的垃圾回收机制 ``` v8 的垃圾回收机制基于分代回收机制,这个机制又基于世代假说,这个假说有两个特点,一是新生的对象容易早死,另一个是不死的对象会活得更久。基于这个假说,v8 引擎将内存分为了新生代和老生代。 新创建的对象或者只经历过一次的垃圾回收的对象被称为新生代。经历过多次垃圾回收的对象被称为老生代。 新生代被分为 From 和 To 两个空间,To 一般是闲置的。当 From 空间满了的时候会执行 Scavenge 算法进行垃圾回收。当我们执行垃圾回收算法的时候应用逻辑将会停止,等垃圾回收结束后再继续执行。这个算法分为三步: (1)首先检查 From 空间的存活对象,如果对象存活则判断对象是否满足晋升到老生代的条件,如果满足条件则晋升到老生代。如果不满足条件则移动 To 空间。 (2)如果对象不存活,则释放对象的空间。 (3)最后将 From 空间和 To 空间角色进行交换。 新生代对象晋升到老生代有两个条件: (1)第一个是判断是对象否已经经过一次 Scavenge 回收。若经历过,则将对象从 From 空间复制到老生代中;若没有经历,则复制到 To 空间。 (2)第二个是 To 空间的内存使用占比是否超过限制。当对象从 From 空间复制到 To 空间时,若 To 空间使用超过 25%,则对象直接晋升到老生代中。设置 25% 的原因主要是因为算法结束后,两个空间结束后会交换位置,如果 To 空间的内存太小,会影响后续的内存分配。 老生代采用了标记清除法和标记压缩法。标记清除法首先会对内存中存活的对象进行标记,标记结束后清除掉那些没有标记的对象。由于标记清除后会造成很多的内存碎片,不便于后面的内存分配。所以了解决内存碎片的问题引入了标记压缩法。 由于在进行垃圾回收的时候会暂停应用的逻辑,对于新生代方法由于内存小,每次停顿的时间不会太长,但对于老生代来说每次垃圾回收的时间长,停顿会造成很大的影响。 为了解决这个问题 V8 引入了增量标记的方法,将一次停顿进行的过程分为了多步,每次执行完一小步就让运行逻辑执行一会,就这样交替运行。 ``` 相关资料: [《深入理解 V8 的垃圾回收原理》](https://www.jianshu.com/p/b8ed21e8a4fb) [《JavaScript 中的垃圾回收》](https://zhuanlan.zhihu.com/p/23992332) ### 37. 哪些操作会造成内存泄漏? - 1.意外的全局变量 - 2.被遗忘的计时器或回调函数 - 3.脱离 DOM 的引用 - 4.闭包 * 第一种情况是我们由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。 * 第二种情况是我们设置了`setInterval`定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。 * 第三种情况是我们获取一个DOM元素的引用,而后面这个元素被删除,由于我们一直保留了对这个元素的引用,所以它也无法被回收。 * 第四种情况是不合理的使用闭包,从而导致某些变量一直被留在内存当中。 相关资料: [《JavaScript 内存泄漏教程》](http://www.ruanyifeng.com/blog/2017/04/memory-leak.html) [《4 类 JavaScript 内存泄漏及如何避免》](https://jinlong.github.io/2016/05/01/4-Types-of-Memory-Leaks-in-JavaScript-and-How-to-Get-Rid-Of-Them/) [《杜绝 js 中四种内存泄漏类型的发生》](https://juejin.im/entry/5a64366c6fb9a01c9332c706) [《javascript 典型内存泄漏及 chrome 的排查方法》](https://segmentfault.com/a/1190000008901861) ```! 以下38~46条是ECMAScript 2015(ES6)中常考的基础知识点 ``` ### 38. ECMAScript 是什么? ECMAScript 是编写脚本语言的标准,这意味着JavaScript遵循ECMAScript标准中的规范变化,因为它是JavaScript的蓝图。 ECMAScript 和 Javascript,本质上都跟一门语言有关,一个是语言本身的名字,一个是语言的约束条件 只不过发明JavaScript的那个人(Netscape公司),把东西交给了ECMA(European Computer Manufacturers Association),这个人规定一下他的标准,因为当时有java语言了,又想强调这个东西是让ECMA这个人定的规则,所以就这样一个神奇的东西诞生了,这个东西的名称就叫做ECMAScript。 javaScript = ECMAScript + DOM + BOM(自认为是一种广义的JavaScript) ECMAScript说什么JavaScript就得做什么! JavaScript(狭义的JavaScript)做什么都要问问ECMAScript我能不能这样干!如果不能我就错了!能我就是对的! ——突然感觉JavaScript好没有尊严,为啥要搞个人出来约束自己, 那个人被创造出来也好委屈,自己被创造出来完全是因为要约束JavaScript。 ### 39. ECMAScript 2015(ES6)有哪些新特性? * 块作用域 * 类 * 箭头函数 * 模板字符串 * 加强的对象字面 * 对象解构 * Promise * 模块 * Symbol * 代理(proxy)Set * 函数默认参数 * rest 和展开 ### 40. `var`,`let`和`const`的区别是什么? var声明的变量会挂载在window上,而let和const声明的变量不会: ```js var a = 100; console.log(a,window.a); // 100 100 let b = 10; console.log(b,window.b); // 10 undefined const c = 1; console.log(c,window.c); // 1 undefined ``` var声明变量存在变量提升,let和const不存在变量提升: ```js console.log(a); // undefined ===> a已声明还没赋值,默认得到undefined值 var a = 100; console.log(b); // 报错:b is not defined ===> 找不到b这个变量 let b = 10; console.log(c); // 报错:c is not defined ===> 找不到c这个变量 const c = 10; ``` let和const声明形成块作用域 ```js if(1){ var a = 100; let b = 10; } console.log(a); // 100 console.log(b) // 报错:b is not defined ===> 找不到b这个变量 ------------------------------------------------------------- if(1){ var a = 100; const c = 1; } console.log(a); // 100 console.log(c) // 报错:c is not defined ===> 找不到c这个变量 ``` 同一作用域下let和const不能声明同名变量,而var可以 ```js var a = 100; console.log(a); // 100 var a = 10; console.log(a); // 10 ------------------------------------- let a = 100; let a = 10; // 控制台报错:Identifier 'a' has already been declared ===> 标识符a已经被声明了。 ``` 暂存死区 ```js var a = 100; if(1){ a = 10; //在当前块作用域中存在a使用let/const声明的情况下,给a赋值10时,只会在当前作用域找变量a, // 而这时,还未到声明时候,所以控制台Error:a is not defined let a = 1; } ``` const ```js /* * 1、一旦声明必须赋值,不能使用null占位。 * * 2、声明后不能再修改 * * 3、如果声明的是复合类型数据,可以修改其属性 * * */ const a = 100; const list = []; list[0] = 10; console.log(list); // [10] const obj = {a:100}; obj.name = 'apple'; obj.a = 10000; console.log(obj); // {a:10000,name:'apple'} ``` ### 41. 什么是箭头函数? 箭头函数表达式的语法比函数表达式更简洁,并且没有自己的`this,arguments,super或new.target`。箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数。 ```js //ES5 Version var getCurrentDate = function (){ return new Date(); } //ES6 Version const getCurrentDate = () => new Date(); ``` 在本例中,ES5 版本中有`function(){}`声明和return关键字,这两个关键字分别是创建函数和返回值所需要的。在箭头函数版本中,我们只需要()括号,不需要 return 语句,因为如果我们只有一个表达式或值需要返回,箭头函数就会有一个隐式的返回。 ```js //ES5 Version function greet(name) { return 'Hello ' + name + '!'; } //ES6 Version const greet = (name) => `Hello ${name}`; const greet2 = name => `Hello ${name}`; ``` 我们还可以在箭头函数中使用与函数表达式和函数声明相同的参数。如果我们在一个箭头函数中有一个参数,则可以省略括号。 ```js const getArgs = () => arguments const getArgs2 = (...rest) => rest ``` 箭头函数不能访问arguments对象。所以调用第一个getArgs函数会抛出一个错误。相反,我们可以使用rest参数来获得在箭头函数中传递的所有参数。 ```js const data = { result: 0, nums: [1, 2, 3, 4, 5], computeResult() { // 这里的“this”指的是“data”对象 const addAll = () => { return this.nums.reduce((total, cur) => total + cur, 0) }; this.result = addAll(); } }; ``` 箭头函数没有自己的this值。它捕获词法作用域函数的this值,在此示例中,addAll函数将复制computeResult 方法中的this值,如果我们在全局作用域声明箭头函数,则this值为 window 对象。 ### 42. 什么是类? 类(class)是在 JS 中编写构造函数的新方法。它是使用构造函数的语法糖,在底层中使用仍然是原型和基于原型的继承。 ```js //ES5 Version function Person(firstName, lastName, age, address){ this.firstName = firstName; this.lastName = lastName; this.age = age; this.address = address; } Person.self = function(){ return this; } Person.prototype.toString = function(){ return "[object Person]"; } Person.prototype.getFullName = function (){ return this.firstName + " " + this.lastName; } //ES6 Version class Person { constructor(firstName, lastName, age, address){ this.lastName = lastName; this.firstName = firstName; this.age = age; this.address = address; } static self() { return this; } toString(){ return "[object Person]"; } getFullName(){ return `${this.firstName} ${this.lastName}`; } } ``` 重写方法并从另一个类继承。 ```js //ES5 Version Employee.prototype = Object.create(Person.prototype); function Employee(firstName, lastName, age, address, jobTitle, yearStarted) { Person.call(this, firstName, lastName, age, address); this.jobTitle = jobTitle; this.yearStarted = yearStarted; } Employee.prototype.describe = function () { return `I am ${this.getFullName()} and I have a position of ${this.jobTitle} and I started at ${this.yearStarted}`; } Employee.prototype.toString = function () { return "[object Employee]"; } //ES6 Version class Employee extends Person { //Inherits from "Person" class constructor(firstName, lastName, age, address, jobTitle, yearStarted) { super(firstName, lastName, age, address); this.jobTitle = jobTitle; this.yearStarted = yearStarted; } describe() { return `I am ${this.getFullName()} and I have a position of ${this.jobTitle} and I started at ${this.yearStarted}`; } toString() { // Overriding the "toString" method of "Person" return "[object Employee]"; } } ``` 所以我们要怎么知道它在内部使用原型? ```js class Something { } function AnotherSomething(){ } const as = new AnotherSomething(); const s = new Something(); console.log(typeof Something); // "function" console.log(typeof AnotherSomething); // "function" console.log(as.toString()); // "[object Object]" console.log(as.toString()); // "[object Object]" console.log(as.toString === Object.prototype.toString); // true console.log(s.toString === Object.prototype.toString); // true ``` 相关资料: [《ECMAScript 6 实现了 class,对 JavaScript 前端开发有什么意义?》](https://www.zhihu.com/question/29789315) [《Class 的基本语法》](http://es6.ruanyifeng.com/#docs/class) ### 43. 什么是模板字符串? 模板字符串是在 JS 中创建字符串的一种新方法。我们可以通过使用反引号使模板字符串化。 ```js //ES5 Version var greet = 'Hi I\'m Mark'; //ES6 Version let greet = `Hi I'm Mark`; ``` 在 ES5 中我们需要使用一些转义字符来达到多行的效果,在模板字符串不需要这么麻烦: ```js //ES5 Version var lastWords = '\n' + ' I \n' + ' Am \n' + 'Iron Man \n'; //ES6 Version let lastWords = ` I Am Iron Man `; ``` 在ES5版本中,我们需要添加\n以在字符串中添加新行。在模板字符串中,我们不需要这样做。 ```js //ES5 Version function greet(name) { return 'Hello ' + name + '!'; } //ES6 Version function greet(name) { return `Hello ${name} !`; } ``` 在 ES5 版本中,如果需要在字符串中添加表达式或值,则需要使用`+`运算符。在模板字符串s中,我们可以使用`${expr}`嵌入一个表达式,这使其比 ES5 版本更整洁。 ### 44. 什么是对象解构? 对象析构是从对象或数组中获取或提取值的一种新的、更简洁的方法。假设有如下的对象: ```js const employee = { firstName: "Marko", lastName: "Polo", position: "Software Developer", yearHired: 2017 }; ``` 从对象获取属性,早期方法是创建一个与对象属性同名的变量。这种方法很麻烦,因为我们要为每个属性创建一个新变量。假设我们有一个大对象,它有很多属性和方法,用这种方法提取属性会很麻烦。 ```js var firstName = employee.firstName; var lastName = employee.lastName; var position = employee.position; var yearHired = employee.yearHired; ``` 使用解构方式语法就变得简洁多了: ```js { firstName, lastName, position, yearHired } = employee; ``` 我们还可以为属性取别名: ```js let { firstName: fName, lastName: lName, position, yearHired } = employee; ``` 当然如果属性值为 undefined 时,我们还可以指定默认值: ```js let { firstName = "Mark", lastName: lName, position, yearHired } = employee; ``` 标签: js面试题 本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
评论已关闭