一、面向对象介绍

1.1、面向对象和面向过程区分

  • pop面向过程
    • 按流程来按步骤一步步实现,重心放在完成的每个过程上
    • 性能比面向对象高,适合跟硬件联系很紧密的东西,但是不易维护、不易复用、不易扩展
  • oop面向对象
    • 把事务分解成一个一个对象,每个对象都是一个功能中心,明确分工。以功能来划分问题,而不是步骤;
      以封装的思想,重心放在解决问题需要的对象身上,然后通过对对象的操作来完成相应的功能。
    • 性能比面向过程低,更耗资源,但是易维护、易复用、易扩展

1.2、面向对象三大特性(了解)

1.2.1、封装

定义:使用对象封装一些变量和函数

作用:复用和信息隐藏

详解:封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

1.2.2、继承

定义:一个类获取另外一个类属性和方法的一种方式
作用:代码复用
详解:继承,就是可以使用已创建好的类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。

  • 通过继承创建的新类称为“子类”或“派生类”。
  • 被继承的类称为“基类”、“父类”或“超类”。
  • 继承的过程,就是从一般到特殊的过程。

1.2.3、多态

定义:同一个操作,作用于不用的对象,会有不同的行为
作用:具有可拓展性
详解:多态首先是建立在继承的基础上的,先有继承才能有多态。多态是指不同的子类在继承父类后分别都重写覆盖了父类的方法,即父类同一个方法,在继承的子类中表现出不同的形式。js天生就具备多态的特性(弱类型语言)

二、创建对象

  1. 简单工厂创建对象
  2. 自定义构造函数创建对象

2.1、字面量方式创建对象

直接使用字面量方式创建对象比较方便,以键值对{ key:value }的格式来定义数据

  1. // 字面量创建对象
  2. let obj = { // 对象封装的好处: 数据安全,全局容易被修改,对象内部不容易被修改(更多的是一种解决方案,一种编程思想)
  3. name:'张三',
  4. age:18,
  5. ff:function(){
  6. console.log('方法');
  7. }
  8. }
  9. // 对象成员的删除
  10. delete obj.name; // 不建议 有可能这个对象你同事创建的
  11. obj.name = null; // 建议 初始化为null

优点:方便直观,可以直接访问里面的属性方法;
缺点:只能创建单个对象,不能批量创建

2.2、内置构造函数创建对象

使用new关键字+内置的构造函数创建对象

  1. let obj = new Object();
  2. obj.name = '张三';
  3. obj.age = '18';
  4. obj.ff = function () {
  5. console.log('方法');
  6. }
  7. obj.ff();

缺点:只能创建单个对象,不能批量创建

2.3、简单工厂函数创建对象

通过函数封装的思想将重复代码抽取出来封装进函数内

  1. function hs(name, age) {
  2. // 1. 内部创建空对象
  3. let obj = new Object();
  4. // 2. 给空对象添加属性
  5. obj.name = name;
  6. obj.age = age;
  7. // 3. 返回内部创建的对象
  8. return obj;
  9. }
  10. // 可以不用new
  11. var hs1 = hs('张三', 18);
  12. var hs2 = hs('李四', 22);
  13. var dog = hs('狗', 5);
  14. console.log(hs1,hs2);
  15. console.log(dog);

优点:批量创建对象

缺点:无法判断对象的类型(人狗不分),console.log打印出来都是Object类型

2.4、自定义构造函数创建对象

  1. // 面试题: 说出构造函数是如何创建对象的??? abcd
  2. // 也叫作: new的过程,实例化的过程
  3. function Person(name,age){
  4. // a. 内部会默认创建空对象 ---------- var obj = new Object()
  5. // b. 给空对象添加属性并赋构造函数的原型 --------- obj.__proto__ = Person.prototype
  6. // c. 将创建好的空对象赋值给this ---- this = obj
  7. // d. 给this添加成员 ------------- this.xxx = xxx
  8. this.name = name;
  9. this.age = age;
  10. // e. 自动返回this --------------- return this
  11. }
  12. // 必须new创建实例对象 步骤abcde才生效
  13. var p1 = new Person('张三', 18);
  14. var p2 = new Person('李四', 22);

优点:能判断对象的类型,console.log打印出来是Person类型

2.5、工厂函数和自定义构造函数的区别

  1. 构造函数名的首字母要求大写
  2. 在函数中,不需要手动创建对象进行数据封装,会自动创建对象并封装数据
  3. 在函数最后,不需要手动返回创建好的对象,会自动返回
  4. 构造函数一样可以直接调用,此时内部的this执行window,这种方式不太安全,有可能会在函数内部修改当前的全局变量,不建议使用,而且这样做也不能创建对象,必须要搭配new关键字一起使用才能创建对象
  • 面试题
  1. var Fun = function(){
  2. this.name="pp";
  3. // return "j"; // 构造函数返回基本类型 不改变实例的指向 返回无效
  4. return {name:"jj"};// 构造函数返回引用地址 实例=返回的对象
  5. }
  6. var p = new Fun();
  7. console.log(p.name); // jj
  8. // return "j";返回的是pp

三、构造器属性

3.1、类和对象区分

  • 构造器(类)
    • 泛指一类事物
    • 把多个对象相同的部分抽象出来,成为一个类,就是一个函数,构造函数
      js中没有严格的类的概念 一定要说有———-构造函数(模拟面向对象语言)
  • 对象
    • 特质某一个具体事物
    • 使用构造函数new创造出来的对象

3.2、constructor属性和instanceof关键字

  • 需求:定义Person和Dog类,并各自创建出对象,判断对象是否是Person或Dog类型
  1. constructor属性
    constructor 制造者的意思:可以获取到创建对象使用的构造器函数(类)。 ```javascript function Person(name) { this.name = name; } function Dog(name) { this.name = name; } let p = new Person(‘张三’); let d = new Dog(‘狗’);

// 实例化对象的原型对象prototype的constructor原型 if(p.proto.constructor === Person){ console.log(“p是Person对象”); } if(d.proto.constructor === Dog){ console.log(“d是Dog对象”); }

  1. 2. **instanceof关键字**<br />instanceof 实例的意思:判断a(实例对象)的**类型**是不是b(构造函数类) 返回布尔值
  2. ```javascript
  3. function Person(name) {
  4. this.name = name;
  5. }
  6. function Dog(name) {
  7. this.name = name;
  8. }
  9. let p = new Person('张三');
  10. let d = new Dog('狗');
  11. console.log(p instanceof Person);//true
  12. console.log(d instanceof Person);//false

3.3、构造函数中的this指向

  • 构造函数中的this指向实例对象
  1. var _this = null;
  2. function Person(name, age) {
  3. console.log(this); // 构造函数中的this指向创建出来的新对象(也就是实例对象)
  4. _this = this;
  5. this.name = name;
  6. this.age = age;
  7. this.ff = function () {
  8. console.log(this);
  9. }
  10. }
  11. let p = new Person('张三', 18);
  12. let p2 = new Person('李四', 22);
  13. p.ff(); // 谁调用this指向谁
  14. console.log(_this);// 指向p2,p被覆盖了(第二次创建p2,_this又执行了一次)

四、原型对象

4.1、自定义构造函数存在的问题

问题:对象1和对象2的ff方法是同一个内存地址吗?

  1. function Star(uname) {
  2. this.uname = uname;
  3. this.ff = function () {
  4. console.log('方法');
  5. }
  6. }
  7. let dx1 = new Star('张三');
  8. // console.log(Star.newUname = '新张三'); // 静态成员就是在构造函数本身上添加的成员
  9. // console.log(dx1.uname); // 实例成员就是在构造函数内部this添加的成员,只能通过对象的实例化来访问
  10. // console.log(Star.uname); // 不能通过构造函数来访问实例成员
  11. // console.log(dx1.newUname); // 不能通过实例化对象来访问静态成员
  12. let dx2 = new Star('李四');
  13. console.log(dx1.ff===dx2.ff); // 输出false (new两个实例化对象里面共有的方法都会重新开辟内存空间,代码一样,浪费)

上面创建的p对象的内存结构图:

结论:从内存资源分配考虑,我们无需为每个对象创建并分配一份新的函数对象(完全相同),这种函数大家最好共享同一份。

解决办法:那么我们可以把函数单独拿出来,定义全局,函数名做方法对象:

  1. function ff(){
  2. console.log('方法');
  3. }
  4. function Star(name) {
  5. this.name = name
  6. this.say = say; // 每一次都赋值 全局函数ff 的内存地址
  7. }
  8. let dx1 = new Star('张三');
  9. let dx2 = new Star('李四');
  10. console.log(dx1.ff===dx2.ff); // 输出true 减小的内存的浪费

结论:解决方法共享的目的是达到了,但是又产生了新的问题,我们面向对象编程的目的是为了减少全局变量,而这种写法又增加了全局变量,与我们编程思想产生了冲突。

完美的解决办法:这个时候,我们需要在构造函数上想办法,在构造函数身上开辟一个区域,来存放我们的公共方法——原型对象

