JavaScript 支持面向对象的编程范式,面向对象是一种对现实世界理解和抽象的方法

所谓对象通常是指客观世界中的真实存在的事物,实物,实象。例如:一支笔、一本书、一只鸟

面向对象编程的三大特征:

  • 封装:只关心入口和出口,而不关心过程

  • 继承:指类与类之间的关系。如果两个类都有相同的属性或方法,那么可以让一个类继承于另类,这样就不需要在前者再次定义同样的属性或方法

  • 多态:不同的对象可以定义具有相同名称的方法,方法是作用于所在的对象中。这种不同对象通过相同方法的调用实现各自行为的能力,被称之为多态

构造函数

构造函数又可称为对象模版或类型对象,通过构造函数我们可以创建特定类型的对象( 实例 )

构造函数和普通函数的定义方式是一样的,通常为了区分,构造函数的名称首字母要求大写

语法:

1
2
3
function 名称([形参1, 形参2...形参N]){
//constructor body
}

创建实例

语法:

1
var 对象名 = new 构造函数([实参1, 实参2...实参N])

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 定义类 */
function Person(name, age, gender){
this.name = name,
this.age = age,
this.gender = gender;
}
Person.prototype.sayName = function(){ //方法需要添加到原型中以节省资源
alert(this.name)
}
/* 创建实例 */
var xiaoming = new Person("小明", 18, "男");
var xiaomei = new Person("小美", 16, "女");
/* 输出实例 */
console.log(xiaoming, xiaomei) //输出:Person{name: '小明', ...} Person{name: '小美', ...}

类是封装对象的属性和方法的载体。类定义了对象的特征,例如一辆汽车( 对象 )的名称、车型、重量、颜色等等都是这辆车的属性,而汽车的启动和停止则是这辆车的行为(方法)

类是面向对象编程中的一个基本的概念。实际上在 Js 中并没有类的概念❕,但 Js 通过构造函数和原型可以实现对类的支持

实例

类是对象的抽象,而对象是类的具体实例

通过类所创建的对象称为该类的实例,例如上面案例中的对象xiaomingxiaomei都是Person类的实例

this 关键字

this是Js引擎在执行时自动定义的变量,其值会指向一个对象

  • 在全局作用域中访问this时,其值指向全局对象window(默认值)

  • 在函数作用域中访问this时,根据 函数调用方式 的不同,this会指向不同的对象

  1. 以方法调用时,this会指向该方法所在的对象

  2. 以构造函数调用( new 构造函数 )时,this会指向其所创建的实例对象

例:

1
2
3
4
5
6
7
var xiaoming = {
name: '小明'
}
xiaoming.sayName = function() {
console.log('myName is '+this.name);
}
xiaoming.sayName(); //执行结果:myName is 小明

new 运算符

使用new运算符来进行对象实例化(根据类创建对象的过程)

