Jacleklm's Blog

JS基础知识点

2019/10/12

数据类型

数据类型

ES 中有 6 种基本数据类型:undefined(未声明),null(空对象指针,可以说一个变量想用来保存对象,未保存之前最好使其等于 null),boolean,number,string,symbol(fromES6));一种复杂数据类型:Object
引用类型有:Object,Array,Function,Date,RegExp

基本类型与引用类型区别

引用类型(对象类型)和基本类型(原始类型)不同的是,基本类型存储的是值,引用类型存储的是地址(指针)。
当创建了一个引用类型的时候,计算机会在内存中帮我们开辟一个空间来存放值,但是我们需要找到这个空间,这个空间会拥有一个地址(指针)。

typeof vs instanceof

typeof 对于基本类型来说,除 null 会显示成 object(null 不是 object,这是 JS 一个古老的 bug),其余都可以显示正确的类型;
typeof 对于引用类型来说,除了函数(会显示 function)都会显示 object,所以说 typeof 并不能准确判断变量到底是什么类型。

1
2
3
typeof []; // 'object'
typeof {}; // 'object'
typeof console.log; // 'function'

如果我们想判断一个引用类型的正确类型,这时候可以考虑使用 instanceof,因为内部机制是通过原型链来判断的;但是 instanceof 不能用来判断基本类型

1
2
3
4
5
6
7
8
9
const Person = function() {};
const p1 = new Person();
p1 instanceof Person; // true

var str = "hello world";
str instanceof String; // false

var str1 = new String("hello world");
str1 instanceof String; // true
如何判断一个引用类型是数组
  • 根据构造函数来判断 xxx instanceof Array
  • 直接用 Array.isArray() 判断
  • 根据 class 属性判断 Object.prototype.toString.call(obj) === '[object Array]'

类型转换

在 JS 中类型转换只有三种情况,分别是:转换为布尔值;数字;字符串

转 Boolean

用 Boolean()进行转换。在条件判断时,除了 undefined, null, false, NaN, ‘’, 0, -0,其他所有值都转为 true,包括所有对象。
eg. if([]){console.log(3)}是能输出3

对象转原始类型

对象在转换类型的时候,会调用内置的 [[ToPrimitive]] 函数,对于该函数来说,算法逻辑一般来说如下:

  • 如果已经是原始类型了,那就不需要转换了
  • 如果需要转字符串类型就调用 x.toString(),转换为基础类型的话就返回转换的值。不是字符串类型的话就先调用 valueOf,结果不是基础类型的话再调用 toString
  • 调用 x.valueOf(),如果转换为基础类型,就返回转换的值
  • 如果都没有返回原始类型,就会报错

当然可以重写 Symbol.toPrimitive ,该方法在转原始类型时调用优先级最高。

1
2
3
4
5
6
7
8
9
10
11
12
let a = {
valueOf() {
return 0;
},
toString() {
return "1";
},
[Symbol.toPrimitive]() {
return 2;
}
};
1 + a; // => 3
四则运算符

加法运算符不同于其他几个运算符,它有以下几个特点:

  • 运算中其中一方为字符串,那么就会把另一方也转换为字符串
  • 如果一方不是字符串或者数字,那么会将它转换为数字或者字符串
1
2
3
4
5
6
7
1 + "1"; // '11'
true + true; // 2
4 +
[1, 2, 3] + // "41,2,3"
new Array(017) + // NaN
null + // 0
undefined; // NaN

如果你对于答案有疑问的话,请看解析:

  • 对于第一行代码来说,触发特点一,所以将数字 1 转换为字符串,得到结果 ‘11’
  • 对于第二行代码来说,触发特点二,所以将 true 转为数字 1
  • 对于第三行代码来说,触发特点二,所以将数组通过 toString 转为字符串 1,2,3,得到结果 41,2,3
  • 对于第四行。new Array(017),里面 017 是八进制,相当于 15,所以是创建了长度为 15 但内容都是空的数组。单独的 + 会使其转 Number对数组而言,转 Number 时候数组长度大于等于 2 时都为 NaN。数组长度为 0 时是 0,为 1 的时候对数组第一项用 Number()。

另外对于加法还需要注意这个表达式 ‘a’ + + ‘b’

