前端与移动开发----JS高级----构造函数,原型,继承

JS高级02

回顾

面向过程编程: 就是把代码一步步写出来逐行执行, 没有任何的复用和封装性

面向对象编程: 是对象在调用各种功能方法, 封装类进行复用

类(模板)/属性/方法:

class 类名 {
    
    
    constructor (形参1, 形参2){
    
    
        this.属性名 = 形参1;
        this.属性名 = 形参2;
    }
    方法名1() {
    
    
        
    }
    方法名2() {
    
    
        
    }
}

实例对象 (拥有类里的属性和方法)

new 类名(实参1, 实参2);

new的作用

  • 创建一个空白对象{}
  • 执行并替代constructor里this的值
  • 在this(空白对象{})身上添加属性和对应的值
  • constructor自动返回这个对象到调用处

this指向当前函数调用者, constructor指向实例对象

extends用于继承, 如果子类有constructor需要在使用this之前, 调用super()确认父类里的属性和方法

在子类的普通方法里, 还可以使用super.父类普通方法()来调用父类的方法

实例成员指的是new 类名() 创建出的实例对象

封装tab栏插件

为了高薪铺垫

6. 构造函数

学习目标:

  • 目的: class的本质

  • 语法: function 大写开头函数名() { this身上绑定属性 } - 相当于class的constructor

  • 使用: new 大写开头函数名()

6.0 构造函数_基础

什么是构造函数

ES6新增class定义类, 但是之前ES5只能用构造函数+原型来模拟

// 定义构造函数(类)
function Star(theName, theAge) {
    
    
    // 定义属性和方法
    this.uName = theName;
    this.age = theAge;
    this.sing = function(){
    
    
        console.log("学习唱歌");
    }
}
// new实例化对象
var ldh = new Star("刘德华", 18);
var zxy = new Star("张学友", 20);
// 调用属性和方法
console.log(ldh);
console.log(ldh.uName);
ldh.sing();

// 总结:
// 构造函数大写名字开头(为了和普通函数区分)
// 用this来添加属性
// 用new来触发构造函数, 可以传参进去, 必会返回一个对象(回顾new的作用), 不用new 就是个普通函数

6.1 实例成员和静态成员

function Star(theName, theAge) {
    
    
    // 实例成员
    this.uName = theName;
    this.age = theAge;
    this.sing = function(){
    
    
        console.log("学习唱歌");
    }
}
// new实例化对象
var ldh = new Star("刘德华", 18);

// 静态成员
// (1): 函数名本身就是个对象
// (2): 在构造函数身上添加的属性和方法 - 都属于静态成员
Star.hobby = "唱歌";
Star.play = function(){
    
    
    console.log("去打架子鼓");
}

// 调用时的区别 (口诀:谁绑定谁调用)
console.log(ldh.uName);
ldh.sing();
console.log(Star.hobby);
Star.play();

// 总结: 添加在构造函数身上的叫静态成员, 在构造函数内绑定在this身上的叫实例成员 (成员=属性+方法)

6.2 练习

请定义一个构造函数Person, 有name, height, weight, homeTown, sing方法. 方法中打印一句话"学习唱歌", 并实例化2个对象打印即可.
function Person (tName, tHeight, tWeight, tHomeTown) {
    
    
    this.name = tName;
    this.height = tHeight;
    this.weight = tWeight;
    this.homeTown = tHomeTown;
    
    this.sing = function(){
    
    
        console.log("学习唱歌");
    }
}
var per1 = new Person("孙悟空", 180, 140, "花果山");
var per2 = new Person("猪八戒", 150, 280, "高老庄");

console.log(per1.height); // 180
console.log(per2.sing); // function(){console.log("学习唱歌");}
per2.sing();

7. 原型

7.0 内存浪费问题

在这里插入图片描述

实例对象this上属性的值 - 是函数时, 会造成内存浪费

function Star(theName, theAge) {
    
    
    this.uName = theName;
    this.age = theAge;
    this.sing = function(){
    
    
        console.log("学习唱歌");
    }
}
var ldh = new Star("刘德华", 18);
var zxy = new Star("张学友", 20);
console.log(ldh.sing === zxy.sing); // false (证明2个方法的内存地址是不相等的, 也就证明在内存中有2个sing方法存在哦)

