首页 > 技术文章 > js的创建对象的方法

MySweetheart 2020-07-28 17:25 原文

第一种:工厂模式

根据接收参数返回,包含参数的对象

优点:解决创建多个对象的问题

缺点:没法判断对象的类型

            function createPerson(name,age,job){
                var o = new Object();
                o.name = name;
                o.age = age;
                o.job = job;
                o.sayName = function(){
                    alert(this.name);
                }
                return o;
            }

第二种:构造函数模式

            function Person(name,age,job){
                this.name = name;
                this.age = age;
                this.job = job;
                this.sayName = function(){
                    alert(this.name);
                }
            }
            let p1 = new Person('bzw',20,'stu');
            console.log(p1 instanceof Person);//true
            console.log(p1.hasOwnProperty('name'));//true

构造函数模式与工厂模式区别:

1.没有显式的创建函数

2.直接将属性和方法赋值给this对象

3.没有return语句

构造函数定义:任何函数,只要可以通过new操作符来调用,那它就可以作为构造函数

以这种方式调用构造函数会经理以下几个步骤:

1.创建新对象

2.将作用域赋值给新对象

3.执行构造函数的代码

4.返回新对象

缺点:每个方法都要在实例化的对象上面重新创建一遍

第三种:原型模式(重点)

  想要知道原型模式,必须得知道什么叫做原型,每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象包括所有实例可以共享的属性和方法。

此外还需要知道什么叫做原型对象,还是那句话,只要创建一个函数,就会有一个prototype(原型)属性,而这个prototype恰恰指向这个函数的原型对象,这个原型对象包括所有实例可以共享的属性和方法。

每创建一个实例,这个实例会有一个[[prototype]]指针,专门指向构造函数的原型对象,每个原型对象又会有一个constuctor属性,它是一个指向prototype属性所在函数的指针

       function Person1(){
                
            }
            Person1.prototype.name = 'bzw';
            Person1.prototype.age = 20;
            Person1.prototype.job = 'stud';
            console.log(Person.prototype);//{constructor: ƒ}
            console.log(Person.prototype.constructor);//指向Person函数
            let p2 = new Person1();
            let p3 = new Person1();
            console.log(Person1.prototype.isPrototypeOf(p2));//true
            console.log(Person1.prototype.isPrototypeOf(p3));//true
            
            console.log(Object.getPrototypeOf(p2) === Person1.prototype);//true
            console.log(p2.hasOwnProperty('name'));//false

  in与hasOwnProperty的区别:
in会在实例化对象里面找有没有这个属性,如果没有就去它的原型里面去找,hasOwnProperty只会在实例化对象里去找有没有这个属性

            console.log('name' in p2);//true
            console.log(p2.hasOwnProperty('name'));//false                            

  我们可以写个函数专门用来判断某个对象只有原型里面有某个属性

            function hasPrototypeProperty(obj,key){
                return !obj.hasOwnProperty(key) && (key in obj);
            }
            console.log(hasPrototypeProperty(p2,'name'));//true
            p2.name = 10;
            console.log(hasPrototypeProperty(p2,'name'));//false

  给原型添加方法每次都需要写一个Person.prototype这样写太麻烦,我们可以下面这样写

            function Person2(){
                
            }
            Person2.prototype = {
                name:'bzw',
                age:20,
                job:'stu'
            }

  这样写挺方便但是,有个缺点就是原型对象的constructor会指向Object,这不是我们想看到的

             console.log(Person2.prototype.constructor);//Object()
        console.log(Object.keys(Person2.prototype))//["name", "age", "job"]

  我们可以指定它的constructor,这样就更改过来了,

            Person2.prototype = {
                constructor:Person2,//constructor的enumerable默认为false不可遍历,但是这种方法会让它变为true,即可以遍历
                name:'bzw',
                age:20,
                job:'stu'
            }

  这里可能大家有个问题,为什么这样要指定constructor了,因为我们前面说过,一个构造函数的constructor需要指向prototype属性的所在的函数

  我们可以打印一下的Person2.prototype.constructor

 console.log(Person2.prototype.constructor);//person2()

  结果如下:

  或者可以用下图来表达上述的关系

  

  当然这样写还是有点弊端

            //Object.keys()会获取对象中所有可以遍历的属性
            console.log(Object.keys(Person2.prototype))//["constructor", "name", "age", "job"]

  constructor竟然可以被遍历出来,这是不对的,我们发现这个constructor可以遍历,那么上面的方法就不是特别好,我们可以用下下面的方法,即指定了Person2的constructor指向问题,又解决constructor可以遍历的问题,可谓一举两得

            Object.defineProperty(Person2.prototype,'constructor',{
                enumerable:false,
                value:Person2
            });
            console.log(Object.keys(Person2.prototype))//["name", "age", "job"]
            console.log(Person2.prototype.constructor);//Person2();

  原型的动态性

  我们可以利用原型的动态性给原型添加属性和方法

        function Person3(){
                
            }
            let friend = new Person3();
            Person3.prototype.a = 1;
            console.log(friend.a);//1 即使实例已经创建好了, 但是当我给原型添加属性的时候,实例还是原型后面添加的方法
            Person3.prototype = {
                constructor:Person3,
                name:'bzw'
            }
            //实例化对象里面会有一个指针指向原型,而不是指向构造函数,这里把原型修改为另一个对象,就等于切断了最初原型与构造函数之间的关系
            console.log(friend instanceof Person3)//false
            console.log(friend.name)//undefined

