04_JS面向对象

知识点

  • 传统构造函数存在的问题
  • 使用原型解决构造函数问题
  • 原型相关的概念
  • 原型的使用
  • 使用原型的注意事项
  • __proto__访问原型
  • 原型中的构造函数

传统构造函数存在的问题

发现问题

现有构造函数如下:

function Person(name, age){
    this.name = name;
    this.age = age;
    this.sayHi = function(){
        console.log("你好");
    }
}

调用该构造函数创建对象,并对比创建出来的对象的sayHi方法:

var p = new Person("张三", 18);
var p1 = new Person("李四", 19);
console.log(p.sayHi == p1.sayHi); //输出结果为false

注意:如果构造函数没有参数,那么在调用的时候 () 可以省略

由于每个对象都是由new Person创建出来的,因此每创建一个对象,函数sayHi都会被重新创建一次,这个时候,每个对象都拥有一个独立的,但是功能完全相同的方法。


功能相同的函数,完全没有必要再内存中存在这么多份。所以就造成了资源浪费。

解决问题

这里最好的办法就是将函数体放在构造函数之外. 在构造函数中只需要引用该函数即可。

function sayHello(){
    console.log("你好");
}

function Person(name, age){
    this.name = name;
    this.age = age;
    this.sayHi = sayHello;
}

//调用该构造函数创建对象,并对比创建出来的对象的sayHi方法
var p = new Person("张三", 18);
var p1 = new Person("李四", 19);
console.log(p.sayHi == p1.sayHi); //输出结果为true

这样写依然存在问题:

  • 全局变量增多,会增加引入框架命名冲突的风险
  • 代码结构混乱,会变得难以维护

<script type="text/javascript">
    function Animal(){
        this.name = "dongwu";
        this.sayHello = function () {
            console.log("Mie mie");
        }
    }
    var a1 = new Animal;
    a1.sayHello();

    function Person(name,age){
        this.name = name;
        this.age = age;
        this.sayHello = function () {
            console.log("Good Good Study,Day Day Up!");
        }
    }

    var p1 = new Person("zhangsan",24);
    var p2 = new Person("lisi",25);
//    console.log(p1);
    console.log(p1.sayHello == p2.sayHello);//false


    //解决构造函数资源浪费的问题
    function sayHello(){
        //使用这种方式写好的方法中的this指向的就是调用该方法的对象,Person2的对象p3,p4
        //this 谁调用就是谁
        console.log("Good Good Study,Day Day Up2!===>"+this.name);
    }

    function Person2(name,age,func){
        this.name = name;
        this.age = age;
        this.sayHello = func;
    }

    var p3 = new Person2("zhangsan",24,sayHello);
    var p4 = new Person2("lisi",25,sayHello);
    console.log(p3.sayHello == p4.sayHello);//true
    p3.sayHello();
    p4.sayHello();

    //1,如果在构造函数中定义函数,那么每次创建对象,都会重新创建该函数,但是函数内部代码完全相同,就造成了资源浪费
    //2,为了处理这个问题,我们要让所有的对象共用一个方法,在构造函数外部定义好该函数,将该函数赋值给构造函数内的方法


    //使用这种方式存在的问题
    //1.全局变量增多,造成污染
    //2.代码结构混乱,不易维护

</script>

使用原型解决构造函数问题

关键点

  • 每一个函数在定义的时候,都会有跟它关联的一个对象被创建出来
  • 每一个由构造函数创建出来的对象,都会默认的和构造函数的神秘对象关联
  • 当使用一个方法进行属性或者方法访问的时候,会先在当前对象内查找该属性和方法
  • 如果当前对象内未找到,就回去跟它关联的神秘对象内进行查找
function Person(name, age){
    this.name = name;
    this.age = age;
    this.sayHi = function(){
        console.log("Hello!");
    };
}

var p = new Person("张三", 18);
p.sayHi(); //当前对象内有这个方法,所以不会去神秘对象内进行查找
var p1 = new Person("李四", 19);
p1.sayHello(); //当前对象没没有找到这个方法,所以去神秘对象内进行查找

问题来了,如何访问到这个神秘对象呢?

//可以通过 构造函数.prototype 访问这个神秘对象
console.log(Person.prototype);

当尝试给这个对象新增一个方法之后:

Person.prototype.sayHello = function(){
    console.log("我是神秘对象中的方法");
};

使用p,p1都可以访问这个方法:

p.sayHello();
p1.sayHello();

