Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript深入之创建对象的多种方式以及优缺点 #15

Open
mqyqingfeng opened this issue May 10, 2017 · 65 comments
Open

JavaScript深入之创建对象的多种方式以及优缺点 #15

mqyqingfeng opened this issue May 10, 2017 · 65 comments

Comments

@mqyqingfeng
Copy link
Owner

mqyqingfeng commented May 10, 2017

写在前面

这篇文章讲解创建对象的各种方式,以及优缺点。

但是注意:

这篇文章更像是笔记,因为《JavaScript高级程序设计》写得真是太好了!

1. 工厂模式

function createPerson(name) {
    var o = new Object();
    o.name = name;
    o.getName = function () {
        console.log(this.name);
    };

    return o;
}

var person1 = createPerson('kevin');

缺点:对象无法识别,因为所有的实例都指向一个原型

2. 构造函数模式

function Person(name) {
    this.name = name;
    this.getName = function () {
        console.log(this.name);
    };
}

var person1 = new Person('kevin');

优点:实例可以识别为一个特定的类型

缺点:每次创建实例时,每个方法都要被创建一次

2.1 构造函数模式优化

function Person(name) {
    this.name = name;
    this.getName = getName;
}

function getName() {
    console.log(this.name);
}

var person1 = new Person('kevin');

优点:解决了每个方法都要被重新创建的问题

缺点:这叫啥封装……

3. 原型模式

function Person(name) {

}

Person.prototype.name = 'keivn';
Person.prototype.getName = function () {
    console.log(this.name);
};

var person1 = new Person();

优点:方法不会重新创建

缺点:1. 所有的属性和方法都共享 2. 不能初始化参数

3.1 原型模式优化

function Person(name) {

}

Person.prototype = {
    name: 'kevin',
    getName: function () {
        console.log(this.name);
    }
};

var person1 = new Person();

优点:封装性好了一点

缺点:重写了原型,丢失了constructor属性

3.2 原型模式优化

function Person(name) {

}

Person.prototype = {
    constructor: Person,
    name: 'kevin',
    getName: function () {
        console.log(this.name);
    }
};

var person1 = new Person();

优点:实例可以通过constructor属性找到所属构造函数

缺点:原型模式该有的缺点还是有

4. 组合模式

构造函数模式与原型模式双剑合璧。

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

Person.prototype = {
    constructor: Person,
    getName: function () {
        console.log(this.name);
    }
};

var person1 = new Person();

优点:该共享的共享,该私有的私有,使用最广泛的方式

缺点:有的人就是希望全部都写在一起,即更好的封装性

4.1 动态原型模式

function Person(name) {
    this.name = name;
    if (typeof this.getName != "function") {
        Person.prototype.getName = function () {
            console.log(this.name);
        }
    }
}

var person1 = new Person();

注意:使用动态原型模式时,不能用对象字面量重写原型

解释下为什么:

function Person(name) {
    this.name = name;
    if (typeof this.getName != "function") {
        Person.prototype = {
            constructor: Person,
            getName: function () {
                console.log(this.name);
            }
        }
    }
}

var person1 = new Person('kevin');
var person2 = new Person('daisy');

// 报错 并没有该方法
person1.getName();

// 注释掉上面的代码,这句是可以执行的。
person2.getName();

为了解释这个问题,假设开始执行var person1 = new Person('kevin')

如果对 new 和 apply 的底层执行过程不是很熟悉,可以阅读底部相关链接中的文章。

我们回顾下 new 的实现步骤:

  1. 首先新建一个对象
  2. 然后将对象的原型指向 Person.prototype
  3. 然后 Person.apply(obj)
  4. 返回这个对象

注意这个时候,回顾下 apply 的实现步骤,会执行 obj.Person 方法,这个时候就会执行 if 语句里的内容,注意构造函数的 prototype 属性指向了实例的原型,使用字面量方式直接覆盖 Person.prototype,并不会更改实例的原型的值,person1 依然是指向了以前的原型,而不是 Person.prototype。而之前的原型是没有 getName 方法的,所以就报错了!

如果你就是想用字面量方式写代码,可以尝试下这种:

function Person(name) {
    this.name = name;
    if (typeof this.getName != "function") {
        Person.prototype = {
            constructor: Person,
            getName: function () {
                console.log(this.name);
            }
        }

        return new Person(name);
    }
}

var person1 = new Person('kevin');
var person2 = new Person('daisy');

person1.getName(); // kevin
person2.getName();  // daisy

5.1 寄生构造函数模式

function Person(name) {

    var o = new Object();
    o.name = name;
    o.getName = function () {
        console.log(this.name);
    };

    return o;

}

var person1 = new Person('kevin');
console.log(person1 instanceof Person) // false
console.log(person1 instanceof Object)  // true

寄生构造函数模式,我个人认为应该这样读:

寄生-构造函数-模式,也就是说寄生在构造函数的一种方法。

也就是说打着构造函数的幌子挂羊头卖狗肉,你看创建的实例使用 instanceof 都无法指向构造函数!

这样方法可以在特殊情况下使用。比如我们想创建一个具有额外方法的特殊数组,但是又不想直接修改Array构造函数,我们可以这样写:

function SpecialArray() {
    var values = new Array();

    for (var i = 0, len = arguments.length; i < len; i++) {
        values.push(arguments[i]);
    }

    values.toPipedString = function () {
        return this.join("|");
    };
    return values;
}

var colors = new SpecialArray('red', 'blue', 'green');
var colors2 = SpecialArray('red2', 'blue2', 'green2');


console.log(colors);
console.log(colors.toPipedString()); // red|blue|green

console.log(colors2);
console.log(colors2.toPipedString()); // red2|blue2|green2

你会发现,其实所谓的寄生构造函数模式就是比工厂模式在创建对象的时候,多使用了一个new,实际上两者的结果是一样的。

但是作者可能是希望能像使用普通 Array 一样使用 SpecialArray,虽然把 SpecialArray 当成函数也一样能用,但是这并不是作者的本意,也变得不优雅。

在可以使用其他模式的情况下,不要使用这种模式。

但是值得一提的是,上面例子中的循环:

for (var i = 0, len = arguments.length; i < len; i++) {
    values.push(arguments[i]);
}

可以替换成:

values.push.apply(values, arguments);

5.2 稳妥构造函数模式

function person(name){
    var o = new Object();
    o.sayName = function(){
        console.log(name);
    };
    return o;
}

var person1 = person('kevin');

person1.sayName(); // kevin

person1.name = "daisy";

person1.sayName(); // kevin

console.log(person1.name); // daisy

所谓稳妥对象,指的是没有公共属性,而且其方法也不引用 this 的对象。

与寄生构造函数模式有两点不同:

  1. 新创建的实例方法不引用 this
  2. 不使用 new 操作符调用构造函数

稳妥对象最适合在一些安全的环境中。

稳妥构造函数模式也跟工厂模式一样,无法识别对象所属类型。

下一篇文章

JavaScript深入之继承的多种方式和优缺点

相关链接

《JavaScript深入之从原型到原型链》

《JavaScript深入之new的模拟实现》

《JavaScript深入之call和apply的模拟实现》

深入系列

JavaScript深入系列目录地址:https://github.com/mqyqingfeng/Blog

JavaScript深入系列预计写十五篇左右,旨在帮大家捋顺JavaScript底层知识,重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。

@xumengzi
Copy link

xumengzi commented Jun 8, 2017

消灭零回复!

@mqyqingfeng
Copy link
Owner Author

@JarvenIV 哈哈,(๑•̀ㅂ•́)و✧

@liuxinqiong
Copy link

感觉特么都有缺点啊

@mqyqingfeng
Copy link
Owner Author

@liuxinqiong 确实如此哈,一般都是用组合模式

@ry928330
Copy link