实例化的过程:

  1. 创建一个空对象(即{}
  2. 执行构造函数,执行前会先
  • 将变量this的值指向步骤1中所创建的那个对象
  • 并向对象中添加属性__proto__,将该属性的值指向构造函数的原型对象prototype
  1. 构造函数执行完后,返回实例对象( 没有手动返回了一个对象的情况下,为步骤1中所创建的那个对象 )

call、apply、bind 方法

callapplybindFunction类型对象( 函数 )中的方法,通过 call()apply()bind() 方法可以改变函数作用域中变量 this 的值

call()的语法:

1
函数.call(this的值, 实参1, 实参2...实参N)

apply()的语法:

1
函数.apply(this的值, [实参列表])

bind()的语法:

1
函数.bind(this的值, 实参1, 实参2...实参N)

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* 例1 */
window.number = 'one';
var s1 = {number: 'tow'};
function change(){
console.log(this.number);
}
change.apply(); //执行结果:one
change.apply(window); //执行结果:one
change.apply(this); //执行结果:one
change.apply(s1); //执行结果:tow

/* 例2 */
function greet() {
var reply = this.animal + '通常只睡' + this.sleepDuration;
console.log(reply);
}
var obj = {
animal: '猫',
sleepDuration: '12~16个小时'
};
greet.call(obj);

call、apply、bind 的区别

callapplybind 方法都包含一个对象( 严格模式下除外 )作为该方法的第1个参数,其后的参数会作为函数的实参。但在传参时,apply() 方法需要将实参以数组的形式传递至函数

callapply 方法会以指定的 this 的值调用一次该函数,而 bind() 方法会将其所在函数返回,不会自动调用

如果以上传入的第一个参数为 null 或 undefined 则不会改变函数作用域中 this 的值( 严格模式除外 )

例:

1
2
3
4
5
6
function add(c, d){
return this.a + this.b + c + d;
}
var s = {a:1, b:2};
console.log(add.call(s, 3, 4)); //输出:10
console.log(add.apply(s, [5, 6])); //输出:14

原型(prototype)

函数是一个包含属性和方法的 Function 类型的对象。而原型( prototype )就是 Function 类型对象的一个属性

js 中所有函数在定义时,js 引擎都会自动向函数中添加prototype属性,其值是一个 Object 类型的对象( new Object() ),称为原型对象( prototype对象 ),该对象有一个 constructor 属性指向该构造函数

当通过构造函数创建实例时,实例对象中会有一个隐藏的内部属性( __proto__ )指向其构造函数的原型对象

例:

1
2
var obj = new Object();
console.log(obj.__proto__ === Object.prototype); //输出:true

原型链

每个实例对象都有一个私有属性(称之为__proto__)指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(__proto__),层层向上直到一个对象的__proto__属性值为null为止。这种沿着__proto__属性的链式结构就称为原型链

例:

1
2
3
4
5
6
function Fn(){
//this.xxx = xxx;
}
var fun = new Fn();
Fn.prototype.a = 123;
console.log(fun.a, fun.b); //输出:123 undefined

prototype

原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象

当从对象中访问一个属性时会先在自身对象中查找

  • 如果没找到,则沿着__proto__这条链向上查找
  • 如果依然没找到,则返回 undefined

Object.getPrototypeOf()

描述:获取指定对象的原型

语法:

1
Object.getPrototypeOf(对象)

Object.setPrototypeOf()

描述:设置指定对象的原型

语法:

1
Object.setPrototypeOf(对象, 原型)

hasOwnProperty()

描述:检测对象中的属性是否存在( 不包括原型中的属性,检测原型中的属性可以用 in 运算符 ),存在则返回 true,反之则为 false

语法:

1
对象.hasOwnProperty(属性名) //属性名为一个字符串

继承

JavaScript 是基于原型实现的继承,和其它传统的基于类的语言( 如 Java 或 C++ )相比,Js 显得比较独特

原型链继承

核心:将父类实例作为子类原型

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* 定义A类 */
function A(){
this.a = 'My is a'
}
A.prototype.ca = function(){
alert(this.a)
}
/* 定义B类 */
function B(){
this.b = 'My is b'
}
B.prototype = new A(); //子类的prototype指向父类的实例对象
B.prototype.constructor = B; //修正构造函数指向
B.prototype.cb = function(){
alert(this.b)
}
/* 创建B类的实例 */
var b = new B()

PS:原型链虽然很强大,用它可以实现 JavaScript 中的继承,但同时也存在着一些问题。原型链继承实际上是在多个构造函数或对象之间共享属性和方法,这种方法在创建子类的对象时,不能向父类的构造函数传递任何参数

借用构造函数继承

核心:借用父类构造函数来增强子类实例,等于复制父类实例的属性给子类

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 定义A类 */
function A(a){
this.a = a
this.ca = function(){
alert(this.a)
}
}
/* 定义B类 */
function B(b){
A.call(this, 'a') //调用父类构造函数,并绑定 this
this.b = b
this.cb = function(){
alert(this.b)
}
}
/* 创建B类的实例 */
var b = new B('b')

PS:借用构造函数继承就是通过 apply()call() 方法在子类构造函数中调用父类的构造函数,并将父类和子类的 this 绑定在一起,这种方式可以向父类传递参数,但不能实现方法的复用

组合式继承

核心:通过调用父类构造函数来实现向父类构造函数传递参数,然后通过将父类实例作为子类原型来实现方法复用

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* 定义A类 */
function A(a){
this.a = a
}
A.prototype.cs = function(){
alert(this.a)
}
/* 定义B类 */
function B(b){
A.call(this, 'a') //调用父类构造函数,并绑定 this
this.b = b
}
B.prototype = new A; //子类的prototype指向父类的实例对象
B.prototype.constructor = B; //修正构造函数指向
B.prototype.cb = function(){
alert(this.b)
}
/* 创建B类的实例 */
var b = new B('b')

PS:组合继承是将原型链继承和借用构造函数的技术组合在一起,发挥二者长处的一种继承方式,这样,既通过在原型上定义方法实现了方法的复用,又可以保证每个对象都有自己的专有属性,但这种方法,会调用2次父类的构造函数,因此会存在一份多余的实例属性

组合寄生式继承

核心:通过调用父类构造函数来实现向父类构造函数传递参数,然后通过将父类原型作为新对象的原型,并将新对象赋值给子类原型来实现方法复用,并且避免了多余的实例属性

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* 定义A类 */
function A(a){
this.a = a
}
A.prototype.cs = function(){
alert(this.a)
}
/* 定义B类 */
function B(b){
A.call(this, 'a') //调用父类构造函数,并绑定 this
this.b = b
}
B.prototype = Object.create(A.prototype); //将父类原型作为新对象的原型,并将新对象赋值给子类原型
B.prototype.cb = function(){
alert(this.b)
}
/* 创建B类的实例 */
var b = new B('b')

父类和子类

继承的类称为子类,被继承的类称为父类,子类具有父类的全部属性和方法,但父类不能拥有子类的属性和方法

instanceof 运算符

instanceof 运算符用于检测构造函数的 prototype 属性是否在某个实例对象的原型链上,如果在,则返回 true,反之返回 false

语法:

1
对象 instanceof 构造函数

例:

1
2
3
4
5
6
7
8
9
10
function C(){}
function D(){}
var o = new C();
o instanceof C; //返回true
o instanceof D; //返回false
C.prototype instanceof Object; //返回true
D.prototype = new C(); //继承
var o2 = new D();
o2 instanceof D; //返回true
o2 instanceof C; //返回true