1
"a" + +"b"; // -> "aNaN"

因为 + ‘b’ 等于 NaN,所以结果为 “aNaN”。
对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字

1
2
3
4 * "3"; // 12
4 * []; // 0
4 * [1, 2]; // NaN

运算符前置和后置的区别

  • 如果该运算符作为后置操作符,则返回它递减之前的值。
  • 如果该运算符作为前置操作符,则返回它递减之后的值。

后置:

1
2
3
4
var i = 5;
var a = i--;
console.log(i); //输出4
console.log(a); //输出5

前置:

1
2
3
4
var j = 5;
var b = --j;
console.log(j); //输出4
console.log(b); //输出4
比较运算符

注意:+ 的优先级高于 三目运算符 ? : ; + 的优先级高于比较运算符 >

1、如果是对象,就通过 toPrimitive 转换对象
2、如果是字符串,就通过 unicode 字符索引来比较

1
2
3
4
5
6
7
8
9
let a = {
valueOf() {
return 0;
},
toString() {
return "1";
}
};
a > -1; // true

在以上代码中,因为 a 是对象,所以会通过 valueOf 转换为原始类型再比较值。

==比较
  • Boolean,number,string 三类比较的时候把值转换成数字,在看转换结果是否相等。证明:(’1’==true) 是真 (’abc’==true)是假。
  • undefined 参与比较,换成了 NaN,所以其他三个类型跟它比较都是 false,跟 null 类型比较的时候是 true。(NaN==NaN)是假
  • null 参与比较,被当成对象,因为 null 没有 valueof 和 toString,除了 undefined 谁跟他比较都是 false。
  • 值类型与对象比较:先调用对象 valueof 如果仍返回对象,调用 tostring,如果还是没有就不等。**{}转数字是 NaN,[]转数字是 0**
==与===

对于 == 来说,如果对比双方的类型不一样的话,就会进行类型转换。
假如我们需要对比 x 和 y 是否相同,就会进行如下判断流程:

  1. 首先会判断两者类型是否相同。相同的话就是比大小了
  2. 类型不相同的话,那么就会进行类型转换
  3. 会先判断是否在对比 null 和 undefined,是的话就会返回 true
  4. 判断两者类型是否是 string 和 number,是的话就会将字符串转换为 number
1
2
3
1 == '1'

1 == 1
  1. 判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断
1
2
3
4
5
'1' == true

'1' == 1

1 == 1
  1. 判断其中一方是否 object 且另一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断
1
2
3
'1' == { name: 'xxx' }

'1' == '[object Object]'

现在来想想[]==![]输出什么?
首先先执行的是![],它会得到 false。
然后[]==false,返回 true。
那么[]==[],{}=={}又输出什么呢?
类型一致,它们是引用类型,地址是不一样的,所以为 false!
对于 === 来说就简单多了,就是判断两者类型和值是否相同。

各种运算符和操作符

逻辑运算符 (或运算,左假看右;与运算,左真看右)
  • 只要 || 前面为 false,不管 || 后面是 true 还是 false,都返回 || 后面的值。
  • 只要 || 前面为 true,不管 || 后面是 true 还是 false,都返回 || 前面的值。
  • 只要 && 前面是 false,无论 && 后面是 true 还是 false,结果都将返 && 前面的值;
  • 只要 && 前面是 true,无论 && 后面是 true 还是 false,结果都将返 && 后面的值;
位操作符

内存中是用 32 位的二进制来表示数值,没用到的位用 0 填充。负数是用二进制补码表示(绝对值的二进制,再反码,再+1)

  • 按位非。用 ~ 表示,返回数值的反码
  • 按位与 & 。将两个数值每一位对齐,对相同位置上的两个数进行对比。只有两个都是 1 时才返回 1,否则都是 0。
  • 按位或 | 。同理,但是是只有两个都是 0 是才返回 0。
  • 按位异或 ^ 。 同理,只有一个是 1 时候才返回 1。
  • 左移 << 。 二进制中左移几位
  • 有符号的右移 >> 。 同理 保留符号位
  • 无符号的右移 >>> 。 以 0 来填充右移产生的空位(新的符号位),所以正数无影响,负数变化很大

This

更详细的解释
严格模式下禁止 this 关键字指向全局对象

默认绑定
  • 全局环境,默认绑定到 window
  • 函数独立调用的时候,this 默认绑定到 window
  • 被嵌套的函数独立调用时,this 默认绑定到 window
1
2
3
4
5
6
7
8
9
10
11
12
//虽然test()函数被嵌套在obj.foo()函数中,但test()函数是独立调用,而不是方法调用。所以this默认绑定到window
var a = 0;
var obj = {
a: 2,
foo: function() {
function test() {
console.log(this.a);
}
test();
}
};
obj.foo(); //0
  • 立即执行的函数 this 是 window
1
2
3
4
5
6
7
8
9
10
11
12
var a = 0;
function foo() {
(function test() {
console.log(this.a);
})();
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); //0
// 这串代码本质上和上个例子的代码是一样的
  • 闭包的 this 默认绑定到 window
1
2
3
4
5
6
7
8
9
10
11
12
var a = 0;
function foo() {
function test() {
console.log(this.a);
}
return test;
}
var obj = {
a: 2,
foo: foo
};
obj.foo()(); //0
隐式绑定

被直接对象所包含的函数调用时,也称为方法调用,this 隐式绑定到该直接对象

显示绑定

通过 call()、apply()、bind()方法把对象绑定到 this 上,叫做显式绑定。对于被调用的函数来说,叫做间接调用

new 绑定

如果函数或者方法调用之前带有关键字 new,它就构成构造函数调用。对于 this 绑定来说,称为 new 绑定

优先级

首先,new 的方式优先级最高,接下来是 bind 这些函数,然后是 obj.foo() 这种调用方式,最后是 foo 这种调用方式,同时,箭头函数的 this 一旦被绑定,就不会再被任何方式所改变
实例与解析
先来看几个函数调用的场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function foo() {
console.log(this.a)
}
var a = 1
foo();

const obj = {
a: 2,
foo: foo
foo2: foo2() {
foo
}
}
obj.foo()
obj.foo2()

const c = new foo()

接下来一个个分析上面几个场景

  • 对于直接调用 foo 来说,不管 foo 函数被放在了什么地方,this 一定是 window
  • 对于 obj.foo() 来说,我们只需要记住,谁调用了函数,谁就是 this,所以在这个场景下 foo 函数中的 this 就是 obj 对象。但是如果是 foo2 的情况,this 是指向 window 的
  • 对于 new 的方式来说,this 被永远绑定在了 c 上面,不会被任何方式改变 this (在构造函数中 this 指向函数名)

说完了以上几种情况,其实很多代码中的 this 应该就没什么问题了。
下面让我们看看箭头函数中的 this

1
2
3
4
5
6
7
8
function a() {
return () => {
return () => {
console.log(this);
};
};
}
console.log(a()()());

首先箭头函数其实是没有 this 的,箭头函数中的 this 只取决包裹箭头函数的第一个普通函数的 this。在这个例子中,因为包裹箭头函数的第一个普通函数是 a,所以此时的 this 是 window。另外对箭头函数使用 bind 这类函数是无效的
最后种情况也就是 bind 这些改变上下文的 API 了,对于这些函数来说,this 取决于第一个参数,如果第一个参数为空,那么就是 window。
那么说到 bind,如果对一个函数进行多次 bind,那么上下文会是什么呢?

1
2
3
4
5
let a = {};
let fn = function() {
console.log(this);
};
fn.bind().bind(a)(); // => ?

如果认为输出结果是 a,那么就错了。
其实可以把上述代码转换成另一种形式:

1
2
3
4
5
6
7
// fn.bind().bind(a) 等价于
let fn2 = function fn1() {
return function() {
return fn.apply();
}.apply(a);
};
fn2();

可以从上述代码中发现,不管我们给函数 bind 几次,fn 中的 this 永远由第一次 bind 决定,所以结果永远是 window。这个特性可以用来创建绑定了固定 this 的函数(见 MDN)

闭包与作用域链

更详细的解析见深入理解 javascript 原型和闭包(完结)

执行环境((执行)上下文环境)

