【JavaScript】10-深入面向对象
本文介绍JS中深入面向对象的内容。
目录
1. 编程思想
1.1 面向过程
1.2 面向对象oop
2. 构造函数
3. 原型
3.1 原型对象
3.2 constructor属性
3.3 对象原型 __proto__
3.4 原型继承
3.5 原型链
1. 编程思想
1.1 面向过程
分析出解决问题的步骤,用函数将这些步骤一步步实现,使用的时候再一个个依次调用。
1.2 面向对象oop
把事务分解成一个个对象,然后由对象之间分工合作,相当于以功能划分问题,而非步骤。
每一个对象都是功能中心,具有明确分工。
面向对象具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件。
面向对象的特点:封装性、继承性、多态性。
2. 构造函数
封装是面向对象思想中比较重要的一部分,JS面向对象可以通过构造函数来实现对函数的封装。
同样的将变量和函数组合到了一起并能通过this实现数据的共享,不同的是借助构造函数创建出来的实例对象之间彼此不影响。
<script>
// 构造函数 公共的属性和方法封装到 Star 构造函数里
function Star(uname, age){
// this 指向实例对象
this.uname = uname;
this.age = age;
this.sing = function(){
console.log('唱歌');
}
}
// 创建实例对象 默认返回
const c1 = new Star('LIUDEHUA',55);
const c2 = new Star('ZHANGXUEYOU',58);
console.log(c1.sing === c2.sing); // false
// 每创建一次实例对象就会开辟一个新的空间
</script>
每创建一次实例对象就会开辟一个新的空间
所以存在浪费内存的问题
我们希望所有对象使用同一个函数,这样比较节省内存,如何做?
—— 原型
3. 原型
JS规定每一个构造函数都有一个 prototype 属性,指向另一个对象,,所以我们也称为原型对象。
console.log(Star.prototype);
// {constructor: ƒ}
// constructor:
// ƒ Star(uname, age)
// [[Prototype]]: Object
这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存。
我们可以把那些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法。且构造函数和原型对象中的this都指向实例化的对象。
<script>
// 构造函数 公共的属性和方法封装到 Star 构造函数里
function Star(uname, age){
// this 指向实例对象
this.uname = uname;
this.age = age;
}
// Star的原型对象的sing方法
Star.prototype.sing = function(){
console.log('唱歌');
}
// 创建实例对象 默认返回
const c1 = new Star('LIUDEHUA',55);
const c2 = new Star('ZHANGXUEYOU',58);
c1.sing();
c2.sing();
console.log(c1.sing === c2.sing); // true
// 不管实例化多少对象,都只调用同一个方法
</script>
原型对象就类似一个固定的模型,任何对象都可以用其编写公共的方法并调用。不浪费空间。
注意:
公共的属性写在构造方法中,用this实例化
公共的方法写在原型对象中,用prototype
3.1 原型对象
原型对象和构造函数里的this的指向
都是同一个对象
<script>
let a;
let b;
function Star(uname){
a = this;
console.log(this);
this.uname = uname; // this指向创建的实例
}
Star.prototype.sing = function(){
b = this;
console.log('唱歌');
}
// 实例对象 c1
const c1 = new Star('liudehua');
// 构造函数里的this指向的就是实例对象c1
console.log(a === c1); //true
c1.sing(); // c1调用了sing方法
// 原型对象里的this指向的还是实例对象c1
console.log(b === c1); //true
</script>
练习
给数组拓展方法
需求:
1. 给数组拓展求最大值方法和求和方法
比如 const arr = [1,2,3]
arr.reverse() 后 [3,2,1]
拓展完毕后:
arr.sum() 后 结果为6
分析:
给数组的原型挂上方法,达到拓展方法的目的
<script>
// 自己定义数组拓展方法 求和 最大值
// 1.定义的方法 任何数组的实例对象都能使用
// 2.自定义的方法写到 数组.prototype上
// Array.prototype
const arr = [1,2,3];
// 1.最大值
Array.prototype.max = function(){
// return Math.max(...arr); // 展开运算符
return Math.max(...this);
// 原型函数里的this指向 实例对象 arr
}
// 2.最小值
Array.prototype.min = function(){
return Math.min(...this);
// 原型函数里的this指向 实例对象 arr
}
console.log(arr.max()); // arr调用的max() this指向的arr
console.log([2,4,6].max());
console.log([2,9].min());
// 3.求和
Array.prototype.sum = function(){
// reduce 方法
return this.reduce((prev,item) => prev + item , 0); // 累加,起始值
}
console.log([1,2,3].sum());
</script>
3.2 constructor属性
每个原型对象都有constructor属性
该属性指向该原型对象的构造函数
<script>
// constructor
function Star(){
}
const c1 = new Star('LIUDEHUA');
// console.log(Star.prototype);
// 得到构造函数的原型对象 可以看到默认都有constructor属性
console.log(Star.prototype.constructor === Star); // true
</script>
但是如果对prototype这个原型对象进行重写,可以覆盖原来的constructor
<script>
// constructor
function Star(){
}
console.log(Star.prototype);
Star.prototype = {
sing: function(){
console.log('唱歌');
},
dance: function(){
console.log('跳舞')
},
}
// 这和Star.prototype.sing还不一样,上面是全覆盖,这个只是追加而已
console.log(Star.prototype);
</script>
如图:
也就是原型对象的constructor属性被覆盖了 无法指回原来的。
那该怎么做呢?
Star.prototype = {
constructor:Star, // 自己再加回去
sing: function(){
console.log('唱歌');
},
dance: function(){
console.log('跳舞')
},
}
3.3 对象原型 __proto__
构造函数可以创建实例对象 new XXX()
构造函数有一个原型对象 prototype,一些共享的属性和方法可以放在这个原型对象上
但是为什么实例对象可以访问原型对象里的属性和方法?
是因为有对象原型。
__proto__ (两个下划线)
每个对象都会有一个属性 __proto__指向构造函数的 prototype 原型对象
注意:
__proto__ 是JS的非标准属性 只读无法修改
[[prototype]]和__proto__意义相同
用来表明当前实例对象指向哪个原型对象prototype
即 对象原型 指向 原型对象
<script>
function Star(){
}
const ldh = new Star();
console.log(ldh.__proto__);
// 对象原型 __proto__ 指向 该构造函数的原型对象
console.log(ldh.__proto__ === Star.prototype);
</script>
__proto__对象原型里也有一个constructor属性,指向创建该实例对象的构造函数
// 对象原型也有constructor指向构造函数 Star
console.log(ldh.__proto__.constructor === Star); // true
3.4 原型继承
继承是面向对象的一个特征,通过继承进一步提升代码封装的程度,JS中大多是借助原型对象实现继承的特性。
<script>
// 女人 构造函数
function Woman(){
this.eyes = 2;
this.head = 1;
}
const red = new Woman();
console.log(red);
// 男人 构造函数
function Man(){
this.eyes = 2;
this.head = 1;
}
const pink = new Man();
console.log(pink);
// 男人和女人都有人的特性
// 继续抽取封装
</script>
开始抽取封装
<script>
// 人 公共的部分放到原型上可以使用
// 下面可以通过原型继承Person
const Person = {
eyes : 2,
head : 1
}
// 女人 构造函数 继承Person
function Woman(){
}
Woman.prototype = Person;
// 但是不要忘了指回原来的构造函数
Woman.prototype.constructor = Woman;
const red = new Woman();
console.log(red);
console.log(Woman.prototype); // 属性和constructor
// 男人 构造函数 继承Person
function Man(){
}
Man.prototype = Person;
// 但是不要忘了指回原来的构造函数
Man.prototype.constructor = Man;
const pink = new Man();
console.log(pink);
console.log(Man.prototype);
</script>
现在给女人部分添加方法
// 女人 构造函数 继承Person
function Woman(){
}
Woman.prototype = Person;
// 但是不要忘了指回原来的构造函数
Woman.prototype.constructor = Woman;
// 给女人添加一个方法 生育
Woman.prototype.baby = function(){
console.log('生孩子');
}
const red = new Woman();
console.log(red);
console.log(Woman.prototype); // 属性和constructor
如图可以看到男人也有了此方法
男人和女人都同时使用了同一个对象,根据引用类型的特点,他们指向同一个对象,修改一个就都会影响,所以可以选择让男人和女人继承不同的person对象即可解决。
const Person1 = {
eyes : 2,
head : 1
}
const Person2 = {
eyes : 2,
head : 1
}
后面的继承也修改对象的person即可
更好的是 直接用构造函数 他们new出来的对象是不一样的
<script>
// 通过构造函数 new 出来的对象 结构一样,但是对象不一样
function Person(){
this.eyes = 2;
this.head = 1;
}
console.log(new Person);
// new
// 女人 构造函数 继承Person
function Woman(){
}
Woman.prototype = new Person(); // 创建一个对象
// 但是不要忘了指回原来的构造函数
Woman.prototype.constructor = Woman;
// 给女人添加一个方法 生育
Woman.prototype.baby = function(){
console.log('生孩子');
}
const red = new Woman();
console.log(red);
console.log(Woman.prototype); // 属性和constructor
// 男人 构造函数 继承Person
function Man(){
}
Man.prototype = new Person(); // 创建一个对象
// 但是不要忘了指回原来的构造函数
Man.prototype.constructor = Man;
const pink = new Man();
console.log(pink);
console.log(Man.prototype);
</script>
现在给 女人和男人 各自添加方法互不影响
3.5 原型链
<script>
function Person(){
}
const ldh = new Person(); // 创建实例对象
console.log(ldh.__proto__ === Person.prototype); //true
console.log(Person.prototype); // 里面还嵌套了一层 prototype
console.log(Person.prototype.__proto__ === Object.prototype); // true
// Object.prototype 是所有原型对象的对象原型
// 原型对象也是一个对象 该对象的原型 是 Object的原型对象
// 还是满足 对象原型 指向 原型对象
console.log(Object.prototype.__proto__); // null
</script>
如图可以看到一个原型链
ldh.__proto__ —— Person.prototype.__proto —— Object.prototype.__proto —— null
只要是对象 就有 __proto__ 指向原型对象
只要是原型对象就有 constructor 指回构造函数
原型链 — 查找规则:
instanceof 运算符:
console.log(ldh instanceof Person); // ldh是Person创建出来的 true
console.log(ldh instanceof Object); // true
console.log(ldh instanceof Array); // false
console.log(Array instanceof Object); // true
本文介绍JS中深入面向对象的内容。