// 注意: 判断sing的值是否相等, 不要写成了sing()

// 总结:
// 在构造函数中属性的值如果是函数, 会造成内存浪费

7.1 构造函数原型 prototype

构造函数通过原型属性分配的函数是所有实例对象所共享

构造函数天生自带的属性: 构造函数.prototype

在这里插入图片描述

function Star(theName, theAge) {
    
    
    this.uName = theName;
    this.age = theAge;
}
Star.prototype.sing = function(){
    
    
    console.log("学习唱歌");
}
var ldh = new Star("刘德华", 18);
var zxy = new Star("张学友", 20);
console.log(ldh.sing === zxy.sing); // true (代表内存地址相同, 指向的同一个函数体)


// 总结:
// 在原型属性上添加, 让所有实例对象共享, 可以保证内存的不浪费
// 属性写在构造函数内, 函数绑定在prototype上

// 为什么sing在prototype身上, 实例对象也能直接访问到sing方法呢? 往下看

7.2 对象原型__proto__

对象天生自带的属性, 为了让对象能调用更过的方法, 这是内置属性无需手动调用

function Star(theName, theAge) {
    
    
    this.uName = theName;
    this.age = theAge;
}
Star.prototype.sing = function(){
    
    
    console.log("学习唱歌");
}
var ldh = new Star("刘德华", 18);
var zxy = new Star("张学友", 20);
console.log(ldh.sing === zxy.sing); // true (代表内存地址相同, 指向的同一个函数体)

ldh.sing();
console.log(ldh.__proto__ === Star.prototype); // true

// 查找规则: 如果对象本身没有这个方法, 会通过对象的__proto__向上查找, 如果有则使用

// 总结:
// 因为对象.__proto__ 指向 构造函数.prototype 所以对象能访问prototype上的方法

因为无需显示写出调用, 所以__proto__也叫隐式原型

必须张口就来的话: 实例对象.__proto__ 等于(指向) 构造函数.prototype

7.3 prototype原型对象的constructor属性

constructor它称作构造函数, 它的值指回当前构造函数

function Star(theName, theAge) {
    
    
    this.uName = theName;
    this.age = theAge;
}
// 1. 打印constructor属性的值
console.log(Star.prototype.constructor); // Star
console.log(Star === Star.prototype.constructor) // true

var ldh = new Star("刘德华", 18);

// 总结
// 这个属性一般查看某个对象是由哪个构造函数创建出来的 ldh.__proto__.constructor
console.log(ldh.__proto__.constructor);

构造函数, 实例对象, 原型

铁三角关系

JS内部对象的组成关系

在这里插入图片描述

7.4 原型链

对象通过__proto__逐级向上查找的过程就叫原型链

在这里插入图片描述

function Star(theName, theAge) {
    
    
    this.uName = theName;
    this.age = theAge;
}
Star.prototype.sing = function(){
    
    
    console.log("学习唱歌");
}
var ldh = new Star("刘德华", 18);
ldh.sing();
console.log(ldh.toString());

console.log(ldh.__proto__ === Star.prototype) // true
console.log(Star.prototype);
console.log(Star.prototype.__proto__ === Object.prototype);
console.log(Object.prototype.__proto__); // null