execution context。红宝书的说法:定义了变量或函数有权访问其他数据,决定了它们各自的行为。
全局执行环境是 window 对象,即全局变量和函数都是作为 window 对象的属性和方法创建的,每个函数都有自己的执行环境(理解为函数的执行环境就是函数的归属者?)
From 博客的理解:

  • 通俗定义:在执行代码之前,把将要用到的所有的变量都事先拿出来,有的直接赋值了,有的先用 undefined 占个空,即是一种代码执行前的准备工作。eg1. 在全局代码段中,“准备工作”包括:知道有函数声明并赋值;知道有 this 并赋值;知道有变量,赋值为 undefined。eg2. 在函数体内的代码段中,“准备工作”包括了全部工作的那部分之外,还有知道有参数和 argument并赋值,确定了函数体内部自由变量的作用域。**(预编译)**
  • 执行全局代码时,会产生一个执行上下文环境,每次调用函数都又会产生执行上下文环境。这么多上下文环境该如何管理,以及如何销毁而释放内存呢?答案是执行环境栈。即当函数调用完成时,这个上下文环境以及其中的数据都会被消除,再重新回到全局上下文环境,处于活动状态的执行上下文环境只有一个。所以其实是一个进栈和出栈的过程,活动状态的执行环境就是栈尾的环境。
作用域

scope。一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。

  • JS 没有块级作用域。“块”就是大括号“{}”中间的语句。其实就是 if 和 for 的{}中声明的变量在其{}之外的地方也能访问
  • 除了全局作用域之外,只有函数可以创建作用域。所以某函数的{}中就可以看做是该函数的作用域。函数有嵌套关系的时候,他们的作用域就有上下级关系。作用域在函数定义时就已经确定了。而不是在函数调用时确定。
  • 作用域、执行环境、执行环境栈的关系,**详情见这里**。总结为:作用域只是一个“地盘”,一个抽象的概念,其中没有变量。要通过作用域对应的执行上下文环境来获取变量的值。同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值。所以,作用域中变量的值是在执行过程中产生的确定的,而作用域却是在函数创建时就确定了。所以,如果要查找一个作用域下某个变量的值,就需要找到这个作用域对应的执行上下文环境,再在其中寻找变量的值。
变量对象

每个执行环境都有一个与之相关的变量对象,执行环境中定义的所有变量和函数都会保存在这个对象中

作用域链
  • 代码执行过程中会创建变量对象的一个作用域链,作用域链的前端是当前执行环境的变量对象(某函数的作用域),末端是全局执行环境的变量对象(全局作用域)
  • 作用域链的作用是,保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到 window 对象即被终止,作用域链向下访问不行
  • 自由变量:在 A 作用域中使用的变量 x,却没有在 A 作用域中声明(即在其他作用域中声明的),对于 A 作用域来说,x 就是一个自由变量。自由变量跨作用域取值时,要去创建这个函数的作用域取值(去创建这个函数的作用域沿作用域链找),而不是“父作用域”(eg. 闭包(函数作为参数例子)有时会出现这种情况)
  • 变量查找过程:先在自己的作用域中找,如果找不到就沿着作用域链往上找。
  • 延长作用域链:执行环境类型一般只有全局和局部函数两种 try-catch 语句的 catch 块和 with 语句会在作用域链的前端增加一个变量对象(with 现在已经少用了)
闭包
  • 定义:闭包就是能够读取其他函数内部变量的函数,其实就是利用了作用域链向上查找的特点。创建闭包的方式:函数作为返回值;函数作为参数
  • 作用:读取函数内部变量,让这些变量的值一直保持在内存中。但是会增加内容开销,记得及时销毁。
  • 核心内容:函数调用完成之后,其执行上下文环境不一定会接着被销毁,这种不被销毁的情况就是闭包(函数作为返回值的情况下)。eg. fn1 中 return fn2; 外部有 var test = fn1(); 这里 fn1 执行之后其执行环境不会被销毁,因为 test 被赋值成函数,函数的特别之处在于可以创建一个独立的作用域。这里只有再执行一个 test(),那 fn1 和 fn2 的执行环境才会被销毁,否则会一直存在,所以说闭包会增加内容开销。

面向对象

JS 的对象有两种属性值:数据属性 访问器属性

数据属性

包含一个数据值的位置(就是我们常用的属性类型)

数据属性有四个特性

