10.JavaScript 中的类和对象以及继承 可枚举属性的补充varobj{name:why,age:18}Object.defineProperty(obj,address,{value:北京,})// address 是不可枚举的但是属性在浏览器里面会展示成灰色的这个是为了方便我们调试console.log(obj);JavaScript 中的类和对象functionPerson(name,age){}varp1newPerson(why,18)在 JavaScript 中 Person 应该称之为一个构造函数从很多面向对象语言过来的开发者也习惯称之为类面向对象的特性-继承面向对象的三大特性封装、继承、多态封装将属性和方法封装到一个类中可以称之为封装的过程继承子类继承父类可以复用父类的功能并且对父类进行扩展多态不同的对象在执行时表现出不同的形态继承可以帮助我们将重复的代码和逻辑抽取到父类中子类只需要继承父类即可而不需要重复编写相同的代码JavaScript 原型链从一个对象上获取属性如果在当前对象中没有获取到就会去它的原型上面获取varobj{name:张三,age:18}// [[Get]]操作// 1. 先从obj对象中查找属性// 2. 如果obj对象中没有则从原型__proto__中查找console.log(obj.address);obj.__proto__{}obj.__proto__.__proto__{}obj.__proto__.__proto__.__proto__{address:北京}console.log(obj.address);Object 的原型顶层原型是什么?varobj{name:why}console.log(obj.address);// 到底是找到哪一层对象之后停止继续查找了呢console.log(obj.__proto__);// 字面量对象 obj 的原型是 [Object: null prototype] {}// [Object: null prototype] {} 就是顶层的原型console.log(obj.__proto__.__proto__);顶层原型来自哪里?varobj1{}// 创建一个对象varobj2newObject()// 创建一个对象// Object.prototype // 顶层原型console.log(obj2.__proto__Object.prototype);functionObject(){}// Object.prototype // 顶层原型 Object 的原型对象// Object.prototype.constructorconsole.log(Object.getOwnPropertyDescriptors(Object.prototype));[Object: null prototype] {}原型有什么特殊该对象有原型属性但是它的原型属性已经指向的是 null也就是已经是顶层原型了该对象上有很多默认的属性和方法原型链关系的内存图Object 是所有类的原型原型链最顶层的原型对象就是 Object 的原型对象functionPerson(){}console.log(Person.prototype.__proto__);console.log(Person.prototype.__proto__.__proto__);实现继承为什么需要有继承代码方法重复functionStudent(name,age){this.namename;this.ageage;}Student.prototype.runningfunction(){console.log(this.name is running);}Student.prototype.eatingfunction(){console.log(this.name is eating);}Student.prototype.studyingfunction(){console.log(this.name is studying);}functionTeacher(name,age,title){this.namename;this.ageage;this.titletitle;}Teacher.prototype.runningfunction(){console.log(this.name is running);}Teacher.prototype.eatingfunction(){console.log(this.name is eating);}Teacher.prototype.teachingfunction(){console.log(this.name is teaching);}原型链的继承方案// 父类公共属性和方法functionPerson(){this.namewhy;}Person.prototype.eatingfunction(){console.log(this.name eating);}// 子类特有属性和方法functionStudent(){this.sno111;}// 原型链继承varpnewPerson();Student.prototypep;Student.prototype.studingfunction(){console.log(this.name studying);}varstunewStudent();console.log(stu.name);stu.eating()原型链实现继承的弊端1、打印 stu 对象继承的属性是看不到的2、创建出来两个 stu 的对象varstu1newStudent();varstu2newStudent();// 获取引用修改引用中的值会相互影响stu1.friends.push(kobe);console.log(stu2.friends);// 直接修改对象上的属性是给本对象添加了一个新属性stu1.namekobe;console.log(stu2.name);3、在前面的实现类的过程中都没有传递参数varstu1newStudent(lilei,112);借用构造函数继承为了解决原型链继承中存在的问题开发人员提供了一种新的技术constructor stealing(借用构造函数、经典继承、伪造对象)借用继承的做法非常简单在子类型构造函数的内部调用父类型构造函数因为函数可以在任意时刻被调用因此通过apply()和call()方法也可以在新创建的对象上执行构造函数// 父类公共属性和方法functionPerson(name,age,friends){// this stuthis.namename;this.ageage;this.friendsfriends;}Person.prototype.eatingfunction(){console.log(this.name eating);}// 子类特有属性和方法functionStudent(name,age,friends){this.sno111;// 借用上面Person构造函数的执行过程Person.call(this,name,age,friends)}// 原型链继承varpnewPerson();Student.prototypep;Student.prototype.studingfunction(){console.log(this.name studying);}varstu1newStudent(why,18,[lilei]);varstu2newStudent(kobe,30,[james]);console.log(stu1);console.log(stu2);弊端1.Person函数至少被调用了两次2.stu原型对象上会多出来一些属性这些属性是没有必要的