4.2、面向对象中的核心概念

  • 构造函数:Star,和new关键字一起创建对象
    • 静态成员:直接添加在构造函数上的属性和方法,只能使用构造函数才能访问
  • 原型对象:Star.prototype
  • 实例化:由构造器创建实例对象new的过程称之为实例化
  • 实例对象:由构造器创建出来的对象称之为实例对象
    • 实例成员:实例对象上的属性和方法,name,age,只能当前实例对象才能访问
    • 原型成员:原型对象上的属性和方法,say(),使用该原型对象对应构造器创建出来的所有实例对象都能访问
  1. function Star(uname) { // 1.构造函数 ---- 构造器(类) Star
  2. this.uname = uname; // 5.实例成员 uname
  3. }
  4. // 2.构造函数的原型对象prototype (所有实例对象共享的,公共的方法放原型对象里面共享)
  5. Star.prototype.ff = function () { // 6.原型成员 ff
  6. console.log('prototype原型对象里面的方法');
  7. }
  8. let dx1 = new Star('张三'); // 3.实例对象: 构造函数创建出来的新对象 dx1
  9. let dx2 = new Star('李四'); // 4. 实例化: new的过程 构造函数创建实例对象的过程
  10. // 先查看实例对象dx1身上有没有ff这个方法,没有就通过__proto__ prototype查找ff这个方法
  11. console.log(dx1.ff === dx2.ff); // 输出true(共有所以相等)
  12. // 7.静态成员 ---- 给构造函数身上添加的成员(也可以是方法)
  13. Star.staticName = '静态成员';
  14. // console.log(dx1.staticName);// 实例对象不能使用静态成员
  15. console.log(Star.staticName);// 静态成员只能给构造函数使用

4.3、获取原型对象的方法

4.3.1、 proto 属性

定义:在每个实例对象上都有一个 proto 的属性,也是用来获取该对象的原型对象(该属性是在ES6之后才纳入规范,在这之前,只有部分浏览器实现)

  1. // __proto__是实例对象的属性:意义就在于为对象的查找机制提供一个反向,或者说是一条路线,但是他是一个非标准属性,
  2. // 因此实际开发中,不可以使用这个属性,它只是内部指向原型对象prototype
  3. function Star(uname) {
  4. this.uname = uname;
  5. }
  6. let dx1 = new Star('张三');
  7. console.log(dx1.__proto__); // 实例化对象身上系统自带__proto__指向我们的原型对象(现在的控制台打印出来是[[prototype]])
  8. // prototype(构造函数的原型对象)和__proto__(实例对象的原型)是等价的
  9. console.log(Star.prototype === dx1.__proto__); // 输出true

4.3.2、getPrototypeOf方法

除了prototype和 proto能获取原型对象,getPrototypeOf方法也能

语法:Object.getPrototypeOf(实例对象) 获取指定实例对象的原型对象

  1. // Object.getPrototypeOf()方法 是 Object内置构造函数的静态成员
  2. // 作用: 获取某个实例对象的原型对象
  3. console.log(Object.getPrototypeOf(dx1)===Star.prototype);
  4. console.log(Object.getPrototypeOf(dx1)===dx1.__proto__);

五、原型对象的设置和访问(掌握)

5.1、 proto指向

  1. function Person(name, age) {
  2. this.name = name;
  3. this.age = age;
  4. }
  5. // 不会报错 因为new实例化的时候 p.__proto__ = Person.prototype(__proto__指向了Person.prototype)
  6. //(prototype本来就默认存在)
  7. var p = new Person('张三', 18);
  8. Person.prototype.ff = function () {
  9. console.log('prototype原型对象里面的方法');
  10. }
  11. p.ff(); // 先实例对象后给prototype添加方法并调用不会报错

5.2、设置原型遇到的问题

  • 使用构造器.prototype.方法名 = 方法函数 动态添加方法
  • 如果需要在原型对象上添加多个成员的时候用构造器.prototype = {方法名:方法}
    相对于之前的方式,这种方式能够更统一、更方便的在Person原型对象上添加成员。
  1. // 1.先修改原型,再实例化并调用字面量对象{}里面的方法(不会报错)
  2. Person.prototype = {
  3. ff: function () {
  4. console.log('方法一');
  5. },
  6. ff2: function () {
  7. console.log('方法二');
  8. }
  9. }
  10. let p = new Person('张三',18);
  11. p.ff();
  1. // 2.先实例化,再改原型并调用字面量对象{}里面的方法(会报错)
  2. let p = new Person('张三', 18);
  3. Person.prototype = {
  4. ff: function () {
  5. console.log('方法一');
  6. },
  7. ff2: function () {
  8. console.log('方法二');
  9. }
  10. }
  11. p.ff(); // Uncaught TypeError: p.ff is not a function

报错原因:实例化时的实例对象 proto已经指向了Person.prototype

后面再改实例对象为新字面量{}时实例对象不认,所以字面量{}里面的方法自然也不能调用

