JS 中的一些概念问题

Q:描述 JavaScript 中的继承和原型链,并举例子。

JavaScript 是基于原型的面向对象语言,并无传统的基于类的继承系统。

在 JS 中,每个对象都会在内部引用一个叫做prototype的对象,而这个原型对象本身也会引用自己的原型对象,并以此类推。这样就形成了一条原型引用链,这个链的末尾是一个以 null 为原型的对象。JS 就是通过原型链的方式来实现继承的,当一个对象引用了不属于自己的属性时,将遍历原型链,直到找到引用的属性为止(或者直接找到链的末尾,这种情况说明该属性未定义)。

一个简单的例子:

function Animal() { this.eatsVeggies = true; this.eatsMeat = false; }

function Herbivore() {}
Herbivore.prototype = new Animal();

function Carnivore() { this.eatsMeat = true; }
Carnivore.prototype = new Animal();

var rabbit = new Herbivore();
var bear = new Carnivore();

console.log(rabbit.eatsMeat);   // logs "false"
console.log(bear.eatsMeat);     // logs "true"

Q:在下面的代码片段中,alert 将会显示什么?请解释你的答案。

var foo = new Object();
var bar = new Object();
var map = new Object();

map[foo] = "foo";
map[bar] = "bar";

alert(map[foo]);  // what will this display??

这里 alert 将会弹出 bar ,JS 对象本质上是键值对哈希表,其中 key 总是字符串。事实上,当字符串以为外的对象被用作 key 时,并不会发生错误,JS 会隐式的将其转换为字符串,并将该值用作 key。

所以,上面的代码中 map 对象在使用 foo 对象作为 key 时,会自动调用 foo 对象的toString()方法,那么这里就会调用其默认实现。则会得到字符串"[object Object]"。然后再看上面的代码,解释如下:

var foo = new Object();
var bar = new Object();
var map = new Object();

map[foo] = "foo";    // --> map["[Object object]"] = "foo";
map[bar] = "bar";    // --> map["[Object object]"] = "bar";
                     // NOTE: second mapping REPLACES first mapping!

alert(map[foo]);     // --> alert(map["[Object object]"]);
                     // and since map["[Object object]"] = "bar",
                     // this will alert "bar", not "foo"!!
                     //    SURPRISE! ;-)

Q:请解释 JavaScript 中的闭包。什么是闭包?它们有什么独特的特性?你如何以及为什么要使用它们?请举一个例子。

闭包是一个函数,包含在创建闭包时处于作用域内的所有变量或其他函数。在 JavaScript 中,闭包通过“内部函数”的形式来实现,也就是在另一函数的主体内定义的函数。这是一个简单的例子:

(function outerFunc(outerArg) {
  var outerVar = 3;

  (function middleFunc(middleArg) {
    var middleVar = 4;

    (function innerFunc(innerArg) {
      var innerVar = 5;
      // EXAMPLE OF SCOPE IN CLOSURE:
      // Variables from innerFunc, middleFunc, and outerFunc,
      // as well as the global namespace, are ALL in scope here.
      console.log("outerArg="+outerArg+
                  " middleArg="+middleArg+
                  " innerArg="+innerArg+"\n"+
                  " outerVar="+outerVar+
                  " middleVar="+middleVar+
                  " innerVar="+innerVar);
      // --------------- THIS WILL LOG: ---------------
      //    outerArg=123 middleArg=456 innerArg=789
      //    outerVar=3 middleVar=4 innerVar=5
    })(789);
  })(456);
})(123);

闭包的一个重要特性是,即使是在外部函数返回后,内部函数仍然可以访问外部函数的变量。这是因为,在 JavaScript 中,当函数被执行时,它们仍然使用创建函数时有效的作用域。

然而,如果内部函数在被调用时(而不是在创建时)访问外部函数变量的值,就会导致混淆。为了测试候选人对此细微差别的理解,请使用以下代码片段,它将动态创建五个按钮,并问候选人当用户单击第三个按钮时将显示什么内容:

function addButtons(numButtons) {
  for (var i = 0; i < numButtons; i++) {
    var button = document.createElement('input');
    button.type = 'button';
    button.value = 'Button ' + (i + 1);
    button.onclick = function() {
      alert('Button ' + (i + 1) + ' clicked');
    };
    document.body.appendChild(button);
    document.body.appendChild(document.createElement('br'));
  }
}

window.onload = function() { addButtons(5); };

很多人会错误地回答,当用户点击第三个按钮时,会显示“Button 3 clicked”。实际上,上面的代码包含了一个错误(基于对 closure 的误解),当用户点击五个按钮中的任何一个,都将显示“Button 6 clicked”。这是因为,在调用 onclick 方法时(对于任意一个按钮),for 循环已经完成并且变量 i 的值已经是 5。 接下来可以问候选人如何解决上述代码中的错误,以便产生预期的行为(即点击按钮 n 将显示“Button n clicked”)。如果候选人能给出正确答案,说明他们懂得如何正确使用闭包,如下所示:

function addButtons(numButtons) {
  for (var i = 0; i < numButtons; i++) {
    var button = document.createElement('input');
    button.type = 'button';
    button.value = 'Button ' + (i + 1);
    // HERE'S THE FIX:
    // Employ the Immediately-Invoked Function Expression (IIFE)
    // pattern to achieve the desired behavior:
    button.onclick = function(buttonIndex) {
      return function() {
        alert('Button ' + (buttonIndex + 1) + ' clicked');
      };
    }(i);
    document.body.appendChild(button);
    document.body.appendChild(document.createElement('br'));
  }
}

window.onload = function() { addButtons(5); };

Q:描述创建对象的不同方式及其各自的影响,并提供示例。

下图列出了在 JavaScript 中用于创建对象的各种方式,以及每种方式产生的原型链差异。

toptal-blog-image-1480346836122-f4ff7c7e4c3662c7ad17abce24d01eb9.jpg

Q:var foo = function(){},function foo(){} 之间的差异。

使用function foo(){}时,函数 foo 可以在定义之前被引用(通过 “hoisting” 技术)。hositing 技术带来的结果是,函数的最后一个定义将被使用

function foo() { return 1; }

alert(foo());   // 2 will be alert

function foo() { return 2; }

如下代码段答案是直观的,它将预期返回 “1”:

var foo = function() { return 1; }

alert(foo());   // 1 will be alert

foo = function() { return 2; }

参考

https://www.toptal.com/javascript#hiring-guide

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Golang语言社区

go语言的匿名函数

1-声明一个匿名函数 func(参数列表) 返回值列表 { 函数体… } 2-匿名函数的调用

9220
来自专栏web前端

JavaScript基础学习--07函数的传参、重用、价格计算

Demos:   https://github.com/jiangheyan/JavaScriptBase 一、函数传参      1、参数=js数据类型  ...

20680
来自专栏Python

Python的if判断与while循环

1.if判断 Python 编程中 if 语句用于控制程序的执行,基本形式为: if 判断条件: 执行语句 else: 执行语句 Python中...

25670
来自专栏Java学习123

40个你可能不知道的Python的特点和技巧

281100
来自专栏ml

C plus plus 控制格式

使用这些格式需要声明包含<iomainip> long flags( ) const 返回当前的格式标志。 long flays(long newflag) 设...

22040
来自专栏mathor

第五届蓝桥杯决赛B组C/C++——Log大侠

15560
来自专栏前端杂谈

es6之块级作用域

282110
来自专栏IT探索

shell用法(包括awk sed grep eval expr等)

1:取出文件中的第i行到第j行:awk 'NR>=i && NR<=j {print $0}' filename

10010
来自专栏章鱼的慢慢技术路

用Go语言实现一个简单的聊天机器人

Go语言基本构成要素:标识符、关键字、字面量、分隔符、操作符。它们可以组成各种表达式和语句,而后者都无需以分号结尾。

13820
来自专栏Nian糕的私人厨房

JavaScript 函数

通常来说,一个函数就是一个可以被外部代码调用(或者函数本身递归调用)的"子程序",和程序本身一样,一个函数的函数体是由一系列的语句组成的,函数可以接收传入参数,...

8930

扫码关注云+社区

领取腾讯云代金券