总结:
所有对象共享神秘对象(构造函数.prototype)内的属性和方法。

    <script type="text/javascript">
        function Person(name,age,gender){
            this.name = name;
            this.age = age;
            this.gender = gender;
            this.sayHello = function () {
                console.log("你好,我是"+this.name);
            }
        }
        var p1 = new Person("刘德华",44,"男");
        var p2 = new Person("张学友",42,"男");

        Person.prototype.myHello = function () {
            console.log("你好,"+this.name);
        }
        Person.prototype.sing = function () {
            console.log("一千个伤心的流油");
        }

        p1.sayHello();
        p2.sayHello();

        p1.myHello();
        p2.myHello();

        p1.sing();
        p2.sing();

        //如何使用原型来解决构造函数存在的问题?
        //构造函数的原型对象中的成员,可以被该构造函数创建出来的所有对象访问
        //而且,所有的对象共享该对象
        //所以,我们可以将构造函数中需要创建的函数,放到原型对象中存储
        //这样就解决 全局变量污染的问题 以及 代码结构混乱的问题
    </script>

解决方案

既然所有对象共享神秘对象(构造函数.prototype)内的属性和方法。我们只需要将需要共享的东西,也就是重复占用内存的东西,全部都放到 神秘对象(构造函数.prototype)中,那么所有对象就都可以使用,并且内存里面也只有一份了。
改造构造函数

function Person(name, age){
    this.name = name;
    this.age = age;
}

Person.prototype.sayHi = function(){
    console.log("你好");
};

//测试
var p = new Person("张三", 18);
var p1 = new Person("李四", 19);

console.log(p.sayHi == p1.sayHi); //输出true

常见的错误

  • 将属性写在神秘对象(构造函数.prototype)内
function Car(name){
     this.name = name;
}

function Person() {}

Person.prototype.name = '张三'; //基本类型的属性影响不大
Person.prototype.car = new Car("法拉利"); //引用类型的属性,会被所有的对象共享
var p = new Person();
  • 赋值的错误
function Person() {}
Person.prototype.name = '张三';
var p1 = new Person();
var p2 = new Person();
p1.name = '李四';
console.log( p1.name );
console.log( p2.name );

// 如果是访问数据, 当前对象中如果没有该数据就到构造函数的原型属性中去找
// 如果是写数据, 当对象中有该数据的时候, 就是修改值; 如果对象没有该数据, 那么就添加值

原型相关的概念

神秘对象称与构造函数

  • 神秘对象就是构造函数的 "原型属性"
  • 简称原型 (构造函数的原型)

神秘对象与构造函数所创建出来的对象

  • 神秘对象针对构造函数创建出来的对象称为 "原型对象"
  • 简称原型 (对象的原型)



<script type="text/javascript">
    function Person(name,status){
        this.name = name;
        this.status = status;
        this.act = function () {
            console.log("演戏");
        }
        this.exceise = function () {
            console.log("可以不强身健体,当一定要保卫祖国。");
        }
    }
    var p1 = new Person("安吉丽娜","actor");
    //原型是个什么玩意儿?
    //在构造函数创建出来的时候,系统会默认的帮构造函数创建并关联一个神秘的对象,这个对象就是原型
    //原型默认的是一个空的对象

    //原型的作用
    //原型中的属性和方法 可以被使用该构造函数创建出来的对象 使用

    //如何访问构造函数的原型
    // 构造函数.prototype
    console.log(Person.prototype);
    //注意 prototype是构造函数的属性,跟对象没有关系
    console.log(p1.prototype);//undefined

    //如何给原型对象添加属性和方法?
    //使用对象的动态特性
    Person.prototype.exceise = function () {
        console.log("强身健体,保卫祖国。");
    }

    //当使用对象去访问属性和方法的时候
    //1,会首先在对象自己内部进行查找,如果找到了,就直接使用
    //2,如果没有找到,就去原型中查找,查找到就使用原型中属性和方法
    //3,如果原型中还没有,
    //  如果是属性,就是Undefined
    //  如果是方法,就报错
    p1.exceise();
    console.log(p1.prop);//undefined
    p1.sing();//报错,p1.sing is not a function
</script>

原型继承

  • 构造函数创建的对象 继承自 构造函数的原型属性
  • 构造函数创建的对象 继承自 该对象的原型对象
  • 原型中的成员, 可以直接被实例对象所使用
  • 实例对象直接 "含有" 原型中的成员
  • 因此实例对象 继承自 原型
  • 这样的继承就是 "原型继承"

原型的使用

  • 使用对象的动态特性
function Person () { }
Person.prototype.func = function () {
 console.log( 'something' );
};
var p = new Person();
p.func();
  • 直接替换原型对象