5.3、prototype = {} constructor指向问题修改

  • 字面量对象的原型对象是Object.prototype
  1. let obj = {
  2. name: '张三'
  3. }
  4. console.log(obj.__proto__); // 原型对象
  5. console.log(obj.__proto__.constructor === Object); // true 原型对象的构造函数是Object
  6. // 结论: 1.字面量对象的__proto__指向内置构造函数Object的原型对象
  7. // 2. 字面量对象是由内置构造函数Object实例化出来的
  • 解决方法,手动的利用constructor指回原来的构造函数
  1. function Person(name) {
  2. this.name = name;
  3. }
  4. // = 赋值操作了把实例对象的__proto__改向了赋值操作的字面量对象{}
  5. // prototype里面的constructor属性给覆盖掉了,原来的Star.prototype将不再被引用(会被内存销毁)
  6. Person.prototype = {
  7. // 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
  8. constructor: Person,
  9. ff: function () {
  10. console.log('方法一');
  11. },
  12. ff2: function () {
  13. console.log('方法二');
  14. }
  15. }
  16. let p = new Person('张三');
  17. console.log(p.__proto__.constructor === Person); // true 原型对象的构造函数是Person

六、原型相关属性学习(掌握)

  • 需求: 判断成员的归属 (静态成员,实例成员,原型成员)
  1. function Person(name, age) {
  2. this.name = name;
  3. this.age = age;
  4. }
  5. Person.prototype.ff = function () {
  6. console.log('方法');
  7. }
  8. var p = new Person('张三', 18);

6.1、in关键字

  • 语法:''属性名'' in 实例对象 ———— 判断属性是不是 对象的实例成员或原型成员
    • 无论判断的成员是属于当前实例对象还是属于其原型对象的,都返回true,如果都不存在,则返回false。
  1. for (var key in p) {
  2. console.log(key); // name age
  3. }
  4. console.log("name" in p); // true
  5. console.log("ff" in p); // true

6.2、hasOwnProperty方法

  • 语法:实例对象.hasOwnProperty(''属性名'') ———- 判断属性是不是 只属于对象的实例成员
    • 只判断当前实例对象中是否存在实例的属性,存在返回true,反之返回false。
    • Object的原型成员,所有实例对象都能访问的方法
  1. console.log(p.hasOwnProperty('name')); // true
  2. console.log(p.hasOwnProperty('ff')); // false
  3. // 需求: 判断一个属性只属于原型成员
  4. console.log('name' in p && !p.hasOwnProperty('name')); // false
  5. console.log('ff' in p && !p.hasOwnProperty("ff"));// true

6.3、isPrototypeOf方法

  • 语法:某个原型对象.isPrototypeOf(实例对象) ——— 判断原型对象在不在实例对象的原型链上
    • A.isPrototypeOf(B) 判断的是A对象是否存在于B对象的原型链之中
  1. console.log(Person.prototype.isPrototypeOf(p)); // true
  2. console.log(Object.prototype.isPrototypeOf(p)); // true
  3. console.log(Array.prototype.isPrototypeOf(p)); // false

6.4、instanceof关键字

  • 语法:实例对象 instanceof 构造函数
    • 字面意思理解为判断当前对象是否是指定的类型
    • 更深层次理解,指定类型是否在当前实例对象的原型链上,如果是返回true,反之返回false。
  1. console.log(p instanceof Person);// true
  2. console.log(p instanceof Object);// true
  3. console.log(p instanceof Array);// false

七、继承

ES5是没有继承语法的,只能通过方法模拟继承语法,所以有多种方式达到继承效果。
继承目的:通过相应的代码将父类中的成员复制到子类对象中

7.1、混入式继承(了解)

实现原理:将父类成员拷贝到子对象中(浅拷贝)。

实现方法:for…in…循环遍历父类,子类[key]=父类[key]

弊端:共享数据安全问题,修改子类,会影响父类,引用数据类型浅拷贝,会修改引用地址

  1. //混入式继承(拷贝继承)
  2. //obj2继承到obj1中的成员,可以直接将obj1中的成员拷贝到obj2中即可
  3. var obj1 = {
  4. name: '张三',
  5. age: 18,
  6. dx:{
  7. dxName:'李四'
  8. }
  9. };
  10. var obj2 = {};
  11. // 将obj1中的成员拷贝到obj2中
  12. for (var key in obj1) {
  13. obj2[key] = obj1[key];
  14. }
  15. //修改obj1对象中的dx属性的属性值,会造成一改全改
  16. obj1.dx.dxName = '王五';
  17. console.log(obj1); // {name: '张三', age: 18, dx: {dxName: '王五'}}
  18. console.log(obj2); // {name: '张三', age: 18, dx: {dxName: '王五'}}

当我们需要修改其中某一个对象中的引用类型属性时,会造成其他相关的对象也被修改,原因在于大家引用的是同一个内存区域中的数据。

7.2、原型式继承(了解)

实现原理:将父类中的原型成员添加到子类的原型链中。

实现方式:子类.prototype = 父类.prototype