@mqyqingfeng 你好,想请问你两个问题:一个是:4.1动态原型的模式,“使用字面量方式直接覆盖 Person.prototype,并不会更改实例的原型的值”这是为什么?另一个是5.1 寄生构造函数模式,在调用的时候和工厂模式相比,就多了一个new调用,这个有什么用?

@mqyqingfeng
Copy link
Owner Author

@ry928330

function Person(name) {
    this.name = name;
    if (typeof this.getName != "function") {
        Person.prototype = {
            constructor: Person,
            getName: function () {
                console.log(this.name);
            }
        }
    }
}

var person1 = new Person('kevin');

以这个例子为例,当执行 var person1 = new Person('kevin') 的时候,person1.的原型并不是指向 Person.prototype,而是指向 Person.prototype 指向的原型对象,我们假设这个原型对象名字为 O, 然后再修改 Person.prototype 的值为一个字面量,只是将一个新的值赋值给 Person.prototype, 并没有修改 O 对象,也不会切断已经建立的 person1 和 O 的原型关系,访问 person.getName 方法,依然会从 O 上查找

@mqyqingfeng
Copy link
Owner Author

mqyqingfeng commented Aug 29, 2017

@ry928330 关于寄生构造函数模式的使用,例子中也有讲到,你可以理解为这个人就是想用 new 的方式而不是直接调用函数 😂

@ry928330
Copy link

@mqyqingfeng 那现在怎么通过Person再找回原来的prototype(也就是O)呢,因为像这样
function Person(name) {
this.name = name;
}
var person1 = new Person('kevin');
person1.proto === Person.prototype //true
现在给Person.prototype重新赋值为一个字面量后,person1.proto === Person.prototype 肯定是false,那还能从Person这个构造函数(对象),找到原来的prototype么(也就是O)?

1 similar comment
@ry928330
Copy link

@mqyqingfeng 那现在怎么通过Person再找回原来的prototype(也就是O)呢,因为像这样
function Person(name) {
this.name = name;
}
var person1 = new Person('kevin');
person1.proto === Person.prototype //true
现在给Person.prototype重新赋值为一个字面量后,person1.proto === Person.prototype 肯定是false,那还能从Person这个构造函数(对象),找到原来的prototype么(也就是O)?

@mqyqingfeng
Copy link
Owner Author

@ry928330 不能啦~

@qujsh
Copy link

qujsh commented Nov 13, 2017

function Person(name) {
    this.name = name;
    if (typeof this.getName != "function") {
        Person.prototype = {
            constructor: Person,
            getName: function () {
                console.log(this.name);
            }
        }
    }
}

var person1 = new Person('kevin');

当执行 var person1 = new Person('kevin') 的时候,person1.的原型并不是指向 Person.prototype,而是指向 Person.prototype 指向的原型对象

对这句话其实不是很理解,为什么指向了Person.prototype指向的原型对象?
还有上文的一句话:
回顾下 apply 的实现步骤,会执行 obj.Person 方法,这个时候就会执行 if 语句里的内容,注意构造函数的 prototype 属性指向了实例的原型。
我脑子里的印象还是,person1.__proto__ = Person.prototype;

@mqyqingfeng
Copy link
Owner Author

@qujsh 原型也是一个对象,我们假设这个对象叫做 O,看这个例子:

var a = {
     b: O
}

你看 a.b 指向了 O 对象,就相当于 Person.prototype 指向了原型对象这句话。

再看这个例子:

function Person(name) {
    this.name = name;
    if (typeof this.getName != "function") {
        Person.prototype = {
            constructor: Person,
            getName: function () {
                console.log(this.name);
            }
        }
    }
}

var person1 = new Person('kevin');

当 new Person() 的时候,是先建立的原型关系,即 person .__proto__ = Person.prototype,而后修改了 Person.prototype 的值,这就相当于:

// O 表示原型对象
var O = {};

var a = {
     b: O
}

先建立原型关系,指的是 c.__proto__ = a.b = O

而后修改 Person.prototype 的值,相当于

var anotherO = {};
a.b = anotherO;

