一、对象的创建方式
1.用new构造函数创建
<script>
// 通过new构造函数创建
var obj = new Object()
//添加属性和方法
obj.name = '小艾同学'
obj.age = 23
obj.sayHi = function () {
console.log('Hi~');
}
var obj2 = new Object()
obj2.name = 'cp'
obj2.age = 28
obj2.sayHi = function () {
console.log('Hi~');
}
console.log(obj);
console.log(obj2);
/*
缺点:1.添加属性或方法麻烦
2.一次只能创建一次,代码的复用性差
*/
</script>
2.字面量方法创建对象
<script>
let obj = {
name: '小艾同学',
age: 23,
say: function () {
console.log('hi~');
}
}
console.dir(obj);
/*
字面量创建对象:一次只能创建一次,代码的复用性差
*/
</script>
3.通过工厂函数创建对象
<script>
// 工厂函数:利用函数将字面量对象封装,并返回这个对象。解决一次性对象复用性差的问题
function Person(name, age) {
let obj = {
name: name,
age: age,
say: function () {
console.log('hi~');
}
}
return obj
}
let xa = Person('小艾同学', 23)
let cp = Person('cp', 28)
console.log(xa);
console.log(cp);
//缺点:1. 无法识别对象,返回的都是Object
</script>
4.自定义构造函数
<script>
//自定义构造函数注意:1.首字母大写,2.需要配合new操作符
function Person(name, age) {
this.name = name
this.age = age
this.say = function () {
console.log('hi~');
}
}
let xa = new Person('小艾同学', 23)
let cp = new Person('cp', 28)
//此时解决了无法判断对象,类型的问题
console.log(xa);
console.log(cp);
console.log(xa === cp);//false
// 两个相同的方法,却在内存中占用了两个地址值
console.log(xa.say() === cp.say());//true
console.log(xa.say === cp.say);//false
</script>
5.解决内存浪费问题
// 方法一:将公共的方法抽离出来,确保该方法只占一份内存
//缺点 : 将方法放在全局作用域,会污染全局
function sayHi() {
console.log('hi~');
}
function Person(name, age) {
this.name = name
this.age = age
this.say = sayHi
}
let xa = new Person('小艾同学', 23)
let cp = new Person('cp', 28)
console.log(xa);
console.log(cp);
console.log(xa === cp);//false
// 占用内存问题已经解决
console.log(xa.say === cp.say);//true
// 方法二:将公共的方法存入一个对象中
//解决了所有问题,但是需要重新创建一个新对象,写法不够优雅
var tools = {
sayHi: function () {
console.log('hi~');
},
sayBay: function () {
console.log('Bay~');
}
}
function Person(name, age) {
this.name = name
this.age = age
this.say = tools.sayHi
this.bay = tools.sayBay
}
let xa = new Person('小艾同学', 23)
let cp = new Person('cp', 28)
console.log(xa);
console.log(cp);
console.log(xa === cp);//false
// 占用内存问题已经解决
console.log(xa.say === cp.say);//true
console.log(xa.bay === cp.bay);//true
二、原型对象prototype
1.任何函数都有prototypr属性 2.函数的prototype属性的值就是原型对象 3.通过构造函数new出来的对象称为实例对象,一个构造函数可以有多个实例对象 4.实例对象可以访问构造函数原型对象上的所有成员
- 将一些公共的属性和方法挂载到原型对象上完成共享,省内存
function Person(name, age) {
this.name = name
this.age = age
}
//将公共的方法提取到构造函数自带的原型对象中
Person.prototype.say = function () {
console.log('hi~');
}
let xa = new Person('小艾同学', 23)
let cp = new Person('cp', 28)
console.log(Person.prototype);
// 实例对象能够访问构造函数原型对象上的任何成员
xa.say()
//同样解决了内存浪费的问题
console.log(xa === cp);//false
console.log(xa.say === cp.say);//true
原型对象的核心:内存复用,共享方法
内置对象的方法push(),max()等方法都是挂载在构造函数的原型上,避免内存浪费,且每个实例对象都能访问原型对象上的成员
1.new做了哪些事
- 生成了一个空对象
- 将生成的空对象的原型对象自动指向了构造函数的prototype
- 伪代码 --> obj.proto === Person.prototype
- 将构造函数的this指向这个空对象
- 将属性和方法都挂载到这个对象上
- 返回这个对象
- 如果构造函数上没有写return,默认返回new创建的这个对象
- 如果构造函数写了return
- return的是一个基本数据类型,仍然返回new创建的对象
- return的是一个对象,就会返回return后面的这个对象
//伪代码模拟new操作符
function newSelf(ctor, name, age) {
// 1.创建一个空对象
var obj = {}
// 截取参数,为一个数组
var res = Array.from(arguments).slice(1)
// 2.改变this指向为这个空对象,并将数组参数传入
ctor.apply(obj, res)
// 3.将空对象的原型连接到构造函数的原型对象
obj.__proto__ = ctor.prototype
//4.返回这个对象
return obj
}
function Person(name, age) {
this.name = name
this.age = age
}
var p1 = newSelf(Person, '小艾同学', 23)
console.log(p1);
2.特殊的原型对象
- Function.prototype区别于一般函数的prototype,不可修改,不可遍历, 并且是所有函数的原型对象
- Function.prototype的值是一个函数,而普通函数的prototype是个含有constructor属性的普通对象
- Object.prototype是所有对象原型链的尽头,Object.prototype.proto === null
console.log(Function.prototype);//ƒ () { [native code] }
console.log(Object.prototype.__proto__);//null
function Person() { }
console.log(Person.__proto__ === Function.prototype);
3._instanceof _
二、对象的原型__proto__
任何一个对象上都有个__proto__,指向构造函数的prototype。
是一个访问器属性, 暴露了通过它访问的对象的内部[[Prototype]] 。
核心功能 :便于我们查看当前对象的原型对象是谁
//任何对象都有__proto__
function Person(name) { this.name = name }
Person.prototype.say = function () { console.log('hello') }
let xa = new Person
// 实例对象的__proto__指向了当前构造函数的prototype对象
console.log(xa.__proto__);
console.log(xa.__proto__ === Person.prototype);//true
// 2.__proto__是一个私有属性,虽然__proto__指向原型对象,但不推荐用这种方式去操作原型对象, 浏览器为了方便开发者查看当前对象的原型对象是谁暴露出来的
// 内部 使用 [[prototype]] 内置属性发生关联
三.constructor属性
constructor属性是每个原型对象上自带的属性 原型对象的constructor属性指向了构造函数 当直接给原型对象赋值一个对象时,需要用constructor做指针修正,将原型对象手动指回构造函数
function Person() { }
console.log(Person.prototype);
console.log(Person.constructor);
console.log(Person.prototype.constructor === Person);//true
//应用场景
function Star(name) {
this.name = name
this.bey = function () {
console.log('bey~');
}
}
//给原型对象赋值的是一个对象,原型对象就会被覆盖,此时的constructor就不再指向Star了,而是Object
Star.prototype = {
//需要在内部手动修改constructor指向
constructor: Star,
age: '18',
say: function () {
console.log('hello');
}
}
let xa = new Star('小艾同学')
console.log(xa.constructor);// constructor修改指向前:Object 修改指向后:Star
四.三者的关系
-
构造函数 和 原型对象
- 构造函数 通过 prototype属性来访问到原型对象
- 原型对象 通过 constructor属性来访问到构造函数
-
构造函数 和 实例对象的关系
- 构造函数通过new创建出来实例对象
- 实例对象不能去直接访问到构造函数
-
原型对象 和 实例对象的关系
- 实例对象通过__proto__属性访问原型对象上的任意成员
- 实例对象通过原型对象的constructor属性可以间接的访问构造函数
function Person() { }
// 构造函数 和 原型对象
// 1. 构造函数 通过 prototype属性来访问到原型对象
console.log(Person.prototype);
// 2. 原型对象 通过 constructor属性来访问到构造函数
console.log(Person.prototype.constructor);//Person
// 构造函数 和 实例对象的关系
// 1. 构造函数通过new创建出来实例对象
let xa = new Person()
// 2. 实例对象不能去直接访问到构造函数
// 原型对象 和 实例对象的关系
// 1. 实例对象通过__proto__属性访问原型对象上的任意成员
// 2. 实例对象通过原型对象的constructor属性可以间接的访问构造函数
console.log(xa.__proto__ === Person.prototype);//true
console.log(xa.constructor === Person);//true
五.原型链
对象有__proto__属性,指向了当前的原型对象,原型对象也是对象,也有__proto__属性,指向了原型对象的原型对象,当访问某个属性的时候,先在自身上找,没得找到就去__proto__指向的原型对象上查找,还没找到就去原型对象的原型对象上查找,这样一层一层向上查找会形成一个链式结构,就是原型链
function Person() { }
let p = new Person()
console.log(p.__proto__ === Person.prototype);//true
console.log(Person.prototype.__proto__ === Object.prototype);//true
console.log(Object.prototype.__proto__);//null
// p的原型链:p --> Person.prototype --> Object.prototype --> null
// 内置对象的原型链
// 1.数组
//[]本质上是 [] = new Array(),空数组也是两个不同的地址值
console.log([] == []);//false
console.log([] === []);//false
let arr = []
console.log(arr.__proto__ === Array.prototype);//true
console.log(Array.prototype.__proto__ === Object.prototype);//true
console.log(Object.prototype.__proto__);//null
//数组的原型链:arr --> Array.prototype --> Object.prototype --> null
// 2.Date
let date = new Date()
console.log(date.__proto__ === Date.prototype);//true
console.log(Date.prototype.__proto__ === Object.prototype);//true
console.log(Object.prototype.__proto__);//null
//Date的原型链: date --> Date.prototype --> Object.prototype --> null
//3.Math
//Math本质上就是一个对象,不能和new一起使用
console.log(Math);
console.log(Math.__proto__);//Object.prototype
console.log(Object.prototype.__proto__);//null
//Math的原型链:Math --> Object.prototype --> null
完整版原型链
function Person() { }
let p = new Person
// 1.把构造函数当做函数
// 构造函数:Person
// 原型对象:Person.prototype
// 实例对象:p
console.log(Person.prototype);
console.log(Person.prototype.constructor);//Person
console.log(p.__proto__);
// 2.把Person函数当对象来看
//底层: let Person = new Function()
// 构造函数:Function
// 原型对象:Function.prototype
// 实例对象:Person
console.log(Person.__proto__);//ƒ () { [native code] }
console.log(Person.__proto__ === Function.prototype);//true
console.log(Function.prototype.constructor);//ƒ Function() { [native code] }
console.log(Function.prototype.constructor === Function);//true
// 3. 把Object当构造函数来看
var obj = new Object();
// 构造函数: Object
// 原型对象: Object.prototype
// 实例对象: obj
console.log(obj.__proto__ === Object.prototype);//true
// 4. 把Object当实例对象来看
// 底层 var Object = new Function();
// 构造函数: Function
// 原型对象: Function.prototype
// 实例对象: Object
console.log(Object.__proto__);//ƒ () { [native code] }
console.log(Object.__proto__ === Function.prototype);//true
// 5. 把Function当做构造函数看
// 底层 var Function = new Function();
// 构造函数: Function
// 原型对象: Function.prototype
// 实例对象: Function
console.log(Function.__proto__ === Function.prototype);//true
let arr = new Array()
console.log(Array.__proto__); //ƒ () { [native code] }
console.log(Array.__proto__ === Function.prototype);//true
六.属性查找原则
查找一个属性时,如果在当前的对象上找不到该属性,就委托给它的原型对象继续查找,如果原型对象上依然没有找到,那就继续委托给原型对象的源性对象查找,直到找到Object.prototype时,如果有就返回该属性,如果没有就返回undefined;如果找到,不管在哪一层找到了都会直接返回,不在继续向下查找。_
function Person(name, age) {
this.name = name
this.age = age
this.say = function () {
console.log('hello');
}
}
Person.prototype.sex = '女'
Object.prototype.money = 666
let p = new Person('小艾同学', 23)
console.log(p.name);//小艾同学
console.log(p.sex);//女
console.log(p.money);//666
console.log(Person.prototype.name);//undefined
console.log(Person.prototype.money);//666
七.原型设置原则
不会影响其他原型对象
/*
属性设置原则:
如果该对象上有该属性,就把原来的值不该掉,不会影响原型上的属性
如果该对象没有属性,就会添加该属性,也不会影响原型上的属性
*/
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.name = 'cp'
Object.prototype.money = 666
let p = new Person('小艾同学', 23)
p.money = 0
console.log(p.name);//小艾同学
console.log(p.money);//0
//不会影响原型上的属性
console.log(Person.prototype.money);//666