湖南怀化/北京中文seo
本文介绍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('唱歌');}// 实例对象 c1const c1 = new Star('liudehua');// 构造函数里的this指向的就是实例对象c1console.log(a === c1); //truec1.sing(); // c1调用了sing方法// 原型对象里的this指向的还是实例对象c1console.log(b === c1); //true</script>
练习
给数组拓展方法
需求:
1. 给数组拓展求最大值方法和求和方法
比如 const arr = [1,2,3]
arr.reverse() 后 [3,2,1]
拓展完毕后:
arr.sum() 后 结果为6
分析:
给数组的原型挂上方法,达到拓展方法的目的
<script>// 自己定义数组拓展方法 求和 最大值// 1.定义的方法 任何数组的实例对象都能使用// 2.自定义的方法写到 数组.prototype上// Array.prototypeconst 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指向的arrconsole.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指向构造函数 Starconsole.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>// 人 公共的部分放到原型上可以使用 // 下面可以通过原型继承Personconst Person = {eyes : 2,head : 1} // 女人 构造函数 继承Personfunction Woman(){}Woman.prototype = Person;// 但是不要忘了指回原来的构造函数Woman.prototype.constructor = Woman;const red = new Woman();console.log(red);console.log(Woman.prototype); // 属性和constructor// 男人 构造函数 继承Personfunction Man(){}Man.prototype = Person;// 但是不要忘了指回原来的构造函数Man.prototype.constructor = Man;const pink = new Man();console.log(pink);console.log(Man.prototype);</script>
现在给女人部分添加方法
// 女人 构造函数 继承Personfunction 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// 女人 构造函数 继承Personfunction 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// 男人 构造函数 继承Personfunction 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); //trueconsole.log(Person.prototype); // 里面还嵌套了一层 prototypeconsole.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创建出来的 trueconsole.log(ldh instanceof Object); // trueconsole.log(ldh instanceof Array); // falseconsole.log(Array instanceof Object); // true
本文介绍JS中深入面向对象的内容。