[关闭]
@Shel 2017-04-25T08:20:17.000000Z 字数 3634 阅读 923

面试笔记

2017/4/18


JS

1. strict模式

JavaScript在设计之初,为了方便初学者学习,并不强制要求用var申明变量。这个设计错误带来了严重的后果:如果一个变量没有通过var申明就被使用,那么该变量就自动被申明为全局变量。使用var申明的变量则不是全局变量,它的范围被限制在该变量被申明的函数体内,同名变量在不同的函数体内互不冲突。为了修补JavaScript这一严重设计缺陷,ECMA在后续规范中推出了strict模式,在strict模式下运行的JavaScript代码,强制通过var申明变量,未使用var申明变量就使用的,将导致运行错误。

2. scope作用域

在JavaScript中,用var申明的变量实际上是有作用域的。如果一个变量在函数体内部申明,则该变量的作用域为整个函数体,在函数体外不可引用该变量。
如果两个不同的函数各自申明了同一个变量,那么该变量只在各自的函数体内起作用。换句话说,不同函数内部的同名变量互相独立,互不影响。
由于JavaScript的函数可以嵌套,此时,内部函数可以访问外部函数定义的变量,反过来则不行。如果内部函数和外部函数的变量名重名怎么办?JavaScript的函数在查找变量时从自身函数定义开始,从“内”向“外”查找。如果内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量。
由于JavaScript的变量作用域实际上是函数内部,我们在for循环等语句块中是无法定义具有局部作用域的变量的,为了解决块级作用域,ES6引入了新的关键字let,用let替代var可以申明一个块级作用域的变量。由于var和let申明的是变量,如果要申明一个常量,在ES6之前是不行的,我们通常用全部大写的变量来表示“这是一个常量,不要修改它的值”,ES6标准引入了新的关键字const来定义常量,const与let都具有块级作用域。

3. 变量提升(Hoisting)

JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部。
  1. 'use strict';
  2. function foo() {
  3. var x = 'Hello, ' + y;
  4. alert(x);
  5. var y = 'Bob';
  6. }
  7. foo();
虽然是strict模式,但语句`var x = 'Hello,' + y;`并不报错,原因是变量y在稍后申明了。但是alert显示Hello, undefined,说明变量y的值为undefined。这正是因为JavaScript引擎自动提升了变量y的声明,但不会提升变量y的赋值。由于JavaScript的这一怪异的“特性”,我们在函数内部定义变量时,请严格遵守“在函数内部首先申明所有变量”这一规则。最常见的做法是用一个var申明函数内部用到的所有变量。

4. 命名空间

全局变量会绑定到window上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。

5. 对象与原型prototype

除了直接用{ ... }创建一个对象外,JavaScript还可以用一种构造函数的方法来创建对象。它的用法是,先定义一个构造函数,用关键字new来调用这个函数,并返回一个对象。注意,如果不写new,这就是一个普通函数,它返回undefined。但是,如果写了new,它就变成了一个构造函数,它绑定的this指向新创建的对象,并默认返回this,也就是说,不需要在最后写return this;。
用new关键字创建的对象从原型上获得了一个constructor属性,它指向构造函数本身,但没有prototype这个属性,不过可以用__proto__这个非标准用法来查看。而构造函数具有一个prototype属性指向原型,因此将所有共享的方法定义到这个原型上可以极大的节省空间。
如果一个函数被定义为用于创建对象的构造函数,但是调用时忘记了写new怎么办?在strict模式下,this.name = name将报错,因为this绑定为undefined,在非strict模式下,this.name = name不报错,因为this绑定为window,于是无意间创建了全局变量name,并且返回undefined,这个结果更糟糕。所以,调用构造函数千万不要忘记写new。为了区分普通函数和构造函数,按照约定,构造函数首字母应当大写,而普通函数首字母应当小写,这样,一些语法检查工具如jslint将可以帮你检测到漏写的new。
可以将所有的new操作封装在一个函数当中,有几个巨大的优点:一是不需要new来调用,二是参数非常灵活,可以不传,也可以传。
  1. function Student(props) {
  2. this.name = props.name || '匿名'; // 默认值为'匿名'
  3. this.grade = props.grade || 1; // 默认值为1
  4. }
  5. Student.prototype.hello = function () {
  6. alert('Hello, ' + this.name + '!');
  7. };
  8. function createStudent(props) {
  9. return new Student(props || {})
  10. }

6. 对象与继承

7. this

JavaScript中关键字this所引用的是函数上下文,取决于函数是如何调用的,而不是怎么被定义的。
call()和apply()方法强制转换上下文环境。

8. 事件循环

9. 事件代理

10. 数组

以下操作不会改变原来数组

以下操作会改变原数组

11. 字符串

最新的ES6标准新增了一种多行字符串的表示方法,用反引号 ` ... ` 表示;
ES6新增了一种模板字符串,它会自动替换字符串中的变量${ var }.

JavaScript为字符串提供了一些常用方法,注意,调用这些方法本身不会改变原有字符串的内容,而是返回一个新字符串。

12. 对象

13. 条件判断

JavaScript把null、undefined、0、NaN和空字符串''视为false,其他值一概视为true,因此上述代码条件判断的结果是true。

14. 循环

for循环的一个变体是for ... in循环,它可以把一个对象的所有属性依次循环出来。由于Array也是对象,而它的每个元素的索引被视为对象的属性,因此,for ... in循环可以直接循环出Array的索引。请注意,for ... in对Array的循环得到的是String而不是Number。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注