前端开发系列014-基础篇之Javascript面向对象(三)

一、原型对象相关方法

 ❏ in 关键字
 ❏ instanceof
 ❏ hasOwnProperty方法
 ❏ constructor构造器属性
 ❏ isProtoTypeOf方法

in关键字

作用 用来检查对象中是否存在某个属性(不区分实例属性和原型属性)

语法 “属性名” in 对象

<script>

    //01 提供一个构造函数
    function Person(name) {
        this.name = name;
    }

    //02 设置构造函数的原型对象的属性
    Person.prototype.sayHello = function () {
        console.log("hello");
    }

    //03 创建对象
    var p1 = new Person();

    //04 使用in关键字判断对象中是否存在以下属性:name age sayHello
    console.log("age" in p1);       //false
    console.log("name" in p1);      //true
    console.log("sayHello" in p1);  //true

</script>

hasOwnProperty

作用 用来检查对象中是否存在指定的属性(只检查实例属性)

语法 对象.hasOwnProperty(“属性名”)

<script>

    //01 提供一个构造函数
    function Person(name) {
        this.name = name;
    }

    //02 设置构造函数的原型对象的属性
    Person.prototype.sayHello = function () {
        console.log("hello");
    }

    Person.prototype.des = "默认的描述信息";

    //03 创建对象
    var p1 = new Person();

    //04 使用hasOwnProperty方法判断该属性是否是对象的实例属性
    console.log(p1.hasOwnProperty("age"));       //false
    console.log(p1.hasOwnProperty("name"));      //true
    console.log(p1.hasOwnProperty("sayHello"));  //false
    console.log(p1.hasOwnProperty("des"));       //false

</script>
如何判断某对象中存在且只存在某个原型属性?
function isProperty(obj, property) {
    return !obj.hasOwnProperty(property) && (property in obj);
}

constructor构造器属性

  • 原型对象中的constructor属性指向对应的构造函数

  • 实例对象中的constructor指向对应的构造函数,其中这里的constructor就是从原型中获取的constructor是实例对象中的原型属性而非实例属性

    //01 提供一个构造函数
    function Person(name) {
        this.name = name;
    }

    //02 设置构造函数的原型对象的属性
    Person.prototype.sayHello = function () {
        console.log("hello");
    };
    Person.prototype.des = "默认的描述信息";

    //03 创建对象
    var p1 = new Person();
    function isProperty(obj, property) {
        return !obj.hasOwnProperty(property) && (property in obj);
    }
    console.log(isProperty(p1, "constructor"));    //true

isProtoTypeOf

作用 判断是否是某个实例对象的原型对象

语法 构造函数.protoType.isPrototypeOf(对象)

<script>

    function Person() {
    }
    function Dog() {
    }

    Person.prototype.name = "嘿嘿";
    var p1 = new Person();

    console.log(Person.prototype.isPrototypeOf(p1));    //rue
    console.log(Object.prototype.isPrototypeOf(p1));    //true
    console.log(Dog.prototype.isPrototypeOf(p1));       //false

</script>

instanceof

作用 用于检查对象是否是某个构造函数(类型)的实例

语法对象 instance 构造函数

<script>

    var arr = [1,2,3];
    console.log(arr instanceof Array);      //true
    console.log(Array instanceof Object);   //true
    console.log(arr instanceof Object);     //true

    //instanceOf在判断的时候,算上整条原型链
    //arr 是Array 和Object 任何一个类的示例

</script>

所有的对象都是Object构造函数(类型)的实例

二、JavaScript面向对象编程(继承)

继承·概念

继承 即通过一定的方式实现让某个类型A获取另外一个类型B的属性或方法。其中类型A称之为子类型,类型B称之为父类型或超类型。

javaScript中的继承

Object是所有对象的父级 | 父类型 | 超类型 js中所有的对象都直接或间接的继承自Object。

继承主要有两种方式:接口继承和实现继承。在js中只支持实现继承,实现继承主要依赖原型链来完成

JavaScript中实现继承的几种方式

> ① 原型式继承
> ② 原型链继承
> ③ 经典继承(借用构造函数)
> ④ 组合继承