Configurable 表示能否通过 delete 删除属性从而重新定义属性,默认为 true
Enumerable 表示能否通过 for-in 循环返回属性,默认为 true
Writable 表示能否修改属性的值,默认为 true
Value 包含这个属性的数据值 默认为 undefined

1
2
3
4
//使用字面量创建对象,前三个属性默认值为true,value为指定的值
var person = {
name: "xxxx"
};

修改属性默认特性用 Object.defineProperty(obj,attr,{})

1
2
3
4
5
6
7
8
9
10
var person={}
Object.defineProperty(person,'name',{
wirtable:false,
value:'xxxx',
configurable:false;//一旦被设置为false,不可以更改
})

person.name='xx'
console.log(person.name);//xxxx ,严格模式下会报错
delete person.name//无效 严格模式下会报错
访问器属性

访问器属性不包含数据值。但是包含 getter 和 setter 函数,定义读取和写入访问器属性的时候调用的函数(类似 Vue,或者 Vue 说就是借助访问器属性)

四个特性

Configuable 表示能否通过 delete 删除属性从而重新定义属性,默认是 true
enumerable 表示能否通过 for-in 循环返回属性,默认是 true
get 读取属性时调用函数,默认 undefined
ste 写入属性时调用函数,默认 undefined

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var person = {
_age: 20, //下划线写法表示只能通过对象方法访问的属性
state: "young"
};
Object.defineProperty(person, "age", {
get: function() {
return this._age;
},
set: function(newVal) {
if (newVal > 50) {
this._age = newVal;
this.stae = "old";
} else {
this._age = newVal;
}
}
});

定义多个属性(数据属性和访问器属性都可以用),使用 Object.defineProperties();

读取属性的特性

使用 Object.getOwnPropertyDescriptor()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var person={}

Object.defineProperties(person,{
_age:{
values:20;
},
state:{
value:'young';
},
age:{
get:function(){
return this._age;
},
set:function(newVal){
if(newVal>50){
this._age=newVal;
this.stae='old';
}else{
this._age=newVal;
}
}
}
})

var descriptor=Object.getOwnPropertyDescriptor(person,'_age');
descriptor.value//20
descriptor.configurable//false

var descriptor=Object.getOwnPropertyDescriptor(person,'age');
descriptor.value//undefined
descriptor.configurable//false
typeof descriptor //function
函数与对象的关系

js 中的内部对象包括 Array、Boolean、Date、Function、Global、Math、Number、Object、RegExp、String 以及各种错误类对象,包括 Error、EvalError、RangeError、ReferenceError、SyntaxError 和 TypeError。
其中 Global 和 Math 这两个对象又被称为“内置对象”,这两个对象在脚本程序初始化时被创建,不必实例化这两个对象。
一切(引用类型)都是对象,对象是属性的集合
eg. 构造函数可以用 this.的方式说明函数也是可以用 . 的方式来写属性的,其实是因为函数就是对象的一种。同理数组也是可以的,不过写属性方式可能不同,因为数组就像是对象的一个子集一样。但是函数和对象之间的关系比较复杂:

  • 对象都是通过函数创建的。(就算是字面量创建对象,其实也是构造函数法的一种语法糖(简单写法))
1
2
3
4
5
//var obj = { a: 10, b: 20 };
var obj = new Object();
obj.a = 10;
obj.b = 20;
console.log(typeof Object); // function
  • 每个对象都有一个__proto__属性,指向创建该对象的函数的 prototype(即 Object.prototype)。但是 Object.prototype 是一个特例,它的__proto__指向的是 null。同理每个函数的__proto__追溯到祖先都是 Function.prototype。总结关系如下:

继承及原型链

原型链

在 js 中,每个函数都有 prototype 属性,这个属性值是一个对象(同时这个对象带有 constructor 属性,指向函数本身)
通过 new 可以创建一个函数的实例,每个实例都有proto属性,指向构造函数的 prototype 对象。
当我们在一个对象上(或方法)找某个属性的时候,会先查找实例上是否存在这个属性,如果没有的话,沿着原型链向上查找

怎么判断一个属性是对象上的属性还是其原型对象上的属性

使用 hasOwnProperty()返回 true,说明是这个对象上的;如果返回 false,但是属性 in 这个对象返回了 true,说明是原型对象上的属性。如果都是 false,那么不存在这个属性
创建对象采用构造函数和原型模式组合的方式。构造函数用来定义实例属性,原型模式用来定义方法和共享属性

继承的原理

还是跟原型链有关。每个函数都有个原型对象,这个对象用来存储通过这个函数所创建的所有实例的共有属性和方法。在读取某个对象属性的时候,从实例开始,如果实例有就返回,如果没有就找构造函数的原型对象,找到了就返回。通过实例只能访问原型对象里的值,但是不能修改。这就实现了继承

组合继承 (原型链 + 用 call 借用构造函数)

组合继承是最常用的继承方式。
单纯用原型链继承的问题是:包含引用类型的值的属性会被所有实例共享,某个实例改了这个属性则所有实例都会改。eg. this.food = ['apple', 'banana']
单纯用借用构造函数继承的

  • 优点:子类型构造函数可以传参:不会与父类引用类型的值的属性共享
  • 缺点:在父类中定义的方法在子类中并不可见(因为这样的话方法就是引用类型属性)eg. this.getName = function() { xxx }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Parent(age) {
this.age = age;
}
Parent.prototype.getAge = function() {
console.log(this.age);
};
function Child(age) {
Parent.call(this, age);
}
Child.prototype = new Parent();

const child = new Child(5);

child.getAge(); // 5
console.log(child instanceof Parent); // true

以上继承的方式核心是在子类的构造函数中通过 Parent.call(this) 继承父类的属性,然后改变子类的原型为 new Parent()来继承父类的函数。
这种继承方式
优点在于子类型构造函数可以传参,不会与父类引用属性共享
,可以复用父类的函数,但是也存在一个缺点就是在继承父类函数的时候调用了父类构造函数,导致子类的原型上多了不需要的父类属性,存在内存上的浪费

寄生组合继承

这种继承方式对组合继承进行了优化,组合继承缺点在于继承父类函数时调用了构造函数,我们只需要优化掉这点就行了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Parent(value) {
this.val = value;
}
Parent.prototype.getValue = function() {
console.log(this.val);
};

function Child(value) {
Parent.call(this, value);
}
Child.prototype = Object.create(Parent.prototype, {
constructor: {
value: Child,
enumerable: false,
writable: true,
configurable: true
}
});

const child = new Child(1);

child.getValue(); // 1
child instanceof Parent; // true

以上继承实现的核心就是将父类的原型赋值给了子类,并且将构造函数设置为子类,这样既解决了无用的父类属性问题,还能正确的找到子类的构造函数。

ES6 的 Class 继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Parent {
constructor(value) {
this.val = value;
}
getValue() {
console.log(this.val);
return this.val;
}
}
class Child extends Parent {
constructor(value) {
super(value);
}
toString() {
return this.value + " " + super.getValue(); // 调用父类的getValue()
}
}
let child = new Child(1);
child.getValue(); // 1
child instanceof Parent; // true

super 关键字指向当前对象的原型对象
class 实现继承的核心在于使用 extends 表明继承自哪个父类,并且在子类构造函数中必须调用 super,因为这段代码可以看成 Parent.call(this, value)。

Class 中的箭头函数和普通函数有何区别

箭头函数相当于绑定上了 this,类似构造函数内的一个值。
箭头函数会绑定在实例对象上,普通函数最后会绑定在原型上。

DOM

DOM 是针对 HTML 和 XML 文档的一个 API。将文档看作一个层次化的节点树,可以用 JS 来操作这个节点树。

节点层次

文档节点(Document)是每个文档的根节点,它有且仅有一个子节点:html 元素(节点与元素的关系类似于键与值的关系)。

DOM 如何创建元素

创建:createElement(‘div’);
添加:appendChild(element) ; insertBefore(insertdom.chosendom)

DOM 获取元素的方式
  • 通过元素类型获取
    • document.getElementById();//id 名,在实际开发中较少使用,选择器中多用 class id 一般只用在顶级层存在 不能太过依赖 id
    • document.getElementsByTagName();//标签名
    • document.getElementsByClassName();//类名
    • document.getElementsByName();//DOM 般不用
    • document.querySelector();//css 选择符模式,返回与该模式匹配的第一个元素,结果为一个元素;如果没找到匹配的元素,则返回 null
    • document.querySelectorAll()//css 选择符模式,返回与该模式匹配的所有元素,结果为一个类数组
  • 根据关系树来选择
    • parentNode//获取所选节点的父节点,最顶层的节点为#document
    • childNodes //获取所选节点的子节点们
    • firstChild //获取所选节点的第一个子节点
    • lastChild //获取所选节点的最后一个子节点
    • nextSibling //获取所选节点的后一个兄弟节点 列表中最后一个节点的 nextSibling 属性值为 null
    • previousSibling //获取所选节点的前一兄弟节点 列表中第一个节点的 previousSibling 属性值为 null
  • 根据元素节点树来选择
    • parentElement  //返回当前元素的父元素节点(IE9 以下不兼容)
    • children // 返回当前元素的元素子节点
    • firstElementChild //返回的是第一个元素子节点(IE9 以下不兼容)
    • lastElementChild //返回的是最后一个元素子节点(IE9 以下不兼容)
    • nextElementSibling //返回的是后一个兄弟元素节点(IE9 以下不兼容)
    • previousElementSibling //返回的是前一个兄弟元素节点(IE9 以下不兼容)
获取 DOM 节点 get 系列和 query 系列哪种性能好?

1.从性能上说 get 系列的性能都比 query 系列好,get 系列里面各有差异,这些差异可以结合算法如何遍历搜索去理解,都解释得通。
2.getElementsByTagName 比 querySelectorAll 快的原因在于:getElementsByTagName 创建的过程不需要做任何操作,只需要返回一个指针即可。而 querySelectorAll 会循环遍历所有的的结果,然后创建一个新的 NodeList。 3.实际在用的过程中取决于要获取的是什么,再进行选择。

节点属性中 children 和 childNodes 有什么区别?

childNodes 返回的是节点的子节点集合(NodeLists),包括元素节点、文本节点还有属性节点。
children 返回的只是节点的元素节点集合(HTMLCollection)

HTMLCollection 和 NodeList 的比较
  • 共同点
    • 都是类数组对象,都有 length 属性
    • 都有共同的方法:item,可以通过 item(index)获取返回结果的元素
    • 都是实时变动的,document 上面的更改会反映到相关的对象上
    • 注:querySeletorAll 返回的 NodeList 是个浅拷贝的类数组对象,在节点数目上是非实时的,不过对节点属性进行修改,还是实时反映的
  • 区别
    • NodeList 可以包含任何节点类型,HTMLCollection 只包含元素节点。elementNode 就是 HTML 中的标签
    • HTMLCollection 比 NodeList 多一个方法:nameitem(),除了可以用 id,还可以用 name 来获取节点信息

BOM

浏览器对象模型(BOM)以 window 对象为依托,表示浏览器窗口以及页面的可见区域。

  • window 对象:表示浏览器的一个实例。它既是 JS 访问浏览器窗口的一个接口,也是 ES 规定的 Global 对象
  • location 对象:提供了当前窗口中加载的文档有关的信息。它既是 window 对象的属性,也是 document 对象的属性。对象包含有关当前 URL 的信息
  • navigator 对象(提供与浏览器有关的信息)、screen 对象、history 对象包含用户(在浏览器窗口中)访问过的 URL

事件

事件流

事件流描述的是从页面中接收事件的顺序。包括三个阶段:事件捕获阶段(从 window 往事件触发处传播(从外到内),遇到注册的捕获事件会触发),处于目标阶段(传播到事件触发处时触发注册的事件),事件冒泡阶段(从事件触发处往 window 传播,遇到注册的冒泡事件会触发)。

如何给元素注册事件
  • 在 HTML 元素中绑定事件 onclick=function()
  • 获取 dom,dom.onclick
  • addEventListener(click,function,true/false)。第三个参数默认为 false,表示在冒泡阶段调用事件处理程序;true 则是捕获阶段
  • 如果一个节点中的子节点是动态生成的,那么子节点需要注册事件的话应该注册在父节点上(因为有冒泡)。