function Person () { };
Person.prototype = {
    func: function () {
        console.log( '22222' );
    }
};
var p = new Person();
p.func();
  • 直接替换原型会出现的问题
function Person () { }
Person.prototype.func = function () {
    console.log( 'something' );
};
var p = new Person();
Person.prototype.func = function () {
    console.log( 'something' );
};
var p1 = new Person();
p.func();
p1.func();

替换原型之后,在替换前创建出来的对象和替换后创建出来的对象的原型对象不一致

<script type="text/javascript">
    function Person(){
        this.name = "安吉丽娜朱莉";
    }
    //原型的使用
    //1.    利用动态的特性
    Person.prototype.sayHello = function () {
        console.log("就是一个招呼打过去");
    }
    var p1 = new Person;
    //2.    直接替换原型对象
    Person.prototype = {
        msg:"你猜你猜猜",
    }
    var p2 = new Person;

    console.log(p1.msg);//undefined
    console.log(p2.msg);//"你猜你猜猜"

    p1.sayHello();//正常执行,打印:"就是一个招呼打过去"
    p2.sayHello();//报错, p2.sayHello is not a function

    //第二种方式:
    //  在替换原型之前创建的对象和替换原型之后创建的对象的原型对象不是同一个
</script>

使用原型的注意事项

  • 原型的属性是应用类型
    那么所有的对象共享该属性,并且一个对象修改了该引用对象属性,那么其他的对象也会受影响

<script type="text/javascript">
    //使用原型的注意事项
    //  1,使用对象访问属性和方法的时候,如果在本身找不到就会去原型中查找
    //  2,但是使用点语法进行属性赋值的时候,并不会去原型中进行查找
    //  3,使用点语法进行赋值的时候,如果对对象中不存在属性,就会给该对象新增该属性

    function Person(){

    }
    Person.prototype.name = "斯琴高娃";
    Person.prototype.age = 24;

    var p1 = new Person();

    console.log(p1.name);
    p1.actor = "演员";

    //actor是p1对象属性
    console.log(p1.actor);//演员
    console.log(p1);//Person {actor: "演员"}

    var car = {
        brand:"劳斯莱斯",
        price:"unknown",
    }
    Person.prototype.car = car;
    console.log(p1.car);//Object {brand: "劳斯莱斯", price: "unknown"}

    p1.car.brand = "宾利";
    var p2 = new Person();
    console.log(p2.car);//Object {brand: "宾利", price: "unknown"}

    //如果在原型中的属性是引用类型
    //  1,那么所有的对象共享该属性,并且一个对象修改了该引用对象属性,那么其他的对象也会受影响
    //  2,一般情况下不会把属性放入原型的对象当中
    //  3,一般情况下原型中放的是需要共享的方法
</script>

__proto__访问原型

<script type="text/javascript">
    function Person(){

    }
    var p1 = new Person();
    console.log(p1.name);
    //1,通过构造函数访问原型
    Person.prototype.name = "你好";
    console.log(p1.name);
    var p2 = new Person();
    
    //2,通过对象访问属性
    //  __proto__属性
    //  __proto__是一个非标准的属性
    console.log(p1.__proto__ === p2.__proto__);//true
    Person.prototype = {
        name:"hello"
    };
    var p3 = new Person();
    console.log(p1.__proto__ === p3.__proto__);//false
</script>

原型中的构造函数

  • 原型对象在创建出来的时候,会默认的有一个constructor属性
<script type="text/javascript">
    function Person(name,age,gender){
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    var p1 = new Person("张学友","18","male");
    console.log(p1);

    var p2 = new p1.constructor("刘德华","20","male");//相当于直接使用 new Person()
    console.log(p2);

    //原型对象在创建出来的时候,会默认的有一个constructor属性
    //指向对应的构造函数
    //        var o = Person.prototype;
    //        Person.prototype.constructor
    console.log(Person.prototype);
</script>
  • 使用新的对象替换掉默认的原型对象之后原型的构造函数
<script type="text/javascript">
    var obj = {
        name:"hh"
    }
    console.log(obj.constructor);//function Object() { [native code] }

    function Person(){}
    console.log(Person.prototype.constructor);//function Person(){}
    Person.prototype = {};
    console.log(Person.prototype.constructor);//function Object() { [native code] }

    //在使用新的对象替换掉默认的原型对象之后
    //原型对象中的constructor属性变成object
    //为了保证整个 构造函数 --- 原型 --- 对象 之间合理性,应做如下操作:
    //在替换原型对象的时候,在新的原型对象中手动添加 constructor 属性
    Person.prototype = {
        constructor:function Person(){}
    };
    console.log(Person.prototype.constructor);
</script>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容