JavaScript的面向对象
概述
在典型的OOP语言中(如JAVA),都存在类的概念,类是对象的模版,对象是类的实例。当在ES6之前,JS并没有引入类的概念。
在ES6之前,对象不是基于类创建的,而是用一种成为__构建函数__的特殊函数来定义对象和它们的特征。
创建对象的三种方式
对象字面量(无法复用)
var obj = {};
new Object(无法复用)
var obj = new Object();
自定义构造函数
function Star(name, age) {
this.name = name;
this.age = age;
this.sing = function () {
console.log('我会唱歌');
}
}
let ldh = new Star('刘德华', 18);
ldh.sing();
let zxy = new Star('张学友', 20);
zxy.sing();
__构成函数__是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与new一起使用。我们可以把对象总一些公共的属性和方法抽取出来,然后封装到这个函数里面。
通过对构造函数执行new命令时会做的四件事情:
- 在内存总创建一个新的空对象。
- 让this指向这个新对象。
- 执行构造函数里的代码,给这个新对象添加属性和方法。
- 返回这个对象(只是构造函数里面不需要写return)。
静态成员和实例成员:
- 静态成员:在构造函数上添加的成员称为静态成员。只能由构造函数本身来访问。
function Star(name, age) {
this.name = name;
this.age = age;
this.sing = function () {
console.log('我会唱歌');
}
}
let ldh = new Star('刘德华', 18);
// 创建一个变量名为gender的静态成员
Star.gender = '男';
console.log(Star.gender); // 男
console.log(ldh.gender); // undefined
- 实例成员:在构造函数内部创建的对象成员称为实例成员,只能通过实例化的对象来访问。
自定义构造函数创建对象的问题
构造函数方法虽然好用,但是存在浪费内存空间的问题。对于通过同一个构造函数创建出的实例对象,其中的复杂数据类型都会再开辟一块内存空间来存放,而不会复用。通过使用构造函数原型prototype即可解决以上问题。

构造函数原型prototype
构造函数通过原型分配的函数是对所有对象__共享的__(内存复用)。
JavaScript规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个对象总的所有的属性和方法,都会被构造函数说拥有。
我们可以把哪些不变的方法,直接定义在prototype对象上,这样所有的实例对象就可以共享这些方法了。
function Star(name, age) {
this.name = name;
this.age = age;
// this.sing = function () {
// console.log('我会唱歌');
// }
}
// prototype是函数才会有的属性
// 向Star构造函数的原型对象中添加方法
Star.prototype.sing = function(){
console.log('我会唱歌');
}
let ldh = new Star('刘德华', 18);
ldh.sing();
let zxy = new Star('张学友', 20);
zxy.sing();
console.log(ldh.sing === zxy.sing) // true
对象原型 __proto__
对象都会有一个属性__proto__指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有__proto__ 原型的存在。
function Star(name, age) {
this.name = name;
this.age = age;
this.sing = function () {
console.log('wowowo我会唱歌');
}
}
// 向Star构造函数的原型对象中添加方法
Star.prototype.sing = function(){
console.log('我会唱歌');
}
let ldh = new Star('刘德华', 18);
ldh.sing(); // wowowo我会唱
console.log(ldh.__proto__ === Star.prototype); // true
实例对象方法的查找规则:首先会先看当前实例对象的内部是否存在xx方法,如果有就去执行这个对象上的xx方法。如果对象上没有定义这个方法,因为实例对象上有__proto__的存在,就去构造函数的prototype对象中查找xx方法。

__proto__对象原型和原型对象prototype是等价的。__proto__对象原型的意义就在于为对象的查找机制提供了一个方向,或者说一条路线。但是它是一个非标准的属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象prototype。
contructor 构造函数
对象原型( __proto__)和构造函数(prototype)原型对象里面都有一个consgructor的属性,constructor我们称之为构造函数,因为它指回构造函数本身。
constructor主要用于记录该对象应用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
__(很多情况下,我们需要手动利用constructor)__这个属性指回原来的构造函数。
function Star(name, age) {
this.name = name;
this.age = age;
}
Star.prototype = {
construct: Star,
sing: function () {
console.log('我会唱歌儿');
},
movie: function () {
console.log('我会拍电影');
}
}
let ldh = new Star('刘德华', 18);
let zxy = new Star('张学友', 20);
console.log(Star.prototype)
ldh.sing();
zxy.movie();
当一个函数被创建时,它的prototype属性也被创建,且该原型对象的construct属性指向该函数,当使用对象字面量形式改写原型对象Star.prototype时,则该constructor指向的是Object,为了避免这一点,需要手动改写原型对象。即通过手动设置construct的属性。
构造函数、实例、原型对象(prototype)三者之间的关系

原型链

JavaScript的成员查找机制:
- 当访问一个对象的属性或方法时,首先查找这个__对象自身__中属否存在该属性。
- 如果没有就查找它的原型(也就是实例对象
__proto__指向的__prototype原型对象__)。 - 如果依旧没有找到就继续去向上查找原型对象的原型(Object的原型对象)。
- 以此类推一直找到Object为止(null)。
原型对象中this指向
function Star(name, age) {
// 在构造函数中的this指向的是对象实例
this.name = name;
this.age = age;
}
let that;
// Star构造函数的原型对象
Star.prototype.sing = function () {
that = this;
//this指向最终的调用对象
console.log('我会唱歌');
}
var ldh = new Star('刘德华', 18);
console.log(ldh.sing());
console.log(that);
- 在构造函数中,里面this指向的是实例对象。
- 原型对象函数里的this,指向的是最终调用该函数的实例对象。
扩展内置对象的方法
// 通过构造函数的原型对象扩展原有的功能
Array.prototype.sum = function () {
let sum = 0;
for (let i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
}
// 不允许采用这种方式书写
// Array.prototype = {
// constructor: Array,
// sum: function () {
// let sum = 0;
// for (let i = 0; i < this.length; i++) {
// sum += this[i];
// }
// return sum;
// }
// }
let nums = [1, 2, 3, 4];
console.log(nums.sum());
继承
ES6之前没有提供extends继承。我们可以通过构造函数+原型对象模拟实现继承,这种方式被称为组合继承。
call
调用这个函数,并自定义函数运行时的this指向
func.call(thisArg, arg1, arg2, ...)
- thsiArg: 当前调用函数的this所要指向的对象。
- arg1,arg2: 向函数中传递的参数。
借用父构造函数继承属性
// 1. 父构造函数
function Father(uname, age){
this.uname = uname;
this.age = age;
}
// 2. 子构造函数
function Son(uname, age, school) {
this.school = school;
// 当通过call方法调用Father构造函数的时候,运行时,Father中的this指向的是son,那么对uname和age的赋值就是作用在son上的。
Father.call(this, uname, age);
}
let son = new Son('刘德华', 18, "不知道哦");
console.log(son.uname);
console.log(son.age);
console.log(son.school);
真的是继承么?
继承意味着复制,然而JavaScript默认不会复制对象的属性,相反,JavaScript只是在两个对象之间创建了一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承不如说是委托准确些。