-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Comments
消灭零回复! |
@JarvenIV 哈哈,(๑•̀ㅂ•́)و✧ |
感觉特么都有缺点啊 |
@liuxinqiong 确实如此哈,一般都是用组合模式 |
@mqyqingfeng 你好,想请问你两个问题:一个是:4.1动态原型的模式,“使用字面量方式直接覆盖 Person.prototype,并不会更改实例的原型的值”这是为什么?另一个是5.1 寄生构造函数模式,在调用的时候和工厂模式相比,就多了一个new调用,这个有什么用? |
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 上查找 |
@ry928330 关于寄生构造函数模式的使用,例子中也有讲到,你可以理解为这个人就是想用 new 的方式而不是直接调用函数 😂 |
@mqyqingfeng 那现在怎么通过Person再找回原来的prototype(也就是O)呢,因为像这样 |
1 similar comment
@mqyqingfeng 那现在怎么通过Person再找回原来的prototype(也就是O)呢,因为像这样 |
@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 指向的原型对象 对这句话其实不是很理解,为什么指向了Person.prototype指向的原型对象? |
@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 不知道这样解释的清不清楚,欢迎交流~ |
请问组合模式哪些方法和属性写在prototype,哪些写在构造函数里 |
@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 属性中 |
第二个原型模式优化的内容,如果把新建对象放在用对象字面量重写原型对象之前,再调用原型上的方法就会报错。 function Person(){}
var person_1 = new Person();
Person.prototype = {
constructor:Person,
name:'Joe',
getName : function(){
console.log(this.name);
}
}
person_1.getName(); //error |
@MagicHacker 这是肯定的呀,因为当你第一次 new 的时候,person_1 的原型指向了 Person.prototype 所指向的那个对象,后来再修改 Person.prototype,只会使得以后 new 的对象指向这个新的 Person.prototype,已经 new 的不会再发生改变~ |
谢谢作者。关于重写原型的问题,还是要好好看js高程p156-“原型的动态性”一节的,其中P157图6-3对更好理解字面量重写原型导致的原型链变化有帮助,结合图和new操作符的第二步与重写原型的先后关系就理解了这个问题。 |
在《你不知道的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中不存在“类”且“原型链”是引用而不是复制的情况下,强行使用“类”的设计思路会导致一些的问题,然后使用了这种“纯对象”的形式。 |
从深入第一篇看到这里即将完结,必须给大大点个赞!狂赞一下!写得太好看了,我在坐地铁还有睡觉前,手机微信网页都是置顶大大的博客来看的❤️❤️❤️ |
博主我可以不可以这样理解person1.proto = Person.prototype 这句话就相当于person1.proto 指向了Person.prototype 指向的一块区域,当Person.prototype 以字面量的形式更改的时候,原来的那块区域并没有发生变化。感觉之前看: |
和你一样,一开始也觉得这句话是在难以理解。 “当执行 var person1 = new Person('kevin') 的时候,person1.的原型并不是指向 Person.prototype,而是指向 Person.prototype 指向的原型对象“ 后来想想,其实重点在js创建一个对象时是先建立原型关系,而后执行构造函数才是这个问题的核心。 “当 new Person() 的时候,是先建立的原型关系,即 person .proto = Person.prototype,而后修改了 Person.prototype 的值” 初始先存在constructor关系线和1关系线。
由于new Person()先建立原型关系导致了关系2的生成,然后覆盖了构造函数指向了原型之后,关系1移除,关系3生成。
在person1生成后由于关系3替代了关系1,所以在person2生成时同时生成了关系4(即person2的__proto__指向了新的原型对象),所以person2能找到新的原型对象上的方法。 |
@mqyqingfeng 不知道对于这个 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》中的一句话,更好的进行理解:
所以在上述例子中: 回到这个例子当中:
|
4.1动态原型的模式 let obj = {
a: 1
}
let prosen = obj
obj = {
a: 2
}
console.log(prosen) // {a: 1}
console.log(obj) // {a: 2} 在堆内存中又开辟了一片空间, 切断了和之前的引用关系吗? |
谢谢大佬的图,一下子理解了,但还是想问一下: |
《JavaScript高级程序设计》得反反复复看啊,一看就会,一写就忘 😄 |
第一个问题,我认为用类似C的指针来理解比较容易,虽然js里面似乎没有指针的概念。 |
看到作者这边文章,拿起了我尘封多年的javascript高级程序设计 |
寄生构造函数模式和工厂模式有啥区别,就是一个用new,一个不用new? |
Javascript是动态类型语言。点就定义了,只是没赋值,所以是undefined。undefined不是一个函数,所以调用报错 |
每次看都有新收获 |
请问原型模式这样写有什么问题吗?跟其他的原型模式相比有什么优缺点?还是说这个name属性是作为公有属性的? |
你这是组合模式把, 构造函数 里面的 name 是每个实例对象的属性, 而原型上定义的才是实例对象共享的 |
@anjina 为什么文章里的组合模式要修改整个原型呢?在我的理解里最后实现的效果好像没区别,还是说两个写法都行? 又看了一遍,发现文章中说修改整个原型封装性会好点。。。。。 |
大概是如果 |
这个文章是在讲js继承吗 |
稳妥稳妥构造函数模式 是不是 |
关于4.1的Person.prototype复写的理解: 把a.b换成Person.prototype, c.d换成person1.proto, o就是原来的原型实例, x就是复写的字面量 |
稳妥构造函数模式和工厂模式我咋就看着一样呢,区别在哪 |
@mqyqingfeng `function Person(name) { Person.prototype = { 4.0例子里边添加原型的时候也是重写的,为啥这个的实例原型和原构造函数原型没有断掉呢? |
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); |
我的理解是,实例是在原型重写之前还是之后创建的问题。 但在创建 person2 的时候,关联的就是重写之后的原型了。 而后面通过 return new Person(name) 这种方式来解决,其实就是在重写原型之后,再重新创建实例,这时候的实例关联的就是重写之后的原型。 |
怎么感觉在讲继承似的 |
4.0添加原型是在构造函数外添加的,只会执行一次,可以看成定义而不是重写,所有构造出来的对象实例引用的prototype都是同一个。 |
写在前面
这篇文章讲解创建对象的各种方式,以及优缺点。
但是注意:
这篇文章更像是笔记,因为《JavaScript高级程序设计》写得真是太好了!
1. 工厂模式
缺点:对象无法识别,因为所有的实例都指向一个原型
2. 构造函数模式
优点:实例可以识别为一个特定的类型
缺点:每次创建实例时,每个方法都要被创建一次
2.1 构造函数模式优化
优点:解决了每个方法都要被重新创建的问题
缺点:这叫啥封装……
3. 原型模式
优点:方法不会重新创建
缺点:1. 所有的属性和方法都共享 2. 不能初始化参数
3.1 原型模式优化
优点:封装性好了一点
缺点:重写了原型,丢失了constructor属性
3.2 原型模式优化
优点:实例可以通过constructor属性找到所属构造函数
缺点:原型模式该有的缺点还是有
4. 组合模式
构造函数模式与原型模式双剑合璧。
优点:该共享的共享,该私有的私有,使用最广泛的方式
缺点:有的人就是希望全部都写在一起,即更好的封装性
4.1 动态原型模式
注意:使用动态原型模式时,不能用对象字面量重写原型
解释下为什么:
为了解释这个问题,假设开始执行
var person1 = new Person('kevin')
。如果对 new 和 apply 的底层执行过程不是很熟悉,可以阅读底部相关链接中的文章。
我们回顾下 new 的实现步骤:
注意这个时候,回顾下 apply 的实现步骤,会执行 obj.Person 方法,这个时候就会执行 if 语句里的内容,注意构造函数的 prototype 属性指向了实例的原型,使用字面量方式直接覆盖 Person.prototype,并不会更改实例的原型的值,person1 依然是指向了以前的原型,而不是 Person.prototype。而之前的原型是没有 getName 方法的,所以就报错了!
如果你就是想用字面量方式写代码,可以尝试下这种:
5.1 寄生构造函数模式
寄生构造函数模式,我个人认为应该这样读:
寄生-构造函数-模式,也就是说寄生在构造函数的一种方法。
也就是说打着构造函数的幌子挂羊头卖狗肉,你看创建的实例使用 instanceof 都无法指向构造函数!
这样方法可以在特殊情况下使用。比如我们想创建一个具有额外方法的特殊数组,但是又不想直接修改Array构造函数,我们可以这样写:
你会发现,其实所谓的寄生构造函数模式就是比工厂模式在创建对象的时候,多使用了一个new,实际上两者的结果是一样的。
但是作者可能是希望能像使用普通 Array 一样使用 SpecialArray,虽然把 SpecialArray 当成函数也一样能用,但是这并不是作者的本意,也变得不优雅。
在可以使用其他模式的情况下,不要使用这种模式。
但是值得一提的是,上面例子中的循环:
可以替换成:
5.2 稳妥构造函数模式
所谓稳妥对象,指的是没有公共属性,而且其方法也不引用 this 的对象。
与寄生构造函数模式有两点不同:
稳妥对象最适合在一些安全的环境中。
稳妥构造函数模式也跟工厂模式一样,无法识别对象所属类型。
下一篇文章
JavaScript深入之继承的多种方式和优缺点
相关链接
《JavaScript深入之从原型到原型链》
《JavaScript深入之new的模拟实现》
《JavaScript深入之call和apply的模拟实现》
深入系列
JavaScript深入系列目录地址:https://github.com/mqyqingfeng/Blog。
JavaScript深入系列预计写十五篇左右,旨在帮大家捋顺JavaScript底层知识,重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。
如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。
The text was updated successfully, but these errors were encountered: