多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
[TOC] # 1. 创建对象(普通写法) <mark>1. 写法1</mark>:浪费内存。 ```js function Person (name, age) { this.name = name this.age = age this.type = 'human' this.sayHello = function () { console.log('hello ' + this.name) } } var p1 = new Person('lpz', 18) var p2 = new Person('Jack', 16) // 上面的type、sayHello是实现的功能是永远不变的,但是每new一次Person,都会重复创建这个两个属性 // 没有必要,且浪费内存 console.log(p1.sayHello === p2.sayHello) // => false ``` <mark>2. 写法2</mark>:将共享的属性定义在对象外部 ```js function sayHello = function () { console.log('hello ' + this.name) } function Person (name, age) { this.name = name this.age = age this.type = 'human' this.sayHello = sayHello // 对于不需要传参的函数,调用时可以去掉() } var p1 = new Person('lpz', 18) var p2 = new Person('Jack', 16) console.log(p1.sayHello === p2.sayHello) // => true // 存在的问题是:会造成全局命名空间冲突 ``` <mark>3. 写法3</mark>:将共享的属性定义在另一个对象中 ```js var fns = { sayHello: function () { console.log('hello ' + this.name) }, sayAge: function () { console.log(this.age) } } function Person (name, age) { this.name = name this.age = age this.type = 'human' this.sayHello = fns.sayHello this.sayAge = fns.sayAge } var p1 = new Person('lpz', 18) var p2 = new Person('Jack', 16) console.log(p1.sayHello === p2.sayHello) // => true console.log(p1.sayAge === p2.sayAge) // => true ``` # 2. prototype创建对象 <mark>4. 写法4</mark>:使用`prototype`属性 Javascript 规定,每一个构造函数都有一个 `prototype` 属性,指向```prototype```对象(prototype是一个属性也是一个对象)的内存地址。```prototype```对象的所有属性和方法,都会被构造函数的实例继承。 这也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在 `prototype` 对象上。 ```js function Person (name, age) { this.name = name this.age = age } console.log(Person.prototype) Person.prototype.type = 'human' Person.prototype.sayName = function () { console.log(this.name) } var person1 = new Person(...) var person2 = new Person(...) console.log(person1.sayName === person2.sayName) // => true // 这时所有实例的 type 属性和 sayName() 方法, 其实都是同一个内存地址,指向 prototype 对象,因此就提高了运行效率。 ``` 或 ```js function Person (name, age) { this.name = name this.age = age } Person.prototype = { constructor: Person, // 将Person写在这里的原因是:为了防止prototype对象丢失constructor构造器 type: 'human', sayHello: function () { console.log('我叫' + this.name + ',我今年' + this.age + '岁了') } } ``` ## 3.1 属性成员的搜索原则:原型链 在我们调用 `person1.sayName()` 的时候,会先后执行两次搜索: * 首先,解析器会问:“实例 person1 有 sayName 属性吗?”答:“没有。 * 然后,它继续搜索,再问:“ person1 的原型有 sayName 属性吗?”答:“有。 * 于是,它就读取那个保存在原型对象中的函数。 * 当我们调用 person2.sayName() 时,将会重现相同的搜索过程,得到相同的结果。 * 如果一直到原型链的末端还没有找到,则返回 `undefined` ## 3.2 构造函数、实例、原型三者之间的关系** ![](https://img.kancloud.cn/6f/2e/6f2e913b54ec974fde60288ce62af163_659x436.png) * 任何构造函数都具有一个 `prototype` 属性,该属性是一个对象。 * 构造函数的 `prototype` 对象默认都有一个 `constructor` 属性,指向 `prototype` 对象所在函数。 * 通过构造函数得到的实例对象内部会包含一个指向构造函数的 `prototype` 对象的指针 * 所有实例都直接或间接继承了原型对象的成员 `__proto__`。 ```js function F () { // 构造函数 // 任何构造函数/对象都有一个prototype属性 // 任何构造函数/对象的prototype属性都有一个constructor属性,指向构造函数 // 任何构造函数/对象的实例对象都有一个指向prototype的指针__proto__ } console.log(F.prototype) // => object console.log(F.prototype.constructor === F) // => true var instance = new F() console.log(instance.__proto__ === F.prototype) // => true F.prototype.sayHi = function() {} instance.sayHi() // 实例对象可以直接访问原型对象成员。 ``` ## 3.3 实例对象读写原型对象成员 读取: - 先在自己身上找,找到即返回 - 自己身上找不到,则沿着原型链向上查找,找到即返回 - 如果一直到原型链的末端还没有找到,则返回 `undefined` 值类型成员写入(`实例对象.值类型成员 = xx`): - 当实例期望重写原型对象中的某个普通数据成员时实际上会把该成员添加到自己身上 - 也就是说该行为实际上会屏蔽掉对原型对象成员的访问 引用类型成员写入(`实例对象.引用类型成员 = xx`): - 当实例期望重写原型对象中的某个普通数据成员时实际上会把该成员添加到自己身上 - 也就是说该行为实际上会屏蔽掉对原型对象成员的访问 复杂类型修改(`实例对象.成员.xx = xx`): - 同样会先在自己身上找该成员,如果自己身上找到则直接修改 - 如果自己身上找不到,则沿着原型链继续查找,如果找到则修改 - 如果一直到原型链的末端还没有找到该成员,则报错(`实例对象.undefined.xx = xx`) ## 3.4 原型对象使用建议 * 共享数组 * 共享对象 如果真的希望可以被实例对象之间共享和修改这些共享数据那就不是问题。但是如果不希望实例之间共享和修改这些共享数据则就是问题。 一个更好的建议是,最好不要让实例之间互相共享这些数组或者对象成员,一旦修改的话会导致数据的走向很不明确而且难以维护。所以建议如下: * 私有成员(一般就是非函数成员)放到构造函数中 * 共享成员(一般就是函数)放到原型对象中 * 如果重置了 `prototype` 记得修正 `constructor` 的指向