其他语言中继承通常通过类来实现,js中没有类的概念,js中的继承是某个对象继承另外一个对象,是基于对象的。

原型式继承

原型式继承的方式A

<script>

    //01 提供一个构造函数
    function Person(name,age) {
        this.name = name;
        this.age = age;
    }

    //02 设置原型对象的属性
    Person.prototype.className = "逍遥派1班";

    //03 使用构造函数来创建原型对象
    var p1 = new Person("张三",10);
    var p2 = new Person("李四",20);

    //04 打印p1和p2对象中的className属性
    console.log(p1.className);
    console.log(p2.className);

    //结论:对象p1和p2继承了构造函数原型对象中的属性className
    //但是这并不是严格意义上的继承

</script>

原型式继承的方式B

<script>
    //01 提供一个构造函数
    function Person(name,age) {
        this.name = name;
        this.age = age;
    }

    //02 设置原型对象的属性
    Person.prototype = {
        constructor:Person,
        className:"逍遥派1班"
    };

    //03 使用构造函数来创建原型对象
    var p1 = new Person("张三",10);
    var p2 = new Person("李四",20);

    //04 打印p1和p2对象中的className属性
    console.log(p1.className);
    console.log(p2.className);

    //结论:对象p1和p2继承了构造函数原型对象中的属性className
    //注意:使用原型替换的方式实现继承的时候,原有原型对象中的属性和方法会丢失

</script>

原型式继承的方式C

    //01 提供超类型|父类型构造函数
    function SuperClass() {
        this.name = 'SuperClass的名称';
        this.showName = function () {
            console.log(this.name);
        }
    }

    //02 设置父类型的原型属性和原型方法
    SuperClass.prototype.info = 'SuperClass的信息';
    SuperClass.prototype.showInfo = function () {
        console.log(this.info);
    };

    //03 提供子类型
    function SubClass() {}

    //04 设置继承(原型对象继承)
    SubClass.prototype = SuperClass.prototype;
    SubClass.prototype.constructor = SubClass;

    var sub = new SubClass();
    console.log(sub.name);          //undefined
    console.log(sub.info);          //SuperClass的信息
    sub.showInfo();                 //SuperClass的信息
    sub.showName();                 //sub.showName is not a function

上面的方法可以继承超类型中的原型属性和原型方法,但是无法继承实例属性和实例方法

原型链继承

实现思想 利用原型(链)让一个对象继承另一个对象的属性和方法

实现本质 重写原型对象

原型链结构说明

  ① 每个构造函数都有原型对象
  ② 每个对象都有自己的构造函数
  ③ 每个构造函数的原型都是一个对象
  ④ 那么这个构造函数的原型对象也有自己的构造函数
  ⑤ 那么这个构造函数的原型对象的构造函数也有自己的原型对象
  以上形成一个链式的结构,称之为原型链

原型链中的属性搜索原则

当访问某个对象的成员的时候,采取的搜索策略是:
    ① 先在自身中查找,如果找到则直接使用
    ② 如果在自身中没有找到,则去当前创建当前对象的构造函数的原型对象中查找
        (1)如果找到了则直接使用
        (2)如果在该原型对象中没有找到,则继续查找原型对象的原型对象
                [1] 如果找到则直接使用
                [2] 如果在原型对象的原型对象中也没有找到,则继续向上搜索....
    `→` 重复上面的过程,直到Object的原型对象,若还是没有,则返回undefined(属性)或报错(方法)。

基本写法·代码示例

    //01 提供超类型|父类型
    function SuperClass() {
        this.name = 'SuperClass的名称';
        this.showName = function () {
            console.log(this.name);
        }
    }

    //02 设置父类型的原型属性和原型方法
    SuperClass.prototype.info = 'SuperClass的信息';
    SuperClass.prototype.showInfo = function () {
        console.log(this.info);
    };

    //03 提供子类型
    function SubClass() {
    }

    //04 设置继承(原型对象继承)
    SubClass.prototype = new SuperClass();
    SubClass.prototype.constructor = SubClass;

    var sub = new SubClass();
    console.log(sub.name);          //SuperClass的名称
    console.log(sub.info);          //SuperClass的信息
    sub.showInfo();                 //SuperClass的信息
    sub.showName();                 //SuperClass的名称

