github.io 有点慢,font-awesome 有点大,
客官麻烦搬条板凳坐一下下,我马上就到!

JavaScript 基础

数据类型、对象从属的判断、类属性和实例属性、严格模式、属性读写、属性标签、数组、上下文管理、属性标签、函数属性、闭包、继承。

数据类型

  • number
  • string
  • boolean
  • null
  • undefined
  • Object
    • Function
    • Array
    • Date
    • Number

对象从属的判断

typeof

1
2
> typeof 123
- "number"

instanceof

1
2
3
4
5
6
> new Number(123) instanceof Number
- true
> 123 instanceof Number
- false
> [1,2] instanceof Array
- true

*. instanceof 不能跨 windowiframe

Object.prototype.toString.apply()

1
2
> Object.prototype.toString.apply([])
- "[object Array]"

类属性和实例属性

1
2
3
4
5
6
7
8
9
10
function Foo(){}
Foo.prototype.x = 1;
var obj = new Foo();

> obj.x
- 1
> obj.hasOwnProperty('x')
- false
> obj.__prototype__.hasOwnProperty('x')
- true

严格模式

argumentseval 均为保留字

属性读写

不同的属性读写方式

1
2
3
4
5
var obj = {x:1, y:2};
> obj.x // method 1
- 1
> obj["x"] // method 2
- 1

链式属性查找

1
var yz = obj && obj.y && obj.y.z

继承属性和自有属性

辨析: in 操作符、hasOwnProperty 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
var cat = new Object;
cat.legs = 4;

> "legs" in cat
- true
> "toString" in cat
- true

// 遍历属性时,过滤原型链里的属性
> cat.hasOwnProperty("legs")
- true
> cat.hasOwnProperty("toString")
- false

getter() 和 setter()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var person = {
wechat: 'mywechat',
$age: null,
get age(){
return new Date().getFullYear() - 1997; // 这一行的 new 非常关键!!
},
set age(value){
console.log('Property cannot be set.')
}
}

> person.age
- 21
> person.age = 100
- 'Property cannot be set.'
1
2
3
4
5
6
7
8
9
10
function foo(){}

// 创建了一个只读对象
Object.defineProperty(foo.prototype, 'z', {get: function(){return 1}});

> foo.z
- 1
> foo.z = 5
> foo.z
- 1

JSON 序列化

正向序列化: JSON.stringify(注意奇怪的拼写)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var obj1 = {x:1, y:[1,2,3], nullVal:null, undefinedVal:undefined}
var obj2 = {x:NaN, y:Infinity, z:new Date()}

> JSON.stringify(obj1)
- "{"x":1,"y":[1,2,3],"nullVal":null}" // 注意:undefine 变量已被删去
> JSON.stringify(obj2)
- "{"x":null,"y":null,"z":"2018-07-18T16:51:42.611Z"}" // 注意:格式转换

// 自定义 JSON 格式
var obj1 = {x:1, y:{
x:2, y:3,
toJSON:function(){return this.x + this.y} // 注意:this 的使用,不写有另外的效果
}}

> JSON.stringify(obj1)
- "{"x":1,"y":5}"

反向序列化: JSON.parse

1
2
3
4
5
var json_string = '{"x":1,"y":[1,2,3],"nullVal":null}'
obj = JSON.parse(json_string)

> obj.x
- 1

自定义强制转换规则

toStringvalueOf

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = {x:1, y:2};

obj.toString = function(){return this.x + this.y} // 同样需要注意 this 的用法

> "Result" + obj
- "Result 3"

obj.valueOf = function(){return this.x + this.y + 100}

> +obj // 一元操作符强制转换为数字
- 103
> "Result" + obj
- "Result 103" // 注意:这里有两次转换

属性标签

Object.defineProperty()

感觉是个大学问,日后完善

image-20180719003417900

1
2
3
4
5
6
7
8
9
10
11
var obj = new Object();

Object.defineProperty(obj, 'property', {
value: 123,
configurable: true, // `true` as default => 决定属性是否可以被 `delete`
writeable: false, // `true` as default
enumerable: true, // `true` as default => 决定 for in 循环,和是否出现在 Object.keys(obj) 中
get: function(){return 1},
});

obj.propertyIsEnumerable("property")

属性标签探测

1
2
> Object.getOwnPropertyDescriptor({pro: true}, 'pro')
- {value: true, writable: true, enumerable: true, configurable: true}

属性标签固定

1
2
3
Object.preventExtenions(obj)
Object.seal(obj)
Object.freeze(obj)

数组

arr.length

求长,arr.length -= 1 操作可以移除最后一个元素。

*. delete 操作使得某元素的位置变为 undefined,但数组长度不会变。

arr.push() / arr.pop()

arr.push() 等价于 arr[arr.length]=

arr.unshift() / arr.shift()

在数组头部插入 / 删除

arr.join()

和 python 里的用法反一下。

1
2
3
4
5
6
function repeatString(str, n) {
return new Array(n + 1).join(str)
}

> repeatString('ab', 3)
- 'ababab'

arr.reverse()

同 python,略。

arr.sort()

如果 arr 内的元素是数字,会按照字符串来排序(并不是按大小)。

1
2
arr.sort(function(a, b){return a-b})
// 通过这个方法可以实现数字大小比较(类比 python 的 cmp 函数)

arr.forEach()

1
2
3
4
5
arr.forEach(
function(x, index, arr) {
...
}
)

arr.slice()

arr.splice(a, b, *args)

从原数组第 a 位起挖去 b 位返回,并重新插入 *args 个元素。

arr.map() / arr.filter() / arr.reduce()

同 python,略。

arr.every() / arr.some()

1
2
3
4
5
6
var arr = [1, 2, 3, 4, 5]

> arr.every(function(x){
return x < 10;
});
- false

arr.indexOf(a, b) / lastIndexOf()

原数组从左往右(/右往左)的第 b 个元素开始,查找第一个 a 元素的位置。

上下文管理

with

注意: js 严格模式下禁用 with 块,可以使用变量来代替。

1
2
3
with ({x:1}){
console.log(x);
}
1
2
3
with (document.forms[0]){
console.log(name.value)
} // 这是干啥用的?

this

通常 指代容器所在的外层空间(爸爸的爸爸)。

1
2
3
4
5
6
7
this;
var obj = {
func: function(){return this;}
};

> obj.func();
- obj //实际测试得到 {func: ƒ}
1
2
3
4
5
6
7
o = {f: function(){return this.a+this.b}}
p = Object.create(o)
p.a = 2
p.b = 3

> p.f()
- 5

闭包中的 this 指向 window

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var test = {
a : 5,
b : 6,
sum : function (a,b) {
function getA(a) {
this.a = a; // 在window上增加了一个全局变量a
return this.a; // 此处this = window
}
function getB(b){
this.b = b; //在window上增加了一个全局变量b
return this.b; // 此处this = window
}
return getA(a) + getB(b);
}
}
alert(test.sum(4,3)); // 7
alert(a); // 4
alert(b); // 3

在这种情况下,我们希望 getA() 和 getB() 返回的值是 test.a 和 test.b ,但是此时闭包函数(即函数中的函数)getA 和 getB 中 this 并不指向 test 的实例,该怎么办呢?我们不妨试试下面的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var test = {
a : 5,
b : 6,
sum : function () {
var self = this; // 此处this = test的实例
function getA() {
return self.a;
}
function getB(){
return self.b;
}
return getA() + getB();
}
}
alert(test.sum());
alert(a); // 此处报错:a is not defined
alert(b); // 此处报错:a is not defined

在 test 对象的 sum 函数中用一个局部变量 self 来保存当前的 this 指针,这样在闭包函数 getA 和 getB 中就能通过 self 变量获取 test 实例的属性了。

看起来这样就能够解决闭包函数中 this 的问题了,但是,如果调用 sum 函数的并不是 test 的实例呢,这个时候 var self=this 还能起到作用,获取到 test 的实例吗?

为了更方便地改变函数上下文(context)的空间,我们使用 call() / apply()) / apply() / apply()) / bind() 等函数(见下)。

call() / apply()

这两例中也需要格外注意 this 的用法。

用法: 调用一个对象的一个方法,以另一个对象替换当前对象。

1
2
3
4
5
6
7
function add(c, d) { return this.a + this.b + c + d }
var o = {a:2, b:3};

> add.call(o, 5, 7);
- 17
> add.apply(o, [5, 7]);
- 17
1
2
3
4
5
6
function bar(){
console.log(Object.prototype.toString.call(this));
}

> bar.call(7);
- "[object Number]"

bind()

定义: 将接受多个参数的函数变换成接受一个单一参数(定义时不传参,调用时再传参)。

说明: bind() 方法所返回的函数的 length(形参数量)等于原函数的形参数量减去传入 bind() 方法中的实参数量(第一个参数以后的所有参数),因为传入 bind 中的实参都会绑定到原函数的形参。

1
2
3
4
5
6
7
8
function f(){ return this.a }
var g = f.bind({a: "test"});
var o = {a: 37, f: f, g: g};

> o.g();
- "test"
> o.f();
- 37 "test"

*. 扩展:高阶实例

本实例以下内容来源:使用call、apply和bind解决js中烦人的this,事件绑定时的this和传参问题

1
2
3
4
5
6
7
8
9
10
11
12
13
<button id="btn">烦人的this</button>
<script>
var test = {
isSum: true,
sum: function (event, a, b) {
if (this.isSum) {   // this = button,这个时候不会执行alert(a+b)
alert(a + b);
}
}
}
var button = document.getElementById("btn");
button.addEventListener("click", test.sum, false);
</script>

这里我们就能发现问题所在了,当ID为btn的按钮被点击时会触发test.sum函数,但是这个时候的this=button,而且参数a、b如何传入呢?

这里就能够使用bind函数了,将test.sum函数简化为另一个新的函数,同时传入参数a和b,我们再看看下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
<button id="btn">this</button>
<script>
var test = {
isSum: true,
sum: function (a, b,event) {
if (this.isSum) {  // 此处this=test,this.isSum = true
alert(a + b);  // 9
}
}
}
var button = document.getElementById("btn");
button.addEventListener("click", test.sum.bind(test,4,5), false); // 此处test.sum.bind(test,4,5)返回一个新的函数function(event),
</script>

从上面的代码我们可以看到test.sum.bind(test,4,5)返回一个新的函数function(event),test、4、5分别被绑定到test.sum的上下文、参数a、参数b中。
当ID为btn的按钮被点击时会触发test.sum函数,此时改函数中的this=test,a=4,b=5。

这样就可以解决事件绑定时的this以及传参的问题了,包括现在常用js框架中的事件绑定,如jQuery、signals.min.js等等。

*. 扩展:this 的指向

本实例以下内容来源:js: this,call,apply,bind 总结

JavaScript 中的 this 总是指向一个对象,而具体指向那个对象是在运行时基于函数的执行环境动态绑定的,而非函数声明时的环境。实际应用中 this 的指向大致可以分为以下 4 种:

  1. 作为对象的方法调用
  2. 作为普通函数掉用
  3. 构造器调用
  4. Function.prototype.call 或 Function.prototype.apply 调用, 可以动态地改变出入函数的 this

作为对象的方法调用时, this 指向该对象

1
2
3
4
5
6
7
8
var obj = {
a: 1,
getA: function(){
console.log( this == obj ); // true
console.log( this.a ); // 1
}
};
obj.getA();

作为普通函数掉用,this 总是指向全局对象 window

1
2
3
4
5
6
7
8
console.log(this); // Windows

window.name = "globalName";
var getName = function() {
return this.name;
}

console.log( getName() ); // globalNamev

作为构造器调用,指向返回的新对象

当用 new 运算符调用函数时,该函数总是会返回一个对象,通常情况下,构造函数里的 this 就指向返回的这个对象。

1
2
3
4
5
var MyClass = function(){
this.name = "class";
}
var obj = new MyClass();
console.log( obj.name ); // classv

如果使用 new 调用构造器时,构造器显式地返回了一个 object 类型的对象,那么此次运算结果最终会返回这个对象,而不是我么之前期待的 this。

1
2
3
4
5
6
7
8
var MyClass = function(){
this.name = "class";
return {
name: "other"
}
}
var obj = new MyClass();
console.log(obj.name); // other

*. 再扩展

最后贴一个我还没看完的似乎很棒的长文:详解JS中的this、apply、call、bind(经典面试题)

函数属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function foo(x, y, z){
"use strict";

> arguments.length
- 2 // _实参_ 的个数
> arguments[0]
- 10

arguments[0] = 1
> x
- 0 // 非严格模式下可以这么改变
> z
- "undefined"

> arguments.callee
- Error // 非严格模式下为 function foo
}

foo(1, 2);

> foo.length
- 3 // _实参_ 的个数
> foo.name
- "foo"

闭包

一个典型的错误例子:

1
2
3
4
5
6
7
8
9
document.body.innerHTML = "<div id=div1> aaa </div>"
+"<div id=div2> aaa </div><div id=div3> aaa </div>"
for (var i=1; i<4; i++){
console.log(i)
document.getElementById('div'+i).
addEventListener('click', function(){
alert(i);
})
}

运行后,控制台输出“1 ↙ 2 ↙ 3”,但点击div时,alert 的内容均为4。

运用【立即执行】的匿名函数实现预期功能:

1
2
3
4
5
6
7
8
9
10
11
document.body.innerHTML = "<div id=div1> aaa </div>"
+"<div id=div2> aaa </div><div id=div3> aaa </div>"
for (var i=1; i<4; i++){
!function(i){
console.log(i)
document.getElementById('div'+i).
addEventListener('click', function(){
alert(i);
})
}(i);
}

立即执行(IIFE)

  • 限定变量作用范围(私有属性的实现);
  • 避免污染全局变量;
  • 闭包变量的固定;

以下内容摘自:JavaScript中立即执行函数实例详解

因为IIFE通常用于匿名函数,这里就用简单的匿名函数作为栗子:

1
2
3
4
var f = function(){
console.log("f");
}
f();

我们发现这里f只是这个匿名函数的一个引用变量,那么既然f()能够调用这个函数,我把f替换成函数本身可以么:

1
2
3
function(){
console.log("f");
}();

运行之后得到如下结果:

1
Uncaught SyntaxError: Unexpected token (

产生这个错误的原因是,Javascript引擎看到function关键字之后,认为后面跟的是函数声明语句,不应该以圆括号结尾。解决方法就是让引擎知道,圆括号前面的部分不是函数定义语句,而是一个表达式,可以对此进行运算,这里区分一下函数声明和函数表达式:

1
2
3
4
//用小括号把函数包裹起来
(function(){
console.log("f");
})();

函数成功执行了:

1
f //控制台输出

这种情况下Javascript引擎就会认为这是一个表达式,而不是函数声明,当然要让Javascript引擎认为这是一个表达式的方法还有很多:

1
2
3
4
5
6
7
!function(){}();
+function(){}();
-function(){}();
~function(){}();
new function(){ /* code */ } // 构造器也是立即执行的。
new function(){ /* code */ }() // 只有传递参数时,才需要最后那个圆括号。
……

作用域链

函数内层可以访问外层变量,外层不能访问内层。

继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Person(name, age){
this.name = name;
this.age = age;
}

Person.prototype.sayHi = function(){
console.log(this.name+' says: hi!')
}

function Student(name, age, grade){
Person.call(this, name, age);
this.grade = grade;
}
Student.prototype = Object.create(Person.prototype); // 沿伸原型链
Student.prototype.constructor = Student();

// 函数重写
Student.prototype.sayHi = function(){
console.log('Student '+this.name+' says: hi!');
}

> John = new Student('John', 21, 5)
> John.sayHi()
- 'Student John says: hi!'

实现继承的几种方式

1
2
3
4
5
Student.prototype = Person.prototype;
Student.prototype = new Person();
Student.prototype = Object.create(Person.prototype);
// 不完全实现
Student.constructor = Person();

链式调用

return this

1
2
3
4
5
6
7
8
9
10
11
function ClassManager(){}
ClassManager.prototype.addClass = function(str){
console.log('Class ' + str + ' added!');
return this; // 非常关键!
}

> var manager = new ClassManager();
> manager.addClass('class1').addClass('class2').addClass('class3');
- Class class1 added!
Class class2 added!
Class class3 added!

上面的代码改写如下,效果完全相同:

1
2
3
4
5
6
function ClassManager(){
this.addClass = function(str){
console.log('Class ' + str + ' added!');
return this; // 非常关键!
}
}

正则表达式

表示方法

  • /\d\d\d/
  • RegExp(‘\d\d\d’)

匹配

1
2
3
4
> /\d\d\d/.test(123);
- true
> new RegExp('\d\d\d').test(6512345);
- false

三个 flag

  • g - global
  • i - ignoreCase
  • m - multiline
1
2
/\d\d\d/mgi.test(123);
new RegExp('\d\d\d', 'gim').test(6512345);

字符串用法

  • search
  • test
  • replace
  • match
  • split
1
2
3
4
> 'aabbbccbbd'.match(/b+/);
- ["bbb", index: 2, input: "aabbbccbbd", groups: undefined]
> 'aabbbccbbd'.match(/b+/g);
- ["bbb", "bb"]


————  EOF  ————