如何简洁清晰地回答JavaScript基础面试题

81 阅读5分钟

有哪几种数据类型?

ES6包含8种数据类型,分别为: Number / String / Boolean / Object / Null / Undefined / Symbol / Bigint。

Number默认为双精度浮点数,而Bigint是很大的整数(数字后面加n表示)。 Symbol用于作为唯一标识。

原型链是什么?

思路:一个大的概念,最好分割成小概念,并把抽象的化为具体例子说明。

原型链涉及的概念挺多的,我举例说明一下吧。 假设我们有一个普通的对象x={},这个x会有一个隐藏属性叫__proto__,指向Object.prototype,即

x.__proto__ === Object.prototype

此时,我们把Object.prototype称为x的原型。

__proto__这个隐藏属性唯一的作用就是指向x的原型,如果没有__proto__这个隐藏属性,它就没有原型。

接下来我来我来举例说明一下原型链。 假设我们有一个普通的数组对象a=[],这个a也会有一个隐藏属性叫__proto__,指向Array.prototype,即

a.__proto === Array.prototype

此时,我们把Array.prototype称为a的原型。这跟上面距离的x的不同之处在于,Array.prototype也有个隐藏属性__proto__,它指向了Object.prototype,即

a.__proto__.__proto__ === Object.prototype

这样一来a就有了两层原型:

a => Array.prototype => Object.prototype

这个通过隐藏属性形成的链条就是原型链。

怎么自定义原型链呢?

理论上只要把想设为原型的值的地址付给隐藏属性就可以了:

x.__proto__ = 自定义原型

但这不是标准推荐的写法,推荐的写法有两种:

// 1.使用create方法 (ES6)
a = Object.create(f)
// 2.用构造函数new一个 (ES5)
// 结果是: a.___proto__ = constructor.prototype
a = new constructor() 

原型链解决了什么问题?

在没有class的情况下实现了继承。以a => Array.prototype => Object.prototype为例,我们说,

  1. a是Array的实例,a拥有Array.prototype里的属性
  2. Array继承了Object
  3. a是Object的间接属性,拥有Object.prototype里的属性

这样一来,a既拥有了Array的属性,又有了Object的属性。

优点:简单优雅。

缺点:不支持私有属性。写起来别扭。

怎么解决缺点:ES6有class啦!

判断“代码中的this是什么”的技巧

this就是call的第一个参数。

所有的函数调用如f('test'),其实都是f.call(undefined, 'test')的语法糖。 而浏览器一旦发现this是undefined就会自动把this转成window(node非浏览器环境,所以情况另当别论,依版本不同可能会不转换或转换成global等),让浏览器不要修改this可以用use strict来规定。

那么对象调用函数会发生什么呢?这里我们用obj.child.f('test')举例:

对象在内存中的存储,是用引用的形式,obj的child属性存储一个地址,存放真正的child,而child的f属性又存放了一个地址,存储一个函数,这个函数就是所谓的obj.child.f()。但这个函数并不知道自己叫f(),也不知道是谁在调用它,所以,需要把对象传给这个函数,也就是f.call(obj.child, 'test')

下面就来根据具体代码分析吧!

var length = 4;  
function callback() {  
    console.log(this.length); 
}  
const obj = {  
    length: 5,  
    method(callback) {  
        callback();  
    }  
};  
obj.method(callback, 1, 2);

相当于callback.call(undefined),浏览器把undefined转化为window,而第一行的var length会自动挂在window上。

答案:4

new做了什么?

  1. 创建临时对象
  2. 把原型放在临时对象的隐藏属性里(约定俗成原型放在x.prototype里)
  3. 设置this = 临时对象
  4. 运行构造函数(把独有属性放在临时对象上)
  5. 返回临时对象

立即执行函数是什么?

立即执行函数就是声明后立即执行的函数。

比如一个函数

function (){
    console.log('test')
}()

这个语法是错误的。有很多种方式让它的语法正确

  1. 在外层加一对()
  2. 在前面加一个! / + / - / ~
  3. 在前面加一个new(返回一个空对象,太消耗内存)
  4. var x = 来生命它。

等等。

注意:在代码后面加一个分号可以防止不必要的错误。

它解决了什么问题?

它在ES6以前的标准下,创建了局部变量。而在ES6下只要在块级作用域使用let就可以创建局部变量。

优点:兼容性好。

缺点:丑。

总结:立即执行函数,比起“函数”更强调的是一个“执行过程”。

闭包是什么?

闭包是一种语法特性,任何语言都可以选择支持或不支持这个语言特性。

函数 与 自由变量 结合产生的就叫做 闭包

这里所谓的“自由变量”,是区分于“全局变量”和“局部变量”的概念。因为对于全局变量,每个函数都可以访问它是显而易见的,不会形成闭包。而对于函数的局部变量,它只存在于函数内部,也不会形成闭包。

当我们有一个变量,我们不希望全局对它有很随意的操作,而是开一个口子,只能通过指定的方法对他进行指定的操作,就可以使用闭包。

举个例子,当我们玩超级玛丽的时候,初始设定有3条命。这个值最好不要直接暴露在全局,以免不当访问和修改导致bug:

{
    let lives = 3
    window.countLives = () => { return lives; }
    window.die = () => { lives -= 1; }
}

window.die();

此时,我们只能通过闭包暴露的接口die来修改lives。

用立即执行函数来写就是:

var die = function(){
   let lives = 3
   return () => { lives -= 1; }
}()

die();

解决了什么问题:

  1. 局部变量只暴露接口,只能进行间接访问(最重要的优点)
  2. 避免污染全局环境(因为我们压根没有使用全局变量)
  3. 维持变量存在于内存中,避免被垃圾回收

缺点:

使用不当的情况下会造成内存泄漏。

解决办法:

慎用。

如何实现类