查找过程

  • ldh对象本身
  • ldh的__proto__ (指向的Star.prototype)
  • ldh的__proto__的``proto` 指向的对象 (Object.prototype) - 找到就使用
  • ldh的__proto____proto____proto__的对象找 (发现是null) - 到顶了还没找到要用的方法
  • JS直接抛出错误到控制台: xxxx.xxx is not a function

7.5 instanceof 使用

含义: instanceof检测构造函数的 prototype是否出现在某个实例对象的原型链上

作用: 我们可以判断一个对象是否是由某个构造函数创建出来的

语法: 实例对象 instanceof 构造函数

// 1. 如何判断一个对象是否属性某个构造函数呢
var arr = []; // 相当于new Array(), []写法叫字面量(语法糖)
console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true

function Star(theName, theAge) {
    
    
    this.uName = theName;
    this.age = theAge;
}
Star.prototype.sing = function () {
    
    
    console.log("学习唱歌");
}
var ldh = new Star("刘德华", 18);
console.log(ldh instanceof Star); // true

// 2. 判断对象是否由Star构造函数创建的
console.log(ldh instanceof Star); // true


// 总结
// 用来判断某个对象是否由某个构造函数创建的

7.6 this的指向

function Star(theName, theAge) {
    
    
    this.uName = theName;
    this.age = theAge;
}
Star.prototype.sing = function(){
    
    
    console.log(this.uName + "在" + this.age + "岁唱歌");
}
var ldh = new Star("刘德华", 18);
var zxy = new Star("张学友", 20);
ldh.sing();
zxy.sing();

// 总结
// 看到this, 先看所在function函数(所以把函数看成一个整体)的名字
// 再找到这个函数名.前面那个人就是调用者(this的值就是它) - new特殊记忆

7.7 利用prototype扩展方法(高级)

原理: 用到了原型链

对象本身没有方法, 会在构造函数.prototype上去查找使用

// 构造函数.prototype上添加方法
Array.prototype.sum = function() {
    
    
    var sum = 0;
    for (var i = 0; i < this.length; i++) {
    
    
        sum += this[i];
    }
    return sum;
};

// 以后实例对象, 能够调用prototype上的方法使用
var arr = [1, 2, 3];
console.log(arr.sum());
var arr1 = new Array(11, 22, 33);
console.log(arr1.sum());

7.8 练习

练习1: 定义ChuanTool构造函数
        属性接受2个数字(numA, numB)
        定义2个方法(getMax, randomNum)
        getMax方法返回numA和numB其中比较大的值
        randomNum方法返回一个随机数(范围就是numA到numB, 假设numA是小于numB的)
练习2: 创建实例对象, 给属性传值, 然后调用getMax和randomNum方法, 接收结果打印

        function ChuanTool(theA, theB){
    
    
            this.numA = theA;
            this.numB = theB;
        }
        ChuanTool.prototype.getMax = function(){
    
    
            return Math.max(this.numA, this.numB);
        }
        ChuanTool.prototype.randomNum = function(){
    
    
            return Math.floor(Math.random() * (this.numB - this.numA + 1) + this.numA);
        }
      
        var toolObj = new ChuanTool(10, 100);
        console.log(toolObj.getMax());
        console.log(toolObj.randomNum());

总结

  • 函数有prototype, 让所有实例对象共享方法使用, 节省内存
  • 对象有__proto__, 让对象调用更多的属性和方法
  • 对象通过__proto__逐级查找的过程叫原型链
  • 对象.__proto__ 指向 构造函数.prototype
  • this指向当前函数的调用者

学习目的

  • 了解JS对象查找的机制, 明白内部运作的过程 (有助于理解和找错)
  • 能够在内置/别人的构造函数上扩展额外的方法, 让我们的实例对象能够调用更强大的方法

8. 继承

8.0 call方法

  • 马上调用前面函数执行
  • 本次改变函数内this的指向为参数1的值
// call 方法
function fn(x, y) {
    
    
    console.log('我想喝手磨咖啡');
    console.log(this);
    console.log(x + y);
}
var o = {
    
    
    name: 'andy'
};
// fn();
// 1. call() 可以调用函数
// fn.call();

// 2. call() 可以改变这个函数的this指向 此时这个函数的this 就指向了o这个对象
fn.call(o, 1, 2);
console.log(o);

8.1 寄生组合继承 - 属性

属性的继承: 在子构造函数里用 父构造函数.call(this)

// 1. 父构造函数(父类)
function Father(theName, theAge){
    
    
    this.uname = theName;
    this.age = theAge;
}
// 2. 子构造函数(子类)
function Son(uname, uage, uhobby){
    
    
    Father.call(this, uname, uage); // 属性继承
    this.hobby = uhobby;
}

var son = new Son("刘德华", 18, "唱歌");
console.log(son.name);
console.log(son.age, son.hobby);

在这里插入图片描述

8.2 Object.create方法

**Object.create()**方法创建一个新对象,使用传入的对象来提供新对象的__proto__

function Father(){
    
    }
Father.prototype.money = function(){
    
    
    console.log(10000);
}

// 作用:
// (1): 创建一个新对象
// (2): 以参数作为新对象的__proto__的值使用
var newObj = Object.create(Father.prototype);
console.log(newObj);
console.log(newObj.__proto__ === Father.prototype); // true

8.3 寄生组合继承 - 方法

方法的继承:子类.prototype = Object.create(父类.prototype)

function Father(theName, theAge) {
    
    
    this.name = theName;
    this.age = theAge;
}
Father.prototype.money = function(){
    
    
    console.log(10000);
}
function Son(uname, uage, uhobby) {
    
    
    // 属性继承
    Father.call(this, uname, uage);
    this.hobby = uhobby;
}
// 方法继承
Son.prototype = Object.create(Father.prototype);
Son.prototype.constructor = Son;
Son.prototype.exam = function(){
    
    
    console.log("考试");
}

var son = new Son("刘德华", 18, "唱歌");
console.log(son.name, son.age, son.hobby);
son.exam();

var fa = new Father("刘爸爸", 50);
console.log(fa);

还需要ppt画图来解释哦

8.4 类的本质

class 本质是 构造函数function

class拥有的, 构造函数也拥有

// ES6 之前通过 构造函数+原型实现面向对象 编程
// (1) 类的prototype 
// (2) 类的prototype, 里面有constructor属性, 指向构造函数本身
// (3) 类里的方法, 都会挂载到prototype上
// (4) 类的实例对象__proto__ 指向 构造函数的prototype

class Star {
    
    
	constructor(tName, tAge) {
    
    
        this.uname = tName;
        this.uage = tAge;
    }
    sing() {
    
    
        console.log('学习唱歌');
    }
}

// 1. 类的本质其实还是一个函数 我们也可以简单的认为 类就是 构造函数的另外一种写法
// (1) 类有原型对象prototype 
console.log(Star.prototype);
// (2) 类原型对象prototype 里面有constructor 指向类本身
console.log(Star.prototype.constructor);
// (3) 类的prototype的值上 都是方法
console.log(Star.prototype)

var ldh = new Star();
// (4) 类创建的实例对象有__proto__ 原型指向 类的原型对象
console.log(ldh.__proto__ === Star.prototype);

经验值

问题:构造函数 - 实例化报错

  • 描述:

在这里插入图片描述

  • 分析:

    • 控制台报错,直接找到报错的位置
    var person_1 = New Star("zhangs", 18);
    
    • 报错分析:意外?哪里意外了?
    Unexpected identifier:意外的标识符
    
    • 函数名大写开头, 但是new是小写的
  • 解决:new Star("zhangs", 18);

  • 总结:语法记忆一定要清晰,不要张冠李戴呦

问题:构造函数 - prototype - 设置方法,实例对象调用报错

  • 描述:prototype上设置方法sing,实例化p1调用时有报错找不到该函数;

在这里插入图片描述

  • 分析:

    • 报错提醒在19行,查看19处代码:p1.sing();
    • p1只是个实例化对象,如果实例化对象报错找不到sing这个方法,那么意味着其构造函数的原型对象上该方法设置有问题
    • 打印p1.__proto__:发现其__proto__对象上此时是一个名为sing的函数方法;

    在这里插入图片描述

    • 实例对象.__proto__ 指向 构造函数.prototype 但是这里不是构造函数.prototype的原型对象, 直接变成了一个函数, 瓦特? 这出问题了, 解决它
    • 绑定方法的代码如下:
    Star.prototype = function sing() {
          
          
        console.log(1);
    }
    
    • prototype默认为构造函数的原型对象,是一个对象,上面的代码对这个属性名重新赋值为函数,所以会报错
  • 解决:prototype默认为构造函数的原型对象,原型对象上添加新的方法为:

Star.prototype.sing = function() {
    
    
    console.log(1);
}
  • 总结:各语法都有基本的规则和用法;在使用的过程中应该记忆一些核心的规则;

问题:数组 - prototpye - 添加求和方法,使用不报错, 无结果

  • 描述:输出为undefined

    在这里插入图片描述

在这里插入图片描述

  • 分析:
    • 顺藤摸瓜, 在22行打印的, 调用getSum()返回的结果是个undefined

    • 那getSum()是调用函数, 函数内得用return返回值, getSum()原地才有值, 否则就是undefined

    • 所以来到getSum函数体内(看好function的大括号在哪里一对), 发现内部末尾没有return

    • 加上return sum; 以后, 发现返回值竟然是0, 这时, 需要顺一下getSum方法里代码执行的过程和结果了

    • 继续顺sum的值在哪里来的, 发现是16行累加的, 在16行和for之间打印sum - 发现打印没执行

    • 再接着看, 因为for循环没进来才没打印, 接着看arr的值发现是空数组

    • 但是我们想计算的是testArr数组, 那么testArr本身就是实例对象(因为[]是语法糖, 本质是new Array())

    • 然后实例对象调用getSum() 那么在getSum内部this就是testArr

    • 所以15行arr可以换成this, 16行换成this[i]

  • 解决:需求为函数内部获取实例化对象;关键字this;
Array.prototype.getSum = function() {
    
    
    var sum = 0;
    for (var i = 0; i < this.length; i++) {
    
    
        sum += this[i]
    }
    return sum;
}
  • 总结:
    • 初次使用构造函数及其原型对象上函数内部的this,同学会感到陌生,这里算是语法规则,大家需要记忆;
    • 这里的this为:为了某次的实例化对象,所以对象上属性和方法都可以使用;
    • 一定要在函数第一行写上注释, 代表this的值是谁
    • 口诀: this的值指向当前函数的调用者

面试题

什么叫语法糖

是一种编写代码的语法, 写更少的代码, 实现更多的功能

[]  // 相当于 new Array()
{
    
    }  // 相当于 new Object()
var i = 10;
i++; // 相当于 i = i + 1;  (i++就是一种语法糖)

ES6的继承和ES5的继承有什么区别

es5用function函数定义类, 继承是通过prototype和call来实现

es6用class关键字定义类,类之间通过extends和super关键字实现

typeof和instanceof 区别

判断一个变量的类型可以用typeof

typeof 数字 / 字符串 / 布尔 / undefined 返回都是当前类型的字符串

typeof 函数 返回的是function字符串

typeof 对象 / 数组 / null 返回都是object

在javascript中,instanceof用于判断某个对象是否被另一个函数构造。更精确区分数组和对象

如何准确判断一个变量是数组类型?

变量 instanceof Array

说说你对原型(prototype)理解(必会)

JavaScript是一种通过原型实现继承的语言

JavaScript是的动态的弱类型语言

JavaScript中所有都是对象,原型也是一个对象,通过原型可以实现对象的属性继承

原型的主要作用就是为了实现继承与扩展对象

简单说说js中的继承(必会)

在子构造函数中, 使用call/apply方法, 把父构造函数中的属性蹭下来

再让子构造函数.prototype指向父构造函数的实例对象(并且确保子构造函数.prototype.constructor = 子构造函数

常见的继承方法(必会)

寄生组合式继承

思路:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。

在子构造函数里: 父类.call(this),

子类.prototype = Object.create(父类.prototype)

extend()方法才用了这样的方式

of 区别

判断一个变量的类型可以用typeof

typeof 数字 / 字符串 / 布尔 / undefined 返回都是当前类型的字符串

typeof 函数 返回的是function字符串

typeof 对象 / 数组 / null 返回都是object

在javascript中,instanceof用于判断某个对象是否被另一个函数构造。更精确区分数组和对象

如何准确判断一个变量是数组类型?

变量 instanceof Array

说说你对原型(prototype)理解(必会)

JavaScript是一种通过原型实现继承的语言

JavaScript是的动态的弱类型语言

JavaScript中所有都是对象,原型也是一个对象,通过原型可以实现对象的属性继承

原型的主要作用就是为了实现继承与扩展对象

简单说说js中的继承(必会)

在子构造函数中, 使用call/apply方法, 把父构造函数中的属性蹭下来

再让子构造函数.prototype指向父构造函数的实例对象(并且确保子构造函数.prototype.constructor = 子构造函数

常见的继承方法(必会)

寄生组合式继承

思路:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。

在子构造函数里: 父类.call(this),

子类.prototype = Object.create(父类.prototype)

extend()方法才用了这样的方式

如有不足,请多指教,
未完待续,持续更新!
大家一起进步!

猜你喜欢

转载自blog.csdn.net/qq_40440961/article/details/111053677
今日推荐