即便修改了 Person.prototype 的值,但是 c.__proto__ 还是指向以前的 O

不知道这样解释的清不清楚,欢迎交流~

@yan919
Copy link

yan919 commented Nov 18, 2017

请问组合模式哪些方法和属性写在prototype,哪些写在构造函数里

@mqyqingfeng
Copy link
Owner Author

@monkeySmoke 共享的写在 prototype 中,独立的写在构造函数中。

我们以弹窗组件举个例子:

function Dialog(options) {
   this.options = options;
}

Dialog.prototype.show = function(){...}

如果我们一个页面用到多个 dialog:

var dialog1 = new Dialog({value: 1});
var dialog2  = new Dialog({value: 2});

dialog1 和 dialog2 传入的参数不一样,写在构造函数中,我们可以通过

console.log(dialog1.options) 访问 dialog1 的配置选项

而对于 show 方法而言,所以的 dialog 都是公用的,所以写在 prototype 属性中

@MagicHacker
Copy link

MagicHacker commented Feb 5, 2018

第二个原型模式优化的内容,如果把新建对象放在用对象字面量重写原型对象之前,再调用原型上的方法就会报错。

function Person(){}

var person_1 = new Person();

Person.prototype = {
   constructor:Person,
   name:'Joe',
   getName : function(){
      console.log(this.name);
   }
}
person_1.getName(); //error

@mqyqingfeng
Copy link
Owner Author

@MagicHacker 这是肯定的呀,因为当你第一次 new 的时候,person_1 的原型指向了 Person.prototype 所指向的那个对象,后来再修改 Person.prototype,只会使得以后 new 的对象指向这个新的 Person.prototype,已经 new 的不会再发生改变~

@geekzhanglei
Copy link

谢谢作者。关于重写原型的问题,还是要好好看js高程p156-“原型的动态性”一节的,其中P157图6-3对更好理解字面量重写原型导致的原型链变化有帮助,结合图和new操作符的第二步与重写原型的先后关系就理解了这个问题。

@Tan90Qian
Copy link

在《你不知道的JavaScript(上卷)》中,提到了一种被作者称为“面向委托”的设计思路,即:

Foo = {
  init: function(who) {
  this.me = who; },
  identify: function() {
    return "I am " + this.me;
  } 
};
Bar = Object.create( Foo );
Bar.speak = function() {
  alert( "Hello, " + this.identify() + "." );
};
var b1 = Object.create( Bar );
b1.init( "b1" );
var b2 = Object.create( Bar );
b2.init( "b2" );
b1.speak();
b2.speak();

作者认为在js中不存在“类”且“原型链”是引用而不是复制的情况下,强行使用“类”的设计思路会导致一些的问题,然后使用了这种“纯对象”的形式。
从作者给的思维模型图上看,似乎这种设计模式更加简洁易懂,为何不管是日常开发还是面试中,似乎都没见过这种风格的?它有什么致命缺陷么?还是要调整整个团队的习惯得不偿失?

@jgchenu
Copy link

jgchenu commented Jun 5, 2018

从深入第一篇看到这里即将完结,必须给大大点个赞!狂赞一下!写得太好看了,我在坐地铁还有睡觉前,手机微信网页都是置顶大大的博客来看的❤️❤️❤️

@easyhaloo
Copy link

博主我可以不可以这样理解person1.proto = Person.prototype 这句话就相当于person1.proto 指向了Person.prototype 指向的一块区域,当Person.prototype 以字面量的形式更改的时候,原来的那块区域并没有发生变化。感觉之前看:
person1.的原型并不是指向 Person.prototype,而是指向 Person.prototype 指向的原型对象
这句话都有点迷糊,这样理解是不是要好点

@alex1504
Copy link

alex1504 commented Jun 21, 2018

@qujsh

和你一样,一开始也觉得这句话是在难以理解。

“当执行 var person1 = new Person('kevin') 的时候,person1.的原型并不是指向 Person.prototype,而是指向 Person.prototype 指向的原型对象“

