Web领域三剑客:HTML[服务于内容];CSS[服务于表现];JavaScript[服务于行为];

介绍

JavaScirpt通常涵盖三个部分:

ECMAScript

语言的核心,独立于浏览器只为,可以在其他环境中使用,是一种规范。

DOM

文档对象模型,实际上是提供了一种于HTML,XML文档交互的方式。

BOM

浏览器对象模型,实际上是一个与浏览器环境有关的对象集合。

面向对象

对象

对象名词表示,属性动词表示,方法形容词表示。

类是一种模板,对象是在模板基础上创建的实体。

JavaScript语言没有类,一切都是基于对象的,依靠的是一套原型(prototype)系统,而原型本身也是一种对象。

封装

通常有两部分组成:

  • 相关的数据(用于存储属性)
  • 基于这些数据能做的事(所能调用的方法)

封装,只需要知道所操作对象的接口,而不必关系他的具体实现。

继承

通过继续,可以优雅的实现对现有代码的重用。

传统的OOP环境中,继承通常指类与类之间的关系,但由于JavaScript中没有类,因此它的继承只能发生在对象之间。

多态

不同对象通过相同的方法调用来实现各自行为的能力,(同一接口,不同实现)即为多态。

函数

JavaScript是通过函数来实现面向对象特性的。

函数通常会有返回值,如果没有显示的返回值,就会默认返回undefined。

一个函数只能有一个返回值,如果要返回多个值,可以返回一个数组。

在函数名后面加个括号,就可以调用函数。

函数对于参数来者不拒,多余的参数会自动忽略掉,少的参数默认赋值undefined。

JavaScript中,不是以代码块作为作用域,而是以函数作为作用域。

当JavaScript执行过程进入新的函数时,这个函数内所有声明的变量会被移动或者提升到函数最开始的地方,被提升的只有变量的声明。

函数也是一种数据,即函数可以像其他数据一样,赋值给一个变量。

内建函数

  • parseInt();

    将输入的值,转成整数型输出,转换失败返回NaN。

  • parseFloat();

    将输入的值,转换成十进制数,转换失败返回NaN。

  • isNaN();

    判断一个输入值,是否是一个可以参与算术运算的数字。

  • isFinite();

    判断一个输入值,是否是一个非Infinity非NaN的数字 。

  • encodeURI();

  • decodeURI();

  • encodeURIComponent();

  • decodeURIComponent();

  • eval();

    将输入的字符串当做JavaScript代码执行。Eval is evail

匿名函数

1
2
3
var f = function (a) {
return a;
}

匿名函数用法:

  • 作为参数传递给其他函数
  • 执行一次性任务

回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function invokeAdd(a, b) {
return a() + b();
}

function one() {
return 1;
}

function two() {
return 2;
}

invokeAdd(one, two);//3

//使用匿名函数
invokeAdd(
function () {
return 1
},
function () {
return 2
}
);//3

将函数A传递给函数B,并有函数B来执行A时,A就成了一个回调函数(callback functions)。如果A是一个匿名函数,就是匿名回调函数。

回调函数优势:

  • 可以在不做命名的情况下传递函数,意味着节省变量名的使用
  • 将一个函数调用委托给另一个函数,意味着节省代码
  • 提升性能

即时函数

1
2
3
(function (name) {
alert('Hello' + name);
})('CHC');

将一个匿名函数定义在一个括号里面,在跟一个括号,就是即时函数。第二个括号是立即调用的意思,同时也是向匿名函数传递参数的地方。

即时函数好处是不会产生任何全局变量,缺点无法重复执行,因此适合执行一些一次性的或者初始化的任务。

内嵌函数

1
2
3
4
5
6
function outer(name) {
function inner(input) {
return input;
}
return inner(name);
}

使用内嵌函数,也称私有函数,优势:

  • 确保全局名字空间的纯净性,意味着命名冲突的机会变小
  • 确保私有性

闭包

作用域链

子作用域,可以访问父级作用域,这就形成了一个作用域链(scope chain)。但是,父级作用域却不能访问子作用域。

突破链

闭包,突破了作用域链,使得父级作用域也可以访问子级作用域。

如何突破?–挂到全局上

  • 升级为全局变量
  • 返回给全局空间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//1.赋给全局
var inner;
var F = function () {
var b = "local variable";
var N = function () {
return b;
}
inner = N;
}
F();
inner();//local variable
//2.返回全局
var a = "global variable";
var F = function () {
var b = "local variable";
var N = function () {
var c = "inner variable";
return b;
}
return N;
}
b;//ReferenceError: b is not defined
var inner = F();
inner();//loacl variable

闭包典型案例

  • 循环
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
33
34
35
36
37
38
39
40
function F() {
var arr = [],
i;
for (i = 0; i < 3; i++) {
arr[i] = function () {
return i;
}
}
return arr;
}

var arr = F();

arr[0]();//3
arr[1]();//3
arr[2]();//3

//这里创建了三个闭包,而他们指向了一个共同的局部变量i
//但是,闭包不会记录它们的值,它们拥有的是一个连接(引用)
//当闭包去获取某个变量时,会从所在域开始逐级寻找那个i,而循环结束时,i已经是3。

function F() {
var arr = [],
i;
for (i = 0; i < 3; i++) {
arr[i] = (function (x) {
return function () {
return x;
}
})(i);
}
return arr;
}

var arr = F();

arr[0]();//0
arr[1]();//1
arr[2]();//2
//通过中间函数将i的值本地化
  • getter/setter
1
2
3
4
5
6
7
8
9
10
11
12
var getValue, setValue;
(function () {
var secret = 0;
getValue = function () {
return secret;
}
setValue = function (v) {
if (typeof v === "number") {
secret = v;
}
}
})();

对象

对象的情况跟数据相似,唯一不同的是对象的键值类型是有自己定义的。

除基本类型(number,string,boolean,null,undefined)意外的任何数据都属于对象。

1
2
3
4
5
6
7
8
9
10
var hero = {
name: 'chc',
age: 18,
say: function () {
alert('I Can Fly');
}
}
//1.定义数组用中括号[],定义对象使用大括号{};
//2.组成对象的元素,称为属性,是用逗号分割
//3.键/值对之间用冒号分割,Key:Value

##对象组成

说到数组,其中包含的称为元素,说到对象,其中包含的称为属性。

对象的属性也可以是函数,因为函数本身也是一种数据,这种属性称为方法。

在JavaScript中,通常用数组来表示索引型数组(枚举型数组、索引型数组),用对象来表示关联型数组(哈希表、字典)。

拷贝某个对象或者将对象传递给某个函数,传递的都是对象的引用;引用上做的修改,会影响原有对象。

通过instanceof操作符,可以测试某个对象是不是由某个构造器创建的。

对象访问

访问属性方式:访问方法一样

  • 中括号表示法

  • 点号表示法;

    如果所访问的属性不符合变量命名规范,就不能通过点号表示法访问。

    如果访问的属性不存在,就会返回undefined。

    如果访问的属性名是不确定的,可以使用中括号法允许在运行时通过变量来实现相关属性的动态存取。

对象的修改

由于JavaScript是一种动态语言,所以允许我们随时对对象的属性和方法进行修改。

ES5允许创建不可改变的对象。

构造器函数

对象的创建方式:

  • 对象字面量:使用大括号{}
  • 构造器函数:使用new
1
2
3
4
5
6
function Hero() {
this.name = 'chc';
}

var hero = new Hero();
hero.name;

使用构造器函数的好处之一就是它可以在创建函数时,传一些参数。

依照惯例,将构造器函数的首字母大写,以区别其他函数。

使用new,创建的是一个对象,this,指向该对象;没有new,就是函数调用,返回值,this,指向全局对象。对于web浏览器,全局对象就是window。

五种基本类型,除了undefined和null外,其他三个都有自己的构造器函数,分别是Number(),String(),Boolean();

构造器属性

创建对象的同时,就赋予了该对象一种特殊的属性,即构造器属性(constructor property)。该属性实际上指向用于创建对象的构造器函数的引用。

1
2
3
4
5
6
7
hero.constructor;
ƒ Hero() {
this.name = 'chc';
}

//由于构造器属性引用的是一个函数,我们可以利用它创建一个新的对象。
var h = new hero.constructor();

对于通过对象字面量生成的对象,实际上是由内建构造器Object()函数创建的。

返回对象的函数

除了使用new,还可以使用一般函数,来创建对象,就是通过函数返回一个对象。

原型

##prototype

每个函数,在创建时,都会生产一个原型属性prototype,原型属性对函数本身不会有什么影响,只有当函数作为构造器使用时,属性才会有作用。

可以利用原型,对构造器生产的对象添加属性和方法。

对于原型来说,最重要的一点是要理解它的实时性。在JavaScript中,几乎所有的对象都是通过引用传值,因此,所创建的每个新对象并没有一分属于自己的原型副本。意味着修改原型,会对所有的对象产生影响。

如果一个对象自身属性中没有找到指定的属性,就会在原型链中查找相关的属性,对象自身属性要优于原型属性。

判断一个属性是自身属性还是原型属性?hasOwnProperty();

判断一个属性是否可枚举?propertyIsEnumerable();

判断一个对象是否是另一个对象的原型?isPrototypeof();

获取一个对象的原型?Object.getPrototypeof();

_proto__连接

在JavaScript环境中,对象存在一个指向原型的链接,这个神秘的链接,就是对象的proto属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
var monkey = {
feeds: 'bananas',
breathes: 'air'
}

function Human() {}
Human.prototype = monkey;

var developer = new Human();
developer.feeds = 'pizza';
developer.hacks = 'JavaScript';

developer.__proto__ === monkey;//true

##原型扩展

在JavaScript中,内建的构造器函数(如,Array,String,Object和Function)都可以通过原型来进行扩展的。

1
2
3
4
5
6
7
8
9
10
11
Array.prototype.inArray = function (needle) {
for (var i = 0, len = this.length; i < len; i++) {
if (this[i] === needle) {
return true;
}
}
return false;
}
//扩展Array,查找数据是否存在某个值。
var colors = ['red', 'black'];
colors.inArray('red');//true

扩展原型,最常用来实现让老式浏览器支持新功能。

扩展原型,首先要检查方法是否已经存在,避免覆盖。

原型陷阱

处理原型问题,需要注意:

  • 对原型对象执行完全替换时,可能触发原型链的异常
  • prototype.constructor属性是不可靠的
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
33
34
35
36
37
function Hero() {
this.name = 'chc';
}

var zhangsan = new Hero();
var wangwu = new Hero();


Hero.prototype.say = function () {
return 'Hello';
}

zhangsan.say();//Hello
wangwu.say();//Hello

zhangsan.constructor === Hero;
wangwu.constructor === Hero;

//原型覆盖
Hero.prototype = {
age: 18
};

zhangsan.age;//undefined
zhangsan.say();//Hello

zhangsan.__proto__.say();//Hello
zhangsan.__proto__.age;//undefined

//重新生成对象
var lisi = new Hero();
lisi.say();//Uncaught TypeError: lisi.say is not a function
lisi.age;//18

lisi.constructor;//ƒ Object() { [native code] }
//新对象的constructor不能保持正确了,应该是Hero的引用,却指向了Object;
//可以通过重置constructor属性

当我们重写某对象的prototype时,需要重置相应的constructor属性。

继承

原型链

默认的继承模式,即通过原型来实现继承关系。

继承的作用,可以使每个对象都能访问其继承链上的任何一个属性。

在JavaScript中,每个函数都有一个指向某一对象的prototype属性。

函数在被new操作符调用时,会创建一个对象,该对象会有一个指向原型对象的秘密链接,链接名为_proto_,因此,对象可以调用原型对象的属性和方法。

原型链末端是Object对象,该对象是JavaScript中最高级父对象,语言中所有对象必须继承它。

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
function Shape() {
this.name = 'Shape';
this.toString = function () {
return this.name;
}
}

function Circle() {
this.name = 'Circle';
}

function Triangle(side, height) {
this.name = 'Triangle';
this.side = side;
this.height = height;
this.getArea = function () {
return this.side * this.height / 2;
}
}

Circle.prototype = new Shape();
Triangle.prototype = new Circle();
//重置constructor
Circle.prototype.constructor = Circle;
Triangle.prototype.constructor = Triangle;

var my = new Triangle(5, 10);
my.getArea();
my.toString();//Triangle,这里调用的是继承Shape里面的toString()

![1][原型链.jpg]

原型共享

当用某一个构造器创建对象时,其属性就会被添加到this中去,并且被添加的属性实际上不会随着实体对象改变,如果将属性添加到原型上,就可以在对象之间共享,即该属性不在是私有属性。

注意必须在扩展原型对象之前,就完成继承关系的构建,因为继承本身就是对原型对象的一种覆盖。

出于效率考虑,尽可能将一些可重用的属性和方法添加到原型中去。

× 请我吃糖~
打赏二维码