弊端:数据安全,子类会修改父类原型对象 造成原型链结构混乱

  1. // 父类
  2. function Person(name) {
  3. this.name = name;
  4. }
  5. Person.prototype.ff = function () {
  6. console.log('prototype原型对象里面的方法');
  7. }
  8. // 子类
  9. function Son(name, age) {
  10. this.name = name;
  11. this.age = age;
  12. }
  13. // 子类的原型对象指向父类的原型对象(地址)
  14. Son.prototype = Person.prototype;
  15. // 在Son新原型中添加新方法
  16. Son.prototype.ff2=function(){
  17. console.log('新方法');
  18. }
  19. var son = new Son('张三', 18);
  20. son.ff(); son.ff(); // 成功调用父类的ff方法
  21. console.log(Person.prototype); // Person原型对象多了个ff2

7.3、原型链继承(掌握)

实现原理:将子类的原型对象指向父类构造函数。

实现方法:子类.prototype = new 父类()

存在问题:存在数据共享问题,无法给父类构造函数传递参数

弊端:只能继承原型成员不能继承实例成员

  1. // 父类
  2. function Person(name, age) {
  3. this.name = name;
  4. this.age = age;
  5. }
  6. Person.prototype.ff = function () {
  7. console.log('prototype原型对象里面的方法');
  8. }
  9. // 子类
  10. function Son(name, age) {
  11. this.name = name;
  12. this.age = age;
  13. }
  14. // 1. 实现原型链继承 子类原型指向父类实例(新地址Person)
  15. Son.prototype = new Person(); // 不需要传参
  16. // 2. 注意点: 记得将constructor属性指向子类
  17. Son.prototype.constructor = Son;
  18. // 3. 子类需要方法,记得加上
  19. Son.prototype.ff2 = function () {
  20. console.log('新方法');
  21. }
  22. // 创建子类的实例
  23. var son = new Son('张三', 18);
  24. son.ff(); // 成功调用父类的ff方法
  25. console.log(son.__proto__.constructor); // 指向了子类Son
  26. console.log(Person.prototype); // Person原型对象不会多了个ff2

7.4、借用构造函数继承(掌握)

7.4.1、call方法和apply方法的基本使用(十分重要)

  • call语法:方法.call(对象,参数1,参数2)
  • apply语法:方法.apply(对象,[参数1,参数2.....])

call和apply的相同点: 将方法借给另一个对象使用并自动调用,会将方法中的this指向借给的对象

call和apply的不同点: 传参方式不同

  1. let obj1 = {
  2. name: '张三',
  3. age: 20,
  4. add(a, b) {
  5. console.log(this.name);
  6. console.log(a + b);
  7. return a + b;
  8. }
  9. }
  10. let obj2 = {
  11. name: "李四",
  12. age: 50,
  13. }
  14. // 将obj1的add方法借给obj2并马上调用
  15. obj1.add.call(obj2, 100, 200); // 300 注意此时add方法里面this.name打印的是李四!
  16. obj1.add.apply(obj2, [100, 200]);

拓展

  1. // 拓展: 获取数组中的最大值
  2. var arr = [1, 2, 4, 6, 8, 3];
  3. // max 借给谁不重要, 只要传参方式 .max刚刚好需要传数组参数用apply
  4. console.log(Math.max.apply(null,arr));

7.4.2、借用构造函数继承说明

实现原理:在子构造函数中调用父构造函数,达到继承并向父构造函数传参的目的

实现方法:

  1. 将父对象的构造函数设置为子对象的成员
  2. 调用这个方法,类似于将父构造函数中的代码复制到子构造函数中来执行
  3. 用完之后删掉

弊端:只能继承实例成员不能继承原型成员(和原型链继承刚刚好相反)

  1. 如果父子构造函数存在相同的成员,那么子构造函数会覆盖父构造函数中的成员
  1. // 需求: 子类继承父类的实例成员
  2. // 好处,子类里面的name和age父里面有但他也有代码冗余,这样写只要拿到父里面的name跟age就行
  3. function Person(name, age) {
  4. this.name = name;
  5. this.age = age;
  6. }
  7. function Son(name, age, score) {
  8. // Person(name, age);
  9. // 函数之间调用, 造成this指向window的问题
  10. // 重点: 改变this指向
  11. // 思路:
  12. // 1. 先将父类构造函数赋值给子类的实例对象
  13. this.fn = Person;
  14. // 2. 子类实例去调用这个方法------父类内部的this指向子类的实例
  15. this.fn(name, age);
  16. this.score = score;
  17. // 3. 用完之后,删掉
  18. delete this.fn;
  19. }
  20. var son = new Son('张三', 18, 50);
  21. console.log(son); // {name: '张三', age: 18, score: 50}

高级实现方法:凡是要借用方法,首先想到使用call或apply

  1. function Student(name,age,score) {
  2. // 把父构造函数借给自己来调用,解决this指向window的问题
  3. Person.call(this,name,age);
  4. Person.apply(this,[name,age]);
  5. this.score = score;
  6. }

7.5、组合继承(必须掌握)

实现原理:基原型链继承+借用构造函数继承