后来想想,其实重点在js创建一个对象时是先建立原型关系,而后执行构造函数才是这个问题的核心。

“当 new Person() 的时候,是先建立的原型关系,即 person .proto = Person.prototype,而后修改了 Person.prototype 的值”

画图理解一下就很清晰:
bb.png

初始先存在constructor关系线和1关系线。

var person1 = new Person('kevin');

由于new Person()先建立原型关系导致了关系2的生成,然后覆盖了构造函数指向了原型之后,关系1移除,关系3生成。

var person2 = new Person('daisy');

在person1生成后由于关系3替代了关系1,所以在person2生成时同时生成了关系4(即person2的__proto__指向了新的原型对象),所以person2能找到新的原型对象上的方法。

当person1和person2生成后的最终关系图
nn.png

@Xing-He
Copy link

Xing-He commented Sep 17, 2018

@mqyqingfeng 不知道对于这个 示例 4.1 的理解是否正确,还望能指出

function Person(name) {
    this.name = name;
    if (typeof this.getName != "function") {
        Person.prototype = {
            constructor: Person,
            getName: function () {
                console.log(this.name);
            }
        }
    }
}

var person1 = new Person('kevin');
var person2 = new Person('daisy');

// 报错 并没有该方法
person1.getName();

// 注释掉上面的代码,这句是可以执行的。
person2.getName();

个人对于原文这个示例的理解:

讨论中有个示例图,解释的比较清楚的一点是:js创建一个对象时是 先建立原型关系,而 后执行构造函数
那么在 第一个 var person1= new Person('Kevin') 调用的时候,函数(类)的 Person.prototype 还并没有被修改,然后再执行类似 Person.apply(obj) 的操作,在这个apply操作中,构造被执行,那么 if 里边的内容被执行,然后 Person.prototype 才被修改,指向新的一个字面量对象,
重点是,这个时候 person1 的原型还是指向的被 修改之前Person.prototype,而在第二次 var person2 = new Person('Daisy') 的时候,Person.prototype 已经被修改,因此 person1 原型上是没有 getName,而 person2 可以正常调用

@itsuki0927
Copy link

动态原型模式为什么不能用字面量直接赋值,以下是我的理解:
举一个简单的例子:

let obj = {
    name: "obj"
};
let per = obj;
obj = {
    age: 20
};
// obj { age:20 }
// per { name:"obj" }

引用《你不知道的JS》中的一句话,更好的进行理解:

JavaScript中引用指向的是值。
简单值(null、undefined、字符串、数字、布尔和 ES6 中的 symbol)是通过值的复制。
而对象和函数则是通过引用的复制。

所以在上述例子中:
perobj都指向了值 { name:'obj' },然后又将obj重新赋值,由于引用指向的是值本身,也就是值{ name:'obj' },而不是perobj变量,所以obj的重新赋值无法更改per引用的指向,所以结果如最后所示。

回到这个例子当中:
假设 Person.prototype就是上述例子的obj,new出来的实例对象是per

  1. new 操作符会在内部绑定其对应的原型对象就是类似于上述例子中per = obj
  2. 然后在函数中给Person.prototype进行重新赋值,类似于上述例子中的obj = { age:10 }
  3. 此时实例对象per的指向的原型对象还是没有变的,它还是原来的那个{ name:"obj" },在上面没有getName这个方法,所以就会出现getName is not defined的异常。

@qiqingfu
Copy link

@mqyqingfeng @mqyqingfeng 你好,想请问你两个问题:一个是:4.1动态原型的模式,“使用字面量方式直接覆盖 Person.prototype,并不会更改实例的原型的值”这是为什么?另一个是5.1 寄生构造函数模式,在调用的时候和工厂模式相比,就多了一个new调用,这个有什么用?

4.1动态原型的模式

let obj = {
        a: 1
    }

  let prosen = obj
  obj = {
    a: 2
  }
      console.log(prosen) // {a: 1}
      console.log(obj) // {a: 2}

在堆内存中又开辟了一片空间, 切断了和之前的引用关系吗?