可以继承父类型中的原型属性|原型方法,以及实例属性和实例方法

原型链继承注意点

① 确定原型和实例的关系 instanceof + isPrototypeOf()

② 完成继承之后,不能使用字面量的方式来创建原型[因为会切断原型]

③ 注意重写原型对象的位置,必须先实现原型继承,然后再设置子对象的原型属性和原型方法

原型链继承存在的问题

① 在创建子类型的实例时,不能向父类型的构造函数中传递参数

② 父对象的实例属性会转换为子类型的原型属性,如果父类型的实例成员是引用类型则会存在共享问题

    //01 提供父对象的构造函数
    function SuperType() {
        //02 在构造函数中中设置实例属性,该属性为引用类型
        this.family = ['哥哥','姐姐','爸爸','妈妈'];
    };

    //03 提供子对象的构造函数
    function SubType() {};

    //04 设置原型继承
    SubType.prototype = new SuperType();

    //05 创建父对象构造函数的实例对象,并对内部的实例化属性进行修改
    var subDemo1 = new SubType();
    var subDemo2 = new SubType();

    alert(subDemo1.family);      //哥哥,姐姐,爸爸,妈妈
    alert(subDemo2.family);      //哥哥,姐姐,爸爸,妈妈

    subDemo1.family.push('爷爷','奶奶');
    alert(subDemo1.family);    //哥哥,姐姐,爸爸,妈妈,爷爷,奶奶
    alert(subDemo2.family);    //哥哥,姐姐,爸爸,妈妈,爷爷,奶奶

