举例来说我就很好奇Array构造函数里面有3个地方可以放置方法.
- 构造函数属性上可以放的是of from这种不可枚举的属性
- 构造函数内部通过this.Fn设置的方法
- 在prototype上设置方法,比如Array的sort方法
我就想问在我自己设计自定义类型构造函数的时候,什么样的方法该放到内部,什么样的方法该放到原型上呢?
除了原型上的方法是共享引用地址而构造函数内部方法是每次实例化都要执行一次内存占用之外,还有什么条件可以让我们一定要把某个方法放到构造函数内部呢?
// 方法1
function Person() {
this.sayName = function () {
console.log("in Fn");
};
}
// 方法2
function Person2() {}
Person2.prototype.sayName = function () {
console.log("out Fn");
};
const p = new Person();
const p1 = new Person2();
p.sayName();
p1.sayName();
// 什么时候用方法1, 什么时候用方法2 呢?
首先,不建议采用 ES5 的构造函数定义类的方法,建议使用新的
class
语法来定义。然后,需要区分三个 OO 的概念(先不谈语法):方法、属性和字段
一般这么理解:
在 ES5 中这三个概念区分得并不是很明显,使用 class 语法和 get/set 语法之后,区分得更清晰了。看个示例:
这个示例展示的字段、属性和方法的常见用法。注意上面一直都是说的“对象数据”、“对象行为”等,是因为要实体化类,产生对象,并在对象中使用。应该容易理解的是 :类主要是声明了内/外部接口,对象才是具体的实现,每个对象都拥有自己的数据,所以数据是每个对象的,可以理解为每个对象上都有一份拷贝。讲道理,方法应该也是每个对象的,实际上每个对象的行为都完全一致,只需要能正确的引用数据就行,语句是可以共用的。所以同一个类的所有对象拥有同一个方法,也就是说,方法只有一个拷贝。
这样一来,就容易理解,数据是附着在对象上,而方法是附着在类的原型上(对象们都有同一个原型)
接下来讨论新的情况(修改了
constructor
和greet()
):这里
onGreeting
是一个函数,但在 OO 概念里并不把它称为“方法”,而是称为“数据”。这个数据保存的是一个函数,表示打招呼的方式。也就是说,这个类定义给予用户一定的灵活性来打招呼。你看,同样是函数,
onGreeting
就是数据,greet
就是方法,区别就在于它是固有行为还是可制定的行为。JS 中函数本身就是对象,可以作为数据;但在 Java 或者 C# 中,作为数据的行为是通过对象来传递的(Lambda 也是对象),所以可能会更容易区分。由于
onGreeting
是数据,所以它是附着在对象上,可以像其他数据一样变更的。它不在类的原型上。然后更复杂的情况来了:
这是怎么回事呢?本来
a.greet()
是方法,调用它应该输出greet demo
。但是我们给对象a
赋了一个数据,所以现在a.greet
是一个数据,它是一个函数。这个时候的a.greet()
是它自己的,不是Demo
类原型上的.greet()
。调用的时候就近原则,当然是先找自己的,找不到才会去原型上找。所以调用的就是数据greet
函数了。这个时候原型上的greet()
当然还是在,只是被隐藏了而已。如果我们把这个数据删了,又会调用原型链上的greet()
JS 很灵活,所以会发生这样的事情,但是一般我们在设计/使用的时候应该适当的避免过于灵活,所以通常会约定方法不允许赋值覆盖(隐藏),但是君子协定,并不是很牢靠,看各位的意识。如果是 Java 或者 C#,直接从编译器/语法层面就阻止了这种事情发生。