@superwtt
Copy link

@qujsh

和你一样,一开始也觉得这句话是在难以理解。

“当执行 var person1 = new Person('kevin') 的时候,person1.的原型并不是指向 Person.prototype,而是指向 Person.prototype 指向的原型对象“

后来想想,其实重点在js创建一个对象时是先建立原型关系,而后执行构造函数才是这个问题的核心。

“当 new Person() 的时候,是先建立的原型关系,即 person .proto = Person.prototype,而后修改了 Person.prototype 的值”

画图理解一下就很清晰:
bb.png

初始先存在constructor关系线和1关系线。

var person1 = new Person('kevin');

由于new Person()先建立原型关系导致了关系2的生成,然后覆盖了构造函数指向了原型之后,关系1移除,关系3生成。

var person2 = new Person('daisy');

在person1生成后由于关系3替代了关系1,所以在person2生成时同时生成了关系4(即person2的__proto__指向了新的原型对象),所以person2能找到新的原型对象上的方法。

当person1和person2生成后的最终关系图
nn.png

谢谢大佬的图,一下子理解了,但还是想问一下:
第一次new Person()生成关系网络的时候,this指代的是实例person1, 这个时候进入判断语句this.getName相当于person1.getName,为什么打印是undefined,而下面person1.getName()的时候直接报错?
我理解的是,程序在进入if语句的时候,实例找不到getName属性就应该直接报错,而不是变成undefined
求问大神怎么理解。

@luohong123
Copy link

《JavaScript高级程序设计》得反反复复看啊,一看就会,一写就忘 😄

@OldDream
Copy link

OldDream commented Apr 2, 2020

”这是为什么

第一个问题,我认为用类似C的指针来理解比较容易,虽然js里面似乎没有指针的概念。
构造函数中的prototype指向内存地址A,而Person.prototype = {} 赋值后指向地址B,两者其实不会相互影响。

@zhtzjz
Copy link

zhtzjz commented Jun 25, 2020

看到作者这边文章,拿起了我尘封多年的javascript高级程序设计

@xsfxtsxxr
Copy link

寄生构造函数模式和工厂模式有啥区别,就是一个用new,一个不用new?

@puck1006
Copy link

@qujsh
和你一样,一开始也觉得这句话是在难以理解。
“当执行 var person1 = new Person('kevin') 的时候,person1.的原型并不是指向 Person.prototype,而是指向 Person.prototype 指向的原型对象“
后来想想,其实重点在js创建一个对象时是先建立原型关系,而后执行构造函数才是这个问题的核心。
“当 new Person() 的时候,是先建立的原型关系,即 person .proto = Person.prototype,而后修改了 Person.prototype 的值”
画图理解一下就很清晰:
bb.png
初始先存在constructor关系线和1关系线。

var person1 = new Person('kevin');

由于new Person()先建立原型关系导致了关系2的生成,然后覆盖了构造函数指向了原型之后,关系1移除,关系3生成。

var person2 = new Person('daisy');

在person1生成后由于关系3替代了关系1,所以在person2生成时同时生成了关系4(即person2的__proto__指向了新的原型对象),所以person2能找到新的原型对象上的方法。
当person1和person2生成后的最终关系图
nn.png

谢谢大佬的图,一下子理解了,但还是想问一下:
第一次new Person()生成关系网络的时候,this指代的是实例person1, 这个时候进入判断语句this.getName相当于person1.getName,为什么打印是undefined,而下面person1.getName()的时候直接报错?
我理解的是,程序在进入if语句的时候,实例找不到getName属性就应该直接报错,而不是变成undefined
求问大神怎么理解。

Javascript是动态类型语言。点就定义了,只是没赋值,所以是undefined。undefined不是一个函数,所以调用报错

@liyanana
Copy link

每次看都有新收获

@cw84973570
Copy link

cw84973570 commented Dec 10, 2020

function Person(name) {
    this.name = name || 'keivn';
}

