[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` 的指向
- js应用场景
- js组成
- js书写位置
- 浮点数精度问题
- undefined与null的区别
- 数据类型转换
- 运算符优先级
- 代码调试
- 函数
- 函数的定义和调用
- 函数的return细节
- 函数是一种数据类型
- this的指向
- 函数成员
- 函数闭包
- 作用域
- 预解析
- js对象
- 对象的创建与调用
- new关键字
- this关键字
- 构造函数创建对象
- 事件
- 数据类型
- 继承
- 杂项
- 如何阻止标签的默认行为
- 为一个标签绑定或移除任何一个事件
- 如何阻止事件的冒泡行为
- 事件的三个阶段
- 移动元素的条件
- 匀速动画函数封装
- 变速动画函数封装
- 获取元素的css属性值
- 数据类型判断方法
- 创建对象的7种写法
- 如何继承
- 为js内置对象添加原型函数
- 将局部变量转换为全局变量
- call函数的用法
- 沙箱
- 浅拷贝
- 深拷贝
- 对象赋值会改变对象
- 解析URL中的字符串
- 格式化日期
- 获取当前浏览器类型
- Vue3.x
- 调式工具Vue Devtools