JavaScript(七) 面向对象
JavaScript 支持面向对象的编程范式,面向对象是一种对现实世界理解和抽象的方法
所谓对象通常是指客观世界中的真实存在的事物,实物,实象。例如:一支笔、一本书、一只鸟
面向对象编程的三大特征:
封装:只关心入口和出口,而不关心过程
继承:指类与类之间的关系。如果两个类都有相同的属性或方法,那么可以让一个类继承于另类,这样就不需要在前者再次定义同样的属性或方法
多态:不同的对象可以定义具有相同名称的方法,方法是作用于所在的对象中。这种不同对象通过相同方法的调用实现各自行为的能力,被称之为多态
构造函数
构造函数又可称为对象模版或类型对象,通过构造函数我们可以创建特定类型的对象( 实例 )
构造函数和普通函数的定义方式是一样的,通常为了区分,构造函数的名称首字母要求大写
语法:
1 | function 名称([形参1, 形参2...形参N]){ |
创建实例
语法:
1 | var 对象名 = new 构造函数([实参1, 实参2...实参N]) |
例:
1 | /* 定义类 */ |
类
类是封装对象的属性和方法的载体。类定义了对象的特征,例如一辆汽车( 对象 )的名称、车型、重量、颜色等等都是这辆车的属性,而汽车的启动和停止则是这辆车的行为(方法)
类是面向对象编程中的一个基本的概念。实际上在 Js 中并没有类的概念❕,但 Js 通过构造函数和原型可以实现对类的支持
实例
类是对象的抽象,而对象是类的具体实例
通过类所创建的对象称为该类的实例,例如上面案例中的对象
xiaoming
和xiaomei
都是Person
类的实例this 关键字
this是Js引擎在执行时自动定义的变量,其值会指向一个对象
在全局作用域中访问this时,其值指向全局对象
window
(默认值)在函数作用域中访问this时,根据 函数调用方式 的不同,this会指向不同的对象
以方法调用时,this会指向该方法所在的对象
以构造函数调用(
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运算符来进行对象实例化(根据类创建对象的过程)
实例化的过程:
- 创建一个空对象(即
{}
)- 执行构造函数,执行前会先
- 将变量
this
的值指向步骤1中所创建的那个对象- 并向对象中添加属性
__proto__
,将该属性的值指向构造函数的原型对象(prototype
)
- 构造函数执行完后,返回实例对象( 没有手动返回了一个对象的情况下,为步骤1中所创建的那个对象 )
call、apply、bind 方法
call
、apply
、bind
是 Function类型对象( 函数 )中的方法,通过 call()
、apply()
、bind()
方法可以改变函数作用域中变量 this 的值
call()
的语法:
1 | 函数.call(this的值, 实参1, 实参2...实参N) |
apply()
的语法:
1 | 函数.apply(this的值, [实参列表]) |
bind()
的语法:
1 | 函数.bind(this的值, 实参1, 实参2...实参N) |
例:
1 | /* 例1 */ |
call、apply、bind 的区别
call
、apply
、bind
方法都包含一个对象( 严格模式下除外 )作为该方法的第1个参数,其后的参数会作为函数的实参。但在传参时,apply()
方法需要将实参以数组的形式传递至函数
call
和apply
方法会以指定的 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 | var obj = new Object(); |
原型链
每个实例对象都有一个私有属性(称之为
__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原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象
当从对象中访问一个属性时会先在自身对象中查找
- 如果没找到,则沿着
__proto__
这条链向上查找- 如果依然没找到,则返回 undefined
Object.getPrototypeOf()
描述:获取指定对象的原型
语法:
1 Object.getPrototypeOf(对象)Object.setPrototypeOf()
描述:设置指定对象的原型
语法:
1 Object.setPrototypeOf(对象, 原型)hasOwnProperty()
描述:检测对象中的属性是否存在( 不包括原型中的属性,检测原型中的属性可以用 in 运算符 ),存在则返回 true,反之则为 false
语法:
1 对象.hasOwnProperty(属性名) //属性名为一个字符串
继承
JavaScript 是基于原型实现的继承,和其它传统的基于类的语言( 如 Java 或 C++ )相比,Js 显得比较独特
原型链继承
核心:将父类实例作为子类原型
例:
1 | /* 定义A类 */ |
PS:原型链虽然很强大,用它可以实现 JavaScript 中的继承,但同时也存在着一些问题。原型链继承实际上是在多个构造函数或对象之间共享属性和方法,这种方法在创建子类的对象时,不能向父类的构造函数传递任何参数
借用构造函数继承
核心:借用父类构造函数来增强子类实例,等于复制父类实例的属性给子类
例:
1 | /* 定义A类 */ |
PS:借用构造函数继承就是通过
apply()
或call()
方法在子类构造函数中调用父类的构造函数,并将父类和子类的 this 绑定在一起,这种方式可以向父类传递参数,但不能实现方法的复用
组合式继承
核心:通过调用父类构造函数来实现向父类构造函数传递参数,然后通过将父类实例作为子类原型来实现方法复用
例:
1 | /* 定义A类 */ |
PS:组合继承是将原型链继承和借用构造函数的技术组合在一起,发挥二者长处的一种继承方式,这样,既通过在原型上定义方法实现了方法的复用,又可以保证每个对象都有自己的专有属性,但这种方法,会调用2次父类的构造函数,因此会存在一份多余的实例属性
组合寄生式继承
核心:通过调用父类构造函数来实现向父类构造函数传递参数,然后通过将父类原型作为新对象的原型,并将新对象赋值给子类原型来实现方法复用,并且避免了多余的实例属性
例:
1 | /* 定义A类 */ |
父类和子类
继承的类称为子类,被继承的类称为父类,子类具有父类的全部属性和方法,但父类不能拥有子类的属性和方法
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