// 私有属性放到实例里
// Person.prototype.name = 'keivn';
Person.prototype.getName = function () {
    console.log(this.name);
};

var person1 = new Person();

请问原型模式这样写有什么问题吗?跟其他的原型模式相比有什么优缺点?还是说这个name属性是作为公有属性的?

@anjina
Copy link

anjina commented Dec 10, 2020

function Person(name) {
    this.name = name || 'keivn';
}

// 私有属性放到实例里
// Person.prototype.name = 'keivn';
Person.prototype.getName = function () {
    console.log(this.name);
};

var person1 = new Person();

请问原型模式这样写有什么问题吗?跟其他的原型模式相比有什么优缺点?还是说这个name属性是作为公有属性的?

你这是组合模式把, 构造函数 里面的 name 是每个实例对象的属性, 而原型上定义的才是实例对象共享的

@cw84973570
Copy link

cw84973570 commented Dec 12, 2020

function Person(name) {
    this.name = name || 'keivn';
}

// 私有属性放到实例里
// Person.prototype.name = 'keivn';
Person.prototype.getName = function () {
    console.log(this.name);
};

var person1 = new Person();

请问原型模式这样写有什么问题吗?跟其他的原型模式相比有什么优缺点?还是说这个name属性是作为公有属性的?

你这是组合模式把, 构造函数 里面的 name 是每个实例对象的属性, 而原型上定义的才是实例对象共享的

@anjina 为什么文章里的组合模式要修改整个原型呢?在我的理解里最后实现的效果好像没区别,还是说两个写法都行?


又看了一遍,发现文章中说修改整个原型封装性会好点。。。。。

@cw84973570
Copy link

cw84973570 commented Dec 15, 2020

请问 第一个例子工厂模式,缺点是对象无法识别,我是这样理解,因为返回的是Object创建的对象,所以实例对象不能通过constructor找到对应的构造函数,但是你说的是因为所有的实例都指向同一个原型对象,能详细说下嘛

大概是如果var o = new Object()不做修改的话,不管是createPeople还是createPerson所创建的实例都是指向同一个原型,即对象o的原型永远指向Object.prototype.

@haohongyang1
Copy link

这个文章是在讲js继承吗

@fatFire
Copy link

fatFire commented Mar 14, 2021

function person(name){
    var o = new Object();
    o.sayName = function(){
        console.log(name);
    };
    return o;
}

var person1 = person('kevin');

person1.sayName(); // kevin

person1.name = "daisy";

person1.sayName(); // kevin

console.log(person1.name); // daisy

稳妥稳妥构造函数模式 是不是sayName 这个函数在创建时作用域里保存了person函数的AO 所以每次调用都是kevin

@ConanLF
Copy link

ConanLF commented Jun 16, 2021

关于4.1的Person.prototype复写的理解:
//设地址是0x1234
var o = {
value: 1
}
//a.b指向0x1234
var a = {
b:o
}
var c = {}
//c.d指向a.b指向的地址 即0x1234
c.d = a.b
//设地址0x4321
var x = {
value: 2
}
//a.b指向新地址0x4321
a.b = x
//c.d地址是0x1234, a.b地址是0x4321
console.log(c.d === a.b) // false

把a.b换成Person.prototype, c.d换成person1.proto, o就是原来的原型实例, x就是复写的字面量
这样理解对不对

@xiaqingping
Copy link

稳妥构造函数模式和工厂模式我咋就看着一样呢,区别在哪

@wangxiaotian
Copy link

wangxiaotian commented Mar 7, 2022

@mqyqingfeng
关于4.1看楼主解释和评论区好像明白了,但还有个困惑

`function Person(name) {
this.name = name;
}

Person.prototype = {
constructor: Person,
getName: function () {
console.log(this.name);
}
};
var person1 = new Person();`

4.0例子里边添加原型的时候也是重写的,为啥这个的实例原型和原构造函数原型没有断掉呢?
没看4.1的时候,看4.0是山;看了4.1,感觉4.0不是山了!

@axiaoha
Copy link

