一,面向对象编程
1.在面向对象编程有几个基本的概念:
- 类
Class
:定义对象抽象特点,包含它的属性和方法 - 对象
Object
:类的实例,通过new
生成 - 封装:对数据进行封装
- 继承:子类继承父类
- 多态:由继承而产生了相关的不同的类,对同一个方法可以有不同的响应
- 存取器:用以改变属性的读取和赋值行为
- 修饰符:修饰符是一些关键字,用于限定成员或类型的性质
- 抽象类
- 接口
2.然而直到ES5
没有都没有类Class
的概念,且在ES5
中是通过原型prototype
来实现面向对象编程
二,原型和原型链
1.在js
中,每个实例对象都内置了一个__proto__
属性,每个函数都内置了一个prototype
属性,指向原型对象,且原型本身就是一个对象。
2.当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去 _ proto _
里找这个属性,这个 _ proto _
又会有自己的 _ proto _
,于是就这样一直找下去,直到null
,也就是我们平时所说的原型链的概念。对于一个数组,其原型和形成的原型链如下图所示:
3.关键字 instanceof
用于判断构造函数的prototype
是否出现在实例对象的原型链上。
function C() {}
let obj = new C();
obj instanceof C; //true
obj instanceof Object; //true
三,面向对象编程方法
1.在es5
中没有class
的概念,因此面向对象是使用构造函数来实现。
2.为了区分普通函数和构造函数,通常构造函数首字母大写。
function Animal(sound) {
this.sound = sound;
}
3.构造函数通常没有返回值,或者设置成返回当前实例对象return this
。如果我们给js
的构造函数设置返回值需要注意:
(1)当return
一个基本数据类型,new
一个实例返回实例对象,return
等于无效。
(2)而return
一个引用数据类型时,new
一个实例返回的就是该引用类型。
function Animal(sound) {
this.sound = sound;
return 10;
}
new Animal("hh"); //{sound:'hh',__proto__:Animal.prototype}
function Animal(sound) {
this.sound = sound;
return { K: 1 };
}
new Animal("hh"); // {K:1}
4.可以看出,构造函数就只是一个普通函数,因此他也可以作为普通函数使用,总之,在es6
之前,类都是很不强的概念,
5.我们使用new
来创建一个实例对象,此时函数内部的this
指向当前对象。如何作为普通函数使用那么内部的this
就要视情况而定了。
6.当我们new
一个实例对象时具体做了什么?对于let a = new A()
(1)首先创建了一个空对象:let a={}
(2)让a
的原型指向构造函数的原型:a.__proto__=A.prototype
(3)调用构造函数,使构造函数的this
指向这个对象:A.call(a)
这样就创建好了一个实例对象。根据这个过程我们可以自己实现一个new
Person.prototype.introduce = function () {
console.log(`my name is ${this.name}`);
};
function myNew(fn, ...rest) {
let obj = {}; //创建空对象
obj.__proto__ = fn.prototype; //改变对象原型
fn.apply(obj, rest); // 调用构造函数,使构造函数内部this指向obj
return obj;
}
let myMax = myNew(Person, "max", "female");
let max = new Person("max", "female");
myMax.introduce();
console.log("myNew", myMax);
console.log("new", max);
运行结果:可以发现两个对象完全相同。
5.对于一个构造函数,其原型链如下图所示:
四,在ES5
中完整的一个基本类
1.注意,实例属性不在原型上,实例方法定义在原型上
function Animal(type) {
this.type = type; //实例属性
}
Animal.prototype.kindOf = function () {
console.log("i am " + this.type);
}; //实例方法
Animal.intelligence = "below human"; //静态属性
Animal.say = function () {
console.log("not human lang");
}; //静态方法
五,js
面向对象继承的实现。
1.原型链继承:
无法向父类传参,
父类的引用实例属性被所有实例共享
function Person(name,sex)
{
this.obj={type:"引用属性值"}
this.name=name;
this.sex=sex;
}
Person.prototype.introduce=function()
{
console.log(`my name is ${this.name}`);
}
American.prototype=new Person()
function American(skin)
{
this.skin=skin
}
let p = new American("white") //创建一个实例无法向父类传参
let p1 = new American("white")
p.obj.type="在p中修改obj"
console.log(p1.obj) // {type: "在p中修改obj"} // 父类的引用属性被子类实例共享。
2.借用构造函数:
能够为父类传递参数,且继承了父类构造函数里的实例属性,和实例方法。
缺点:
无法使用父类在原型上定义的一切,包括方法,(因为父类的原型并没有在该创建对象的原型链上)
3.组合式继承
使用call和apply在子类调用父类的构造函数传参,使用原型链来实现复用方法
父类被调用了两次
4.寄生组合式继承
寄生组合式继承是最好的继承,解决了组合式继承调用两次父构造函数的问题。
function object(obj) {
function F() {}
F.prototype = obj.prototype;
return new F();
}
function prototypeInherit(SuperClass, Sub) {
prototype = object(superClass);
prototype.constructor = sub;
Sub.prototype = prototype;
}
function Sub(name, age) {
SuperClass.call(this, name); //重要的一步,相当于es6 class 的 super
this.age = age;
}
注意,子类原型方法的定义只能在继承父类之后执行,否则跟没写一样,会在继承的过程中被覆盖。