# 坑一:变量提升

js 在解析时,会将所有的变量与函数先声明再进行赋值操作。例如

// 先输出再声明并赋值变量
console.log(a); // 输出结果为 undefined
var a = 10;

像这类代码,在执行时等于如下效果

var a;
console.log(a);
a = 10;

** 这说明 js 在执行时,会将变量提至最前面先声明,再进行赋值操作。** 同理,我们进行函数调用时也是如此进行函数提升

fu1();  // 在未定义 fu1 () 时就调用它,此时我们执行时是可以输出 xxx 的
function  fu1(){
	console.log('xxx');
}
// 但我们使用变量保存函数时,函数无法执行
fun();  // 此时控制台报错
var fun = function (){
    console.log('asuhe');
}

# 坑二:逻辑中断

当出现 表达式1 && 表达式2 时,前面的表达式 1 返回 true 则返回表达式 2 值,若表达式 1 返回 false 则返回表达式 1 的值

var a = 5 > 3 && 10;
console.log(a); // 输出 10
var b = 1 > 3 && 10;
console.log(b); // 输出 false

当出现 表达式1 || 表达式2 时,表达式 1 返回 true 则返回表达式 1 的值,若表达式 1 返回 false 则返回表达式 2 的值

var a = 5 > 3 || 10;
console.log(a); // 输出 true
var b = 1 > 3 || 10;
console.log(b); // 输出 10

# 坑三:变量声明

在 js 中我们可以不使用 var 关键字直接对一个变量名进行赋值,而且该变量还会成为一个全局变量

function foo(){
	a = 10;
	console.log(a);
}
foo(); // 输出 10
console.log(a); // 输出 10

# 坑四:函数的形参与实参可以不匹配

js 的函数在定义时可以不书写形参,但是在调用该函数时,我们却可以传入实参。传入的实参会被一个 argument 对象保存,在函数内部可以使用该对象获取传入实参的值

function foo(){
	console.log(arguments[0]); // 输出 10
    console.log(arguments[1]); // 输出 20
    console.log(arguments[2]); // 输出 undefined
}
foo(10,20);
// 形参多于实参时,未匹配的形参值为 undefined
function foo(a,b){
	console.log(a); // 输出 10
    console.log(b); // 输出 undefined
}
foo(10);
// 形参少于实参时,多余的实参值不会赋值给形参,但仍然保存在 arguments 中
function foo(a){
	console.log(a); // 输出 10
    console.log(arguments[1]); // 输出 20
}
foo(10,20);

arugments 的内部结构

function foo(){
	console.log(arguments); // 输出 arguments 结构
}
foo(10,20);

arguments中保存的东西

# 坑五:内置 Array.sort 方法里的比较默认是按第一位大小比较的

在 js 的内置 Array.sort 方法里,大小默认是为按第一位的大小比较的

var a = [1,8,31,40,9,10,2,16];
a.sort();
for (let index = 0; index < a.length; index++) {
    console.log(a[index]);
}

数组按第一位的大小比较

若要变成正确的升序办法要传入特定的函数

var a = [1,8,31,40,9,10,2,16];
a.sort(function(a,b){ return a - b;}); //a - b 是升序,固定写法
for (let index = 0; index < a.length; index++) {
    console.log(a[index]);
}

正确的升序

降序

var a = [1,8,31,40,9,10,2,16];
a.sort(function(a,b){ return b - a;}); //b - a 是降序,固定写法
for (let index = 0; index < a.length; index++) {
    console.log(a[index]);
}

降序

# 坑六:所谓的构造函数

js 里的所谓的构造函数实际上就是一个自定义的用来创建对象的普通函数,只是因为它的功能是用来创建对象所以才叫构造函数。本质上与普通函数并没有区别,不是 C++ 或 Java 里构造函数的概念。这也就是说,js 本质上不是面向对象的,只是实现了类似面向对象的功能

function Person(name,age,sex){
    this.name = name; //this 指针写出成员赋值
    this.age = age;
    this.sex = sex;
    this.getName = function(){ return this.name;}
}
var obj = new Person('Asuhe',18,'保密'); // 创建对象时还是要使用 new 关键字
console.log(obj.getName()); // 输出 Asuhe
console.log(obj); // 输出 obj 的结构

构造函数

# 坑七:作用域链

因为变量提升导致我们在调用一个在后面定义并赋值的变量时,会先输出这个变量未赋值的状态。作用域链就是,当我们发现在当前作用域寻找不到变量声明时,就会往上一级作用域去寻找,找到为止。若在全局作用域中都未寻找到该变量声明,则报错。

function foo(){
    console.log(a);
    var a = 10; // 变量提升
}
foo();  // 此时输出 undefined
----------
function foo(){
    console.log(a);
}
foo();  // 此时依然输出 undefined
var a = 20; // 变量提升
----------
var a = 20;
function foo(){
    console.log(a);
    var a = 10; // 变量提升
}
foo(); // 依然输出 undefined , 因为在 foo 函数内部作用域中已经找到 a 的声明,所以不会继续往外查找

# 坑八:类没有变量提升

前面我们讲到对于函数有函数提升,变量有变量提升,我们可以先使用后声明不会报错。而 es6 中引入的 class 关键字就没有类似的提升机制。我们只能先把 class 的定义放在使用它之前。

var obj = new Man();  // 此行报错,不能先于 Man 类的定义使用
class Man{
	constructor(){	};
	say(){
		console.log('hello world!');
	}
}

# 坑九:类里面的 this 指向

在 es6 中引入了 class 关键字,用于实现类似面向对象的功能。但是这个 class 里的 this 指针的指向并不是总是指向实例化对象本身。在 constructor 函数中,this 指针是一直指向实例化对象本身的。而在这个 class 的方法中,this 指针指向的是调用该方法的对象,有时候调用该方法的并不是该实例化对象所以 this 指向会发生改变。

<button>点击</button> //点击该按钮输出 undefined
<script>
	class Person {
		constructor(name,word){
            this.name = name;
            this.word = word;
            this.btn = document.querySelector('button'); //绑定至btn
            this.btn.onclick = this.Say; //button 调用Say方法
        }
        getName(){
            console.log(this.name);
        }
        Say(){
            console.log(this.word);
        }
	}
	var asuhe = new Person('Asuhe','up');
	asuhe.getName(); //输出 Asuhe
	asuhe.Say(); //输出 up
</script>

# 坑十:supre 关键字在子类 constructor 中的使用

我们都知道 supre 不仅可以调用父类的 constructor ,还可以用于调用父类的普通成员函数。

子类在 constructor 中使用 super 关键字,必须放到 this 指针 前面 ,但在普通成员函数中无此规则 (必须先调用父类的构造方法,在使用子类构造方法)

class Father {
	constructor(x,y){
		this.x = x;
		this.y = y;
	}
	sum(){
		console.log(this.x + this.y);
	}
}
class Son extends Father {
	constructor(x,y){
		this.x = x;
		this.y = y;
		super(x,y); // 此行报错,super 在 constructor 中必须先于 this。super.sum (); 同样报错
	}
	subtract(){
		console.log(this.x - this.y);
	}
}
var obj = new Son(5,3);
更新于