下图可以反映上述的情况

  原型模式的缺点

            function Person4(){
                
            }
            Person4.prototype={
                constructor:Person4,
                name:[4],
            }
            let p4 = new Person4();
            console.log(p4.name);//[4]
            p4.name.push(5);
            let p5 = new Person4();
            console.log(p4.name,p5.name);//[4, 5],[4, 5]

  我们发现实例竟然可以对更改原型的值,这个不太好,于是就有其他的对象创建方法

第四种:组合构造函数和原型模式

       function Person5(name,age,job){
                this.name = name;
                this.age = age;
                this.job = job;
                this.friend = ['tom','jerry'];
               
            }
            Person5.prototype={
                constructor:Person5,
                sayName:function(){
                    console.log(this.name);
                }
            }
            let p6 = new Person5('bzw',20,'stu');
            let p7 = new Person5('bzw1',19,'stu1');
            p6.friend.push('bob');
            console.log(p6.friend);// ["tom", "jerry", "bob"]
            console.log(p7.friend);//["tom", "jerry"]

第五种:动态原型

            function Person6(name,age,job){
                this.name = name;
                this.age = age;
                this.job = job;
                this.friend = [];
                if(typeof this.sayName != 'function'){
                    Person6.prototype.sayName = function(){
                        return this.name;
                    }
                }
            }
            let p8 = new Person6('bzw2',20,'stu');
            p8.friend.push('v');
            console.log(p8.sayName())//bzw2
            let p9 = new Person6('bzw3',21,'stu');
            console.log(p8.friend,p9.friend);//["v"] []

  使用动态原型不能使用对象字面量来重写原型,否则会切断构造函数与原先原型的关系,也会切断实例与新原型的关系

第六种:寄生构造函数模式(和工厂模式很像)

特点:返回的对象与构造函数或者构造函数的原型属性之间没有关系,也就是说,构造函数返回的对象与在构造函数在外部创建的对象没有什么不同

            function Person7(name,age,job){
                let o = new Object();
                o.name = name;
                o.age = age;
                o.job = job;
                o.sayName = function(){
                    return this.name;
                }
                return o;
            }
            let p10 = new Person7('bzw1',20,'stu');
            console.log(p10.sayName());//bzw1

第七种:稳妥构造函数模式(适合于某些安全执行环境下,这些安全环境会禁用this和new)

          function Person8(name,age,job){
                let o = new Object();
                //定义私有变量和函数
                let sur = '姓名:';
                let sum = function(){
                    return sur+name;
                }
                o.sayName = function(){
                    return sum();
                }
                return o;
            }
            let p11 = Person8('bzw');
            console.log(p11.sayName(),sur);//姓名:bzw,sur is not defined

 

  

 

推荐阅读