弊端: 子类的原型对象中有无效数据

  1. function Person(name, age) {
  2. this.name = name;
  3. this.age = age;
  4. }
  5. Person.prototype.eat = function () {
  6. console.log(this.name);
  7. }
  8. function Son(name, age, score) {
  9. // 借用构造函数继承
  10. Person.apply(this, [name, age]);
  11. this.score = score;
  12. }
  13. // 原型链继承
  14. Son.prototype = new Person();
  15. Son.prototype.constructor = Son;
  16. Son.prototype.zff = function () {
  17. console.log(this.score);
  18. }
  19. var son = new Son('张三', 18, 50);
  20. son.eat(); // 张三
  21. son.zff(); // 50
  22. // 子类的原型对象中有无效数据
  23. console.log(son.__proto__); // {name: undefined, age: undefined, constructor: ƒ, zff: ƒ}

7.6、寄生组合继承(拓展)

实现原理:优化原型链继承(优化也叫寄生式继承)+借用构造函数继承。组合起来就叫寄生组合继承

组合继承的基础上,改造原本的原型链继承。子类和父类中间创建一个空类,过滤掉无用的父类实例属性。

  1. function Person(name, age) {
  2. this.name = name;
  3. this.age = age;
  4. }
  5. Person.prototype.eat = function () {
  6. console.log(this.name);
  7. }
  8. function Son(name, age, score) {
  9. // 借用构造函数继承
  10. Person.apply(this, [name, age]);
  11. this.score = score;
  12. }
  13. // // 原型链继承
  14. // Son.prototype = new Person();
  15. // Son.prototype.constructor = Son;
  16. // 寄生式继承--------对原型链继承的优化
  17. // 思路: 创造一个中间类,过滤无效数据
  18. (function () {
  19. // 立即执行函数:目的是只执行一次 Super 用完之后就会删除
  20. // 函数中的变量在函数执行完毕之后 就会销毁
  21. // 函数局部变量的生命周期-------从创建到销毁的过程
  22. var Super = function () { };
  23. Super.prototype = Person.prototype;
  24. Son.prototype = new Super();
  25. Son.prototype.constructor = Son;
  26. })()
  27. Son.prototype.zff = function () {
  28. console.log(this.score);
  29. }
  30. var son = new Son('张三', 18, 50);
  31. son.eat(); // 张三
  32. son.zff(); // 50
  33. // 子类的原型对象中有没有无效数据
  34. console.log(son.__proto__); // {constructor: ƒ, zff: ƒ} 注意:无效数据被筛出了!

总结: ES5实现继承的方式不止一种。这是因为ES5 中的继承机制并不是明确规定的,而是通过模仿实现的。这意味着所有的继承细节并非完全由解释程序处理。作为开发者,你有权决定最适用的继承方式。

八、绘制完整的原型链结构图(掌握)

完整的结构图如下:

图1:

图2:

总结:

  1. Function函数对象由自身的构造函数创建
  2. Function的原型对象是一个匿名空函数,绑定了函数中的通用方法
  3. 空函数对象的类型是Object
  4. 所有的函数对象都是Function类型,由Function构造函数创建
  5. 所有的对象都是 Object 类型的
  6. 所有对象的 proto 最终都能找到 Object.prototype
  1. // 1.问题: 函数也是对象,原型链结构怎样的??? 函数的__proto__指向???----- Function.prototype
  2. console.log(Person.__proto__);
  3. console.log(Student.__proto__);
  4. console.log(Object.__proto__);
  5. console.log(Object.__proto__ === Student.__proto__);
  6. // 2.问题: 所有函数的构造函数是???----- 内置构造函数Function
  7. console.log(Object.__proto__.constructor);
  8. console.log(Student.__proto__.constructor);
  9. console.log(Person.__proto__.constructor);
  10. // 3.问题: Function.prototype 看做是一个实例对象 她是类型是什么???----- Object
  11. console.log(Function.prototype.__proto__.constructor);
  12. // 4. 问题: Function也可以看做是一个实例对象,对应的类型和原型对象是????
  13. // Function 的 类型 是 Function
  14. console.log(Function.__proto__ === Function.prototype);

九、基本包装类型的使用(理解)

9.1、基本包装类型的创建

定义:方便对string,number,boolean三种基本类型数据操作,ECMAScript提供了三个特殊引用类型——基本包装类型(String,Number,Boolean)

创建方式:

  1. // 方式一:
  2. var str1 = new String("string1");
  3. var num1 = new Number(123);
  4. var bool1 = new Boolean(true);
  5. console.log(str1);
  6. console.log(num1);
  7. console.log(bool1);
  8. // 方式二
  9. var str2 = new Object("string2");
  10. var num2 = new Object(456);
  11. var bool2 = new Object(false);
  12. console.log(str2);
  13. console.log(num2);
  14. console.log(bool2);

9.2、基本包装类型使用的注意点

先看下面的代码,观察存在的问题:

  1. var str = '一个字符串';
  2. console.log(str.length);// 5
  3. console.log(str.substr(0, 2));// 一个