axiaoha commented Apr 21, 2022

@qujsh 原型也是一个对象,我们假设这个对象叫做 O,看这个例子:

var a = {
     b: O
}

你看 a.b 指向了 O 对象,就相当于 Person.prototype 指向了原型对象这句话。

再看这个例子:

function Person(name) {
    this.name = name;
    if (typeof this.getName != "function") {
        Person.prototype = {
            constructor: Person,
            getName: function () {
                console.log(this.name);
            }
        }
    }
}

var person1 = new Person('kevin');

当 new Person() 的时候,是先建立的原型关系,即 person .proto = Person.prototype,而后修改了 Person.prototype 的值,这就相当于:

// O 表示原型对象
var O = {};

var a = {
     b: O
}

先建立原型关系,指的是 c.proto = a.b = O

而后修改 Person.prototype 的值,相当于

var anotherO = {};
a.b = anotherO;

即便修改了 Person.prototype 的值,但是 c.proto 还是指向以前的 O

不知道这样解释的清不清楚,欢迎交流~

image

function Person(name) {
   this.name = name
}

Person.prototype.getName = function () {
   console.log(this.name);
};
var person1 = new Person('kevin');
Person.prototype = {
   getName : 1
}
console.log(person1.__proto__);
console.log(person1.getName);
var person2 = new Person('kevin');
console.log(person2.__proto__);
console.log(person2.getName);

@liynxy
Copy link

liynxy commented Jul 9, 2022

@mqyqingfeng 不知道对于这个 示例 4.1 的理解是否正确,还望能指出

function Person(name) {
    this.name = name;
    if (typeof this.getName != "function") {
        Person.prototype = {
            constructor: Person,
            getName: function () {
                console.log(this.name);
            }
        }
    }
}

var person1 = new Person('kevin');
var person2 = new Person('daisy');

// 报错 并没有该方法
person1.getName();

// 注释掉上面的代码,这句是可以执行的。
person2.getName();

个人对于原文这个示例的理解:

讨论中有个示例图,解释的比较清楚的一点是:js创建一个对象时是 先建立原型关系,而 后执行构造函数
那么在 第一个 var person1= new Person('Kevin') 调用的时候,函数(类)的 Person.prototype 还并没有被修改,然后再执行类似 Person.apply(obj) 的操作,在这个apply操作中,构造被执行,那么 if 里边的内容被执行,然后 Person.prototype 才被修改,指向新的一个字面量对象,
重点是,这个时候 person1 的原型还是指向的被 修改之前Person.prototype,而在第二次 var person2 = new Person('Daisy') 的时候,Person.prototype 已经被修改,因此 person1 原型上是没有 getName,而 person2 可以正常调用

我的理解是,实例是在原型重写之前还是之后创建的问题。
demo
创建 person1 实例的时候,关联的是最初的原型,然后原型就被重写了。

但在创建 person2 的时候,关联的就是重写之后的原型了。

而后面通过 return new Person(name) 这种方式来解决,其实就是在重写原型之后,再重新创建实例,这时候的实例关联的就是重写之后的原型。

@Jamartin-create
Copy link

image
这个应该改成Array.push.apply(values, arguments); 吧

@DaphnisLi
Copy link

怎么感觉在讲继承似的

@tinyblckc0000al
Copy link

@mqyqingfeng 关于4.1看楼主解释和评论区好像明白了,但还有个困惑

`function Person(name) { this.name = name; }

Person.prototype = { constructor: Person, getName: function () { console.log(this.name); } }; var person1 = new Person();`

4.0例子里边添加原型的时候也是重写的,为啥这个的实例原型和原构造函数原型没有断掉呢? 没看4.1的时候,看4.0是山;看了4.1,感觉4.0不是山了!

4.0添加原型是在构造函数外添加的,只会执行一次,可以看成定义而不是重写,所有构造出来的对象实例引用的prototype都是同一个。
而4.1是每次构造函数都会执行,所以旧的对象所引用的prototype会因为新的对象构造过程而被覆盖。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests