介绍

这是些零散的知识点,整理整理。涉及ES5/ES6/ES7。

1、数据类型 / 转换

JavaScript 默认内置其中数据类型:
6种原始类型:null,undefined, boolean,number,string和ES6新出的symbol
1种其他类型:object

1
2
3
4
5
6
typeof 0              // number
typeof true // boolean
typeof 'Leo' // string
typeof Math // object
typeof null // object
typeof Symbol('Leo') // symbol (New ES6)
①对比 null 和 undefined

undefined 表示尚未定义,常作为变量,函数参数,对象属性的默认值。当函数没有返回值时也会默认返回undefined
null 表示空值,常被赋值给一个变量表示没有值

②隐式转换类型
1
2
let leo = 'leo'
if(leo) console.log(leo + 'is good boy!') # leo is good boy!

此时字符串变量 leo 会被转换成 true ,并执行后面的代码。
Falsy 类型的值,指在强制类型转换时会被转成布尔 false 的值。包括: "",0,null,undefined,NaN,false
除了 Falsy 类型的值,其他都成为 truthy 类型的值,并转换成 true

1
2
3
4
5
6
Boolean(null)         // false
Boolean('leo') // true
Boolean('0') // true
Boolean(' ') // true
Boolean([]) // true
Boolean(function(){}) // true
③String 和 Number 转换

+ 既可以作为算术运算也可以连接字符串。
但是 *,/-与字符串一起使用,会强制将字符串转换成数字类型。

1
2
3
4
5
6
7
8
9
10
11
12
1 + "2"        = "12"
"" + 1 + 0 = "10"
"" - 1 + 0 = -1
"-9\n" + 5 = "-9\n5"
"-9\n" - 5 = -14
"2" * "3" = 6
4 + 5 + "px" = "9px"
"$" + 4 + 5 = "$45"
"4" - 2 = 2
"4px" - 2 = NaN
null + 1 = 1
undefined + 1 = NaN
④== 和 ===

== 比较强制类型转换后的结果,而 === 是直接比较。

1
2
3
4
1 == '1'            // True
1 === '1' // False
undefined == null // True
undefined === null // False

强制类型转换会造成一些混乱,比如:

1
2
3
4
5
6
7
8
let a1 = '0',a2 = false,a3 = 0;
Boolean(a1) // True
Boolean(a2) // False
Boolean(a3) // False
a1 == a2 // True
a1 == a3 // True
a1 === a2 // False
a1 === a3 // False

即:
其他类型Boolean 对比,会先把 其他类型 转换成 Boolean类型 再比较;
string类型Number 对比,会先把 string类型 转换成 Number类型 再比较;
还有一些特殊的比较:

1
2
3
4
5
6
7
8
9
false == ""  // true
false == [] // true
false == {} // false
"" == 0 // true
"" == [] // true
"" == {} // false
0 == [] // true
0 == {} // false
0 == null // false

2、函数表达式 和 函数声明

函数表达式只有被执行后才可用,它不会被提升(相当于赋值函数表达式给变量):

1
2
3
let leo = function(a,b){
return a + b ;
}

函数声明则可以在定义前后被任意调用,因为它始终会被提升:

1
2
3
function leo(a,b){
return a + b;
}

3、var let const

var 在某一函数内部声明的变量和方法只能在其函数作用域内部访问到。
let 所声明的变量,不会被提升,只在 let 命令所在的代码块内有效。
const 所声明的变量,不能被重新赋值,但其指向的值是可以被操作的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function funvar(){
if(true){
var a = '这是funvar'
}
console.log(a);
}
function funlet(){
if(true){
let b = '这是funlet'
}
console.log(b);
}
funvar(); // 这是funvar
funlet(); // <b is not defined>

const leo = 'my name is leo~';
leo = 'try to change name';
console.log(leo) // TypeError: Assignment to constant variable.

const leoArr = ['hello','my','name','is','leo'];
console.log(loeArr[0]); // ['hello','my','name','is','leo']
leoArr[0] = 'Hi';
cconsole.log(leoArr) // ["Hi", "my", "name", "is", "leo"]

4、上下文

上下文的概念经常会同作用域之间混淆。为了保持条理清晰,我们需要注意以下两条:

上下文是在函数被调用时确定的。它通常指的是你的代码当中某一部分的值。
作用域值的则是变量能过被访问到的范围。

1
2
3
4
5
6
7
8
9
10
11
// 作用域
var param = 1; // global scope
function myScope(){
var param = 2; // local scope
}
// 上下文
this.prop = 1; // global context
function myContext(){
this.prop = 2; // local context
}
var myInstance = new myContext();

例如上述实例当中调用 this 的位置不同,this 的指向也是不同的,也就表示着不同的上下文;而作用域则是我们在编写代码时使用 var 关键字来确定的。

5、函数调用方式:call apply bind

这三种方法可以改变函数调用时 this 的指向,区别则在于函数调用的时候。

.call() 会立即调用函数,并要求你按次序一个个传入参数。
.apply() 也会立即调用函数,不过你需要以数组的形式传参。
.call() 和 .apply() 效用几乎是相同的,它们都可以用来调用对象中的某个方法,具体怎么使用取决于你的使用场景里如何传参更方便。

1
2
3
4
5
6
7
8
9
10
const Snow = {username: 'Snow'}
const char = {
username: 'Stark',
knows: function(arg, name) {
console.log(`You know ${arg}, ${name} ${this.username}`);
}
}
char.knows('something', 'Bran'); // You know something, Bran Stark
char.knows.call(Snow, 'nothing', 'Jon'); // You know nothing, Jon Snow
char.knows.apply(Snow, ['nothing', 'Jon']); // You know nothing, Jon Snow

注意: 如果你在使用 .call() 时传入了数组形式的参数,它会把整个数组当作一个参数使用。

不过在 ES6 里你倒是可以试试展开操作符的方法进行传参:

1
char.knows.call(Snow, ...["nothing", "Jon"]);  // You know nothing, Jon Snow 

.bind() 不会直接触发某个方法,而是根据你传入的参数和上下文返回一个新的方法。
当你想要在程序之后的某些上下文环境中调用一个方法时可以使用 .bind() 这种方式。

这在我们使用一些异步操作或者事件处理函数时非常有用。.bind() 的传参形式则类似于 .call() 你需要讲参数以逗号分隔传入:

1
2
3
4
5
6
7
8
const Snow = {surename: 'Snow'}
const char = {
surename: 'Stark',
knows: function(arg, name) {
console.log(`You know ${arg}, ${name} ${this.surename}`);}
}
const whoKnowsNothing = char.knows.bind(Snow, 'nothing');
whoKnowsNothing('Jon'); // You know nothing, Jon Snow

5、严格模式

我们可以通过使用 "use strict" 指令来启用 JavaScript 的严格模式。它会为你的代码添加更多的限制及错误处理。
使用严格模式的好处有:

更方便调试 你能够看到更多的报错,例如在你试图为只读的全局对象或属性赋值时。
防止意外产生全局变量 对未声明的变量进行赋值时会报错。
禁止无效的删除操作 尝试删除变量、函数、不可删除的属性时会报错。
禁止重复的属性名及参数 如果有命名重复的属性名或者参数值就会报错。
eval() 的调用更加安全 在 eval() 方法内部定义的变量及函数不会污染其他作用域。
禁止 this 指向全局对象 当 this 的值为 null 或者 undefined 时不会再默认指向到全局对象。这也就意味着在函数内部的 this 不会再默认指向 window 对象了。

6、Object.keys()

Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用 for...in 循环遍历该对象时返回的顺序一致 (两者的主要区别是 一个 for-in 循环还会枚举其原型链上的属性)。
详细:点击查看
🌰栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* Array 对象 */ 
let arr = ["a", "b", "c"];
console.log(Object.keys(arr)); // ['0', '1', '2']

/* Object 对象 */
let obj = { foo: "bar", baz: 42 },
keys = Object.keys(obj); // ["foo","baz"]

/* 类数组 对象 */
let obj = { 0 : "a", 1 : "b", 2 : "c"};
console.log(Object.keys(obj)); // ['0', '1', '2']

// 类数组 对象, 随机 key 排序
let anObj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.keys(anObj)); // ['2', '7', '100']

/* getFoo 是个不可枚举的属性 */
var my_obj = Object.create(
{},
{ getFoo : { value : function () { return this.foo } } }
);
my_obj.foo = 1;
console.log(Object.keys(my_obj)); // ['foo']

7、”void”运算符

查看原文 MDN介绍
这个运算符能向期望一个表达式的值是undefined的地方插入会产生副作用的表达式。
void 运算符通常只用于获取 undefined 的原始值,一般使用void(0)(等同于void0)。在上述情况中,也可以使用全局变量undefined 来代替(假定其仍是默认值)。

在MDN上的定义是:void运算符对给定的表达式进行求值,然后返回undefined
使用它来实现立即执行的函数表达式(IIFE),如下:

1
2
3
4
5
6
7
8
9
void function foo(){
console.log('hello Leo')
}()

// 与下面等效

(function foo(){
console.log('hello Leo')
})()

使用void必须注意的是,无论给定表达式返回结果如何,void运算符整体返回的结果都是空的(undefined)!

1
2
3
4
5
6
7
const w1 = void function foo(){ 
return 'hello leo'
}() // undefined

const w2 = (function foo(){
return 'hello leo'
})() // hello

async 一起使用,这样就能把函数作为异步代码的入口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void async function (){
try{
const res = await fetch('air.ghost.io');
const text = await res.text();
console.log(text);
} catch(e){
console.log(e)
}
}

// 与下面等效

(async ()=>{
try{
const res = await fetch('air.ghost.io');
const text = await res.text();
console.log(text);
} catch(e){
console.log(e);
}
})();

另外,经常也用在 URI操作 上,当用户点击一个以 javascript: URI 时,它会评估URI中的代码,然后用返回的值替换页面内容,除非返回的值是undefinedvoid运算符可用于返回undefined。例如:

1
2
3
4
5
6
7
8
<a href="javascript:void(0);">
这个链接点击之后不会做任何事情,如果去掉 void(),
点击之后整个页面会被替换成一个字符 0。
</a>
<p> chrome中即使<a href="javascript:0;">也没变化,firefox中会变成一个字符串0 </p>
<a href="javascript:void(document.body.style.backgroundColor='green');">
点击这个链接会让页面背景变成绿色。
</a>

注意,虽然这么做是可行的,但利用 javascript: 伪协议来执行 JavaScript 代码是不推荐的,推荐的做法是为链接元素绑定事件。

8、简单介绍闭包

闭包,又一个老生常谈的话题,可以用一句话对之概括:有权访问另一个函数作用域内变量的函数都是闭包。例如:

1
2
3
4
5
6
7
8
9
10
function outer(){
var a = 0;
function inner(){
console.log(a++);
};
return inner;
}
var closure = outer();
closure(); // 1
closure(); // 2

这里返回的inner函数就是能够访问outer函数中变量的闭包,除inner函数之外的外部作用域都无法访问outer函数中的变量a

闭包特性:

  • 1.函数返回嵌套的函数形成闭包
  • 2.闭包内部可以访问外部的参数和变量
  • 3.外部参数和变量在被闭包引用时不会被垃圾回收机制回收

闭包优点:

  • 1.可避免变量对全局的污染
  • 2.允许函数私有成员的存在
  • 3.允许变量长驻内存

闭包缺点:
由于变量常驻内存,增大内存使用量,使用不当很容易造成内存泄漏。

闭包应用场景:

  • 1.采用函数引用方式的setTimeout调用
  • 2.将函数关联到对象的实例方法
  • 3.封装相关的功能集

最近更新 2018.02.07