经典继承(借用构造函数

基本思想 在子类型构造函数的内部调用(需要借助call|apply方法)超类型|父类型构造函数

  //01 提供父类型(对象)的构造函数
    function SuperType(name) {
        //02 在构造函数中中设置实例属性,该属性为引用类型
        this.family = ['哥哥','姐姐','爸爸','妈妈'];
        //实例属性
        this.name = name;
    };

    SuperType.prototype.info = '父类型的原型属性';
    //03 提供子类型(对象)的构造函数
    function SubType() {
        //经典继承|借用构造函数|伪造对象继承
        //SuperType.call(this);

        //构造参数传递参数
        SuperType.call(this,'张老汉');
    };

    //04 创建父类型的实例对象,并对内部的实例化属性进行修改
    var subDemo1 = new SubType();
    var subDemo2 = new SubType();

    alert(subDemo1.info);        //undefined
    alert(subDemo1.family);      //哥哥,姐姐,爸爸,妈妈
    alert(subDemo2.family);      //哥哥,姐姐,爸爸,妈妈

    subDemo1.family.push('爷爷','奶奶');
    alert(subDemo1.family);    //哥哥,姐姐,爸爸,妈妈,爷爷,奶奶
    alert(subDemo2.family);    //哥哥,姐姐,爸爸,妈妈

    //测试构造函数传递参数
    alert(subDemo1.name);

经典继承又称为借用构造函数 | 伪造继承,这种方式能够继承父类型的实例属性,但是`无法继承父类型的原型属性和原型方法`。

经典继承的优点

① 可以在调用call方法的时候向构造函数传递参数

② 解决实例对象共享问题,通过调用父对象的构造函数来实现每个子类型(对象)的实例对象均拥有一份父类型实例属性和方法的副本。

经典继承存在的问题

① 冒充继承的方法无法实现函数的重用

② 无法继承父对象的原型属性和原型方法

组合继承(伪经典继承)

基本思想

> ① `使用原型链实现对原型属性和方法的继承`
> ② `通过伪造(冒充)构造函数来实现对实例属性的继承`

代码示例

//01 提供父类型的构造函数
function SuperType(name) {
    //在构造函数中中设置实例属性,该属性为引用类型
    this.family = ['哥哥','姐姐','爸爸','妈妈'];

    //实例属性
    this.name = name;
};

//原型方法
SuperType.prototype.showName = function () {
    console.log(this.name);
}

//02 提供子类型的构造函数
function SubType(name) {
    //冒充|伪造 构造参数传递参数
    SuperType.call(this,name);
};

SubType.prototype = SuperType.prototype;
//SubType.prototype = new SuperType();

//02 创建父类型的实例对象,并对内部的实例化属性进行修改
var subDemo1 = new SubType('张三');
var subDemo2 = new SubType('张四');

alert(subDemo1.family);    //哥哥,姐姐,爸爸,妈妈
alert(subDemo2.family);      //哥哥,姐姐,爸爸,妈妈

subDemo1.family.push('爷爷','奶奶');
alert(subDemo1.family);    //哥哥,姐姐,爸爸,妈妈,爷爷,奶奶
alert(subDemo2.family);    //哥哥,姐姐,爸爸,妈妈

//测试构造函数传递参数
subDemo1.showName();    //张三
subDemo2.showName();    //张四

三、基本包装类型

基本类型 字符串 + 数值 + null + undefined + 布尔值

为了便于操作基本类型,ECMAScript提供了三个特殊的引用类型:Boolean + Number + String

说明 上述类型和其他的引用类型类似,同时也具备与各自的基本类型相应的特殊行为,每当我们读取一个基本类型的值的时候,后台就会创建一个对应的基本包装类型对象,从而让我们能够调用一些方法来操作这些数据。

var str = '测试字符串';
console.log(str.length);            //5
console.log(str.substring(2));      //字符串

思考 属性和方法本是对象的特征,字符串如何能够拥有length属性以及其他类似subString等方法,内部怎么实现的?

基本类型值并不是对象,因此从逻辑上讨论他们不应该有属性和方法。 内部的具体处理

(1)创建String类型的一个实例对象
(2)在实例对象上面读取指定的属性(length),调用指定的方法(subString)
(3)销毁该对象

Number

Number是与数字值相对应的引用类型

创建Number类型的对象:var num = new Number(10);

String

String是字符串的对象包装类型

创建字符串类型的对象:var str = new String('hello World');

Boolean

Boolean是与布尔值对象的引用类型

可以通过调用Boolean构造函数传递参数来创建boolean类型的对象。var bool = new Boolean(true);

基本包装类型的代码示例

    //001 String
    var str = '测试字符串';
    console.log(str.length);            //5
    console.log(str.substring(2));      //字符串

    //002 Number
    var num = new Number(10);
    console.log(num);                   //Number {[[PrimitiveValue]]: 10}
    console.log(typeof num);            //object
    console.log(typeof 10);             //number

    //003 Boolean
    var bool = new Boolean(true);
    console.log(bool);                  //Boolean {[[PrimitiveValue]]: true}
    console.log(typeof bool);           //object
    console.log(typeof true);           //boolean

基本包装类型的注意点

对象还是基本数据类型值?

对象:通过new 调用构造函数创建出来的是对象
基本数据类型值:直接通过字面量方式赋值|通过省略new关键字调用构造函数方式创建的是基本数据类型值。

    var str1 = new String('hello');
    var str2 = 'hello';
    var str3 = String('hello');
    说明:以上代码中,str1是对象,而str2和str3是字符串(基本数据类型值)

相等问题

基本类型值判断相等 => 值相等

引用类型值判断相等 => 值相等且引用相等

对象是引用类型,因此在判断相等的时候有诸多的注意点和容易出错的地方。

    var str1 = '这是一个字符串';           //基本数据类型
    var str2 = String('这是一个字符串');   //基本数据类型
    console.log(str1 == str2);        //true 相等


    var str3 = new String('这是一个字符串');  //引用类型-对象
    console.log(str1 == str3);    //true    //值相等
    console.log(str2 == str3);    //true    //值相等

    console.log(str1 === str3);    //false  //值相等,但是引用不相等
    console.log(str2 === str3);    //false  //值相等,但是引用不相等

    //判断下面的变量是否相等
    var num1 = 10;                  //基本数据类型
    var num2 = new Number(10);      //对象
    console.log(num1 == num2);      //true
    console.log(num1 ===  num2);    //false

    var bool1 = true;
    var bool2 = new Boolean(true);
    console.log(bool1 == bool2);    //true
    console.log(bool1 === bool2);   //false

posted on 2022-12-10 00:09  文顶顶  阅读(15)  评论(0编辑  收藏  举报

导航