上面代码看似很简单,但是存在一个很基本的疑问点:

str是基本类型,不存在属性和方法一说,但是下面两行代码中却是在访问属性和方法,而且代码还能正确的执行,这是如何实现的呢?

其实,为了让我们实现这种直观的操作,后台已经自动完成了以下操作:

  • 创建 String 类型的一个实例;
  • 在实例上调用指定的方法;
  • 销毁这个实例;
  1. /* 底层实现:
  2. 1. 创建了一个String的实例 var obj = new String(str);
  3. 2. 包装类型去使用属性方法(原型成员的共享) obj.length obj.substr(0,2)
  4. 3. 老渣男行为: 用完删掉 obj = null;
  5. */
  6. // 所以基本数据类型str才能使用length属性和substr方法
  7. console.log(str.length);
  8. console.log(str.substr(0,2));

经过此番处理,基本的字符串值就变得跟对象一样了。而且,上面这三个步骤也分别适用于 Boolean和 Number 类型对应的布尔值和数字值

十、私有成员和特权方法(了解)

  • 成员:对象的属性和方法的统称

实例成员:实例对象的属性和方法

原型成员:构造函数对应的原型对象的属性和方法

静态成员:构造函数自己的属性和方法

私有成员:在构造函数中声明的变量和函数,因为我们只能在函数内部访问,所以是私有的

特权方法:在函数内部使用了私有成员的实例方法被称为是特权方法(闭包)

  1. // 构造函数 Person
  2. function Person(name, age) {
  3. // 构造函数内部的局部变量和函数--------私有成员 syName syFf
  4. var syName = '私有成员';
  5. function syFf() {
  6. return '私有方法';
  7. }
  8. // 实例成员 name age slFf
  9. this.name = name;
  10. this.age = age;
  11. // 使用了私有成员的实例方法------特权方法(函数的私有成员拿到外部使用)
  12. this.slFf = function () {
  13. console.log(syName, syFf());
  14. }
  15. }
  16. // 原型成员 yxFf
  17. Person.prototype.yxFf = function () {
  18. console.log('原型方法');
  19. }
  20. // 静态成员 jtName
  21. Person.jtName = '静态成员';
  22. // 实例对象 p
  23. var p = new Person('张三', 18);
  24. // p.syFf(); 报错 需要特权方法带他出来
  25. p.slFf(); // 私有成员 私有方法
  26. p.yxFf(); // 原型方法
  27. console.log(p.name, p.age); // 张三 18
  28. console.log(Person.jtName); // 静态成员

十一、Object成员(了解)

11.1、原型成员

  • constructor:获取当前对象的构造函数
  • hasOwnProperty:判断当前实例对象中是否存在指定的属性
  • isPrototypeOf:判断当前对象是否在指定对象的原型链中
  • propertyIsEnumerable:实例成员是否可以枚举(循环遍历)返回布尔值
    1. function Person(name, age) {
    2. this.name = name;
    3. this.age = age;
    4. }
    5. Person.prototype.yxFf = function () {
    6. console.log('原型方法');
    7. }
    8. var p = new Person("张三", 20);
    9. // 1. propertyIsEnumerable 判断枚举性(可否遍历循环)------只能判断对象实例成员
    10. // 重点: 理解枚举的概念--- js自带的属性方法一般都不可枚举(打印出来颜色比较浅),可枚举(打印出来颜色比较深)
    11. for (var k in p) {
    12. // constructor 属性没被遍历???? 因为不可枚举
    13. console.log(k); // name age yxFf
    14. }
    15. console.log("---------把Person.prototype看做是Object构造函数创建的一个实例对象----------");
    16. console.log(Person.prototype.propertyIsEnumerable("yxFf")); // true 可枚举
    17. console.log(Person.prototype.propertyIsEnumerable("constructor")); // false 不可枚举
    18. // 不能判断原型成员
    19. console.log("---------p本来就是实例成员 使用这个方法没意义----------");
    20. console.log(p.propertyIsEnumerable("yxFf"));
    21. console.log(p.propertyIsEnumerable("constructor"));
  • toString:返回数据特定的格式的字符串 [object 构造函数]。
    • 几乎所有的构造函数都有原型成员toString,所以字符串,数字,布尔,数组等类型使用toString是使用自己构造函数的toString原型方法
    • Object.prototype.toString.call(…);才能真正使用Object的toString原型方法
    • 作用:可以获取所有数据的真实类型 ```javascript // 2.toString———-转字符串用 console.log(‘字符啊啊’.toString()); // 字符啊啊 console.log((10).toString()); // 10 console.log(true.toString()); // true console.log((new Date()).toString()); // Sun Aug 28 2022 16:48:57 GMT+0800 (中国标准时间) console.log([1, 2, 3].toString()); // 1,2,3 // 上述五个的数据类型调用的都不是 Object.prototype 的方法, // 用的都是自己构造函数原型上的toString console.log({ name: ‘张三’ }.toString()); // [object Object]

// 使用场景: 用Object.prototype原型里面的toString判断所有数据的真实类型(Object.prototype.toString) console.log(Object.prototype.toString.call(‘张三’)); // [object String] console.log(Object.prototype.toString.call(10)); // [object Number] console.log(Object.prototype.toString.call(true)); // [object Boolean] console.log(Object.prototype.toString.call(new Date())); // [object Date] console.log(Object.prototype.toString.call([1,2,3])); // [object Array]

  1. - **valueOf**:返回当前对象对应的值。
  2. - 三大包装类型(还有Date)都有原型成员valueOf,所以基本类型使用valueOf是使用自己包装类型的valueOf原型方法
  3. - Object.prototype.valueOf.call(...);才能真正使用ObjectvalueOf原型方法
  4. - 特点:基本类型会得到包装类型的返回值
  5. ```javascript
  6. let strObj = new String('demo');
  7. console.log(strObj.valueOf());// demo 基本包装类型:返回对应的值
  8. let obj = { name: '张三', age: 18 };
  9. console.log(obj.valueOf()); // {name:'张三',age:18} 返回对象本身
  10. let date = new Date();
  11. console.log(date.valueOf()); // 1661678348888 返回时间戳
  12. console.log(Object.prototype.valueOf.call(1)); // Number {1}
  13. console.log(Object.prototype.valueOf.call('一个字符串')); // String {'一个字符串'}
  14. console.log(Object.prototype.valueOf.call(true)); // Boolean {true}
  15. console.log(Object.prototype.valueOf.call(date)); // Sun Aug 28 2022 17:21:44 GMT+0800 (中国标准时间)