如何阻止事件冒泡?如何取消默认事件?如何阻止事件的默认行为?
  • 阻止事件冒泡:
    W3C: stopPropagation();
    IE: e.cancelBubble=true;
    写法 :
    window.event ? window.event.cancelBubble=true:e.stop(Propagation)
  • 取消默认事件
    W3C:preventDefault()
    IE: e.returnValue:false;
  • 阻止默认行为:
    return false
    原生的 js 会阻止默认行为,但会继续冒泡;
    jquery 会阻止默认行为,并停止冒泡。

严格模式

在JS文件顶部写'use strict'即可

  1. 未声明变量赋值报错
  2. 指向window的this变为undifined;用 apply 、call、bind 改变 this 的值,this指向null的话就是null而不是window
  3. 函数名不能相同;在函数中修改了参数不能反映到arument;不能用caller和callee
    1
    2
    3
    4
    5
    6
    "use strict"
    function test(n) {
    n = 1
    console.log(arguments[0])
    }
    test(100) // 100
  4. 删除了with();0 开头的八进制字面量无效
  5. 保护保留关键字(static之类)

那些会让同事打你的简便写法

~~

对于 ~~ 运算符,它其实表示为:

1
2
var testData= 2.1; 
var testResult=(typeof testData==="number"&&!isNaN(testData)&&testData!==Infinity)?(testData>0)?Math.floor(testData):Math.ceil(testData):0;

参考资料
呓语
poetries
深入理解 javascript 原型和闭包(完结)
《Javascript 高级程序设计》
ECMAScript 6 入门

CATALOG
  1. 1. 数据类型
    1. 1.0.1. 数据类型
    2. 1.0.2. 基本类型与引用类型区别
    3. 1.0.3. typeof vs instanceof
    4. 1.0.4. 如何判断一个引用类型是数组
  • 2. 类型转换
    1. 2.0.1. 转 Boolean
    2. 2.0.2. 对象转原始类型
    3. 2.0.3. 四则运算符
    4. 2.0.4. 比较运算符
    5. 2.0.5. ==比较
    6. 2.0.6. ==与===
  • 3. 各种运算符和操作符
    1. 3.0.1. 逻辑运算符 (或运算,左假看右;与运算,左真看右)
    2. 3.0.2. 位操作符
  • 4. This
    1. 4.0.1. 默认绑定
    2. 4.0.2. 隐式绑定
    3. 4.0.3. 显示绑定
    4. 4.0.4. new 绑定
    5. 4.0.5. 优先级
  • 5. 闭包与作用域链
    1. 5.0.1. 执行环境((执行)上下文环境)
    2. 5.0.2. 作用域
    3. 5.0.3. 变量对象
    4. 5.0.4. 作用域链
    5. 5.0.5. 闭包
  • 6. 面向对象
    1. 6.0.1. 数据属性
      1. 6.0.1.1. 数据属性有四个特性
    2. 6.0.2. 访问器属性
      1. 6.0.2.1. 四个特性
    3. 6.0.3. 读取属性的特性
    4. 6.0.4. 函数与对象的关系
  • 7. 继承及原型链
    1. 7.0.1. 原型链
      1. 7.0.1.1. 怎么判断一个属性是对象上的属性还是其原型对象上的属性
    2. 7.0.2. 继承的原理
    3. 7.0.3. 组合继承 (原型链 + 用 call 借用构造函数)
    4. 7.0.4. 寄生组合继承
    5. 7.0.5. ES6 的 Class 继承
    6. 7.0.6. Class 中的箭头函数和普通函数有何区别
  • 8. DOM
    1. 8.0.1. 节点层次
    2. 8.0.2. DOM 如何创建元素
    3. 8.0.3. DOM 获取元素的方式
    4. 8.0.4. 获取 DOM 节点 get 系列和 query 系列哪种性能好?
    5. 8.0.5. 节点属性中 children 和 childNodes 有什么区别?
    6. 8.0.6. HTMLCollection 和 NodeList 的比较
  • 9. BOM
  • 10. 事件
    1. 10.0.1. 事件流
    2. 10.0.2. 如何给元素注册事件
    3. 10.0.3. 如何阻止事件冒泡?如何取消默认事件?如何阻止事件的默认行为?
  • 11. 严格模式
  • 12. 那些会让同事打你的简便写法
    1. 12.1. ~~