11.2、静态成员

  • assign:将多个对象合并到一个对象中并返回。
  • create:创建对象,并设置原型对象
    1. // 该方法可以接收的参数有一下两种
    2. // 1. null 创建一个空对象,这个空对象中连最基本的原型对象都没有的
    3. Object.create(null)//创建一个空对象
    4. // 2. 对象 创建传递进来的对象,并设置该对象的原型对象为当前的参数
    5. Object.create({name:"张三"}) // {name:"张三"}参数是原型成员
  • is:判断两个参数是否相等,等同于===,注意下面两种特殊的判断即可
    1. console.log(0 === -0); // true
    2. console.log(Object.is(-0, 0)); // false
    3. console.log(NaN === NaN); // false
    4. console.log(Object.is(NaN, NaN)); // true
  • values:快速将对象所有属性的值 合并成一个数组并返回 —ES6新增
    1. var obj = {
    2. name:'张三',
    3. age:20,
    4. gender:"男"
    5. }
    6. console.log(Object.values(obj)); // ['张三', 20, '男']

十二、ES6类的实现—class(掌握)

12.1、class的基本结构

定义及用法:calss关键字定义类,创建构造函数,类名首字母大写

ES6中使用class定义类只是一种语法糖(语法糖能够增加程序的可读性,从而减少程序代码出错的机会的一种语法)

底层最终还是转换成ES5中使用的function类定义类,以及其中的实例成员和原型成员。(简写或者更加语义化)

  1. class Person {
  2. // 构造函数
  3. constructor(name, age) {
  4. // 实例成员
  5. this.name = name;
  6. this.age = age;
  7. }
  8. // 原型成员(对象的原型方法,跟构造函数constructor同级)
  9. ff(){
  10. console.log(this.name);
  11. }
  12. // 静态成员
  13. static jtFf(){
  14. return '静态方法';
  15. }
  16. }
  17. let p = new Person('张三',18);
  18. p.ff(); // 张三
  19. console.log(Person.jtFf()); // 静态方法

12.2、class的继承结构

ES6中class用extends 和 super实现继承。

  1. class Person {
  2. constructor(name, age) {
  3. this.name = name;
  4. this.age = age;
  5. }
  6. eat() {
  7. console.log(this.name + '爱吃盖浇螺蛳面');
  8. }
  9. }
  10. class Student extends Person { // extends 类似原型链继承
  11. constructor(name, age, score) {
  12. // super必须在子类this之前调用(报错)
  13. super(name, age); // super 类似借用构造函数继承 (调用了父类中的构造函数)
  14. this.score = score;
  15. }
  16. getScore() {
  17. console.log(this.score);
  18. super.eat(); // 使用super关键字调用父类中的构造方法
  19. }
  20. }
  21. var s = new Student('张三', 18, 50);
  22. s.eat(); // 张三爱吃盖浇螺蛳面
  23. s.getScore(); // 50 张三爱吃盖浇螺蛳面
  24. console.log(new Person()); // Person {name: undefined, age: undefined}
  25. // js原则上没有类的概念,底层还是原型链,类的概念只是一个语法糖