在ES6之前,对象并不是基于类创建的,而是用一种称为构造函数的特殊函数来定义对象和他们的特性.
创建对象的三种方式:1.对象的字面量创建
var obj1 = {}
2.new Object() 创建
var obj2 = new Object()
3.自定义构造函数创建
function Fun(name,age) {
this.name = name;
this.age = age;
this.play = function() {
console.log('我想要去旅游')
}
}
//创建张三对象
var zs = new Fun('张三',18)
关于构造函数:
构造函数是一种特殊的函数,需要与new一起使用才有意义,把对象中的一些公共的属性和方法抽取出来,封装到一个构造函数里面,其首字母要大写.
构造函数在new时做的事情:首先在内存中创建一个空对象,this指向这个空的对象,构造函数中的属性和方法添加给这个空对象,返回这个新对象(所以构造函数里面不需要return).
构造函数实例成员:
实例成员就是在构造函数内部通过this添加的属性和方法,实例成员只能通过实例化的对象来访问
构造函数静态成员:
静态成员只能通过构造函数来访问,是直接加载构造函数身上的
构造函数的缺点:
在new构造函数的时候,构造函数内的简单数据类型可以直接复用,复杂数据类型会另外开辟一个地址空间,进行存储数据,加入new1000下构造函数,就会特别浪费内存.
构造函数原型对象prototype:
js规定,每一个构造函数都有一个原型对象prototype,这个prototype对象中的属性和方法都归属于该构造函数.所以如果把构造函数的方法定义在原型对象prototype身上,就会解决构造函数的缺点(浪费内存),(假如new1000次构造函数,调用方法play(),那么总共就开辟了一个内存空间)
Fun.prototype.play = function() {}
var zs = new Fun()
var ls = new Fun()
console.log(zs.play === ls.play) //true
对象原型__proto__:
每个对象身上都有一个__proto__属性(我们也称之为__proto__原型),这个属性指向构造函数的prototype原型对象,这也就说明了为什么对象可以访问构造函数prototype原型对象身上的属性和方法.
function Person() {}
Person.prototype.play = function() {console.log('速度上号!')}
var zs = new Person()
console.log(zs.__proto__ === Person.prototype) //true
__proto__对象原型的意义就是为对象查找机制提供一条线路,它只是内部指向原型对象prototype,开发中并不使用
constructor构造函数:
对象原型(__proto__)和构造函数原型对象(prototype)身上都有一个constructor构造函数,因为它指向构造函数本身,也就告诉我们这个对象是由哪个构造函数创建出来的
function Person() {}
Person.prototype.play = function() {console.log('速度上号!')}
var zs = new Person()
console.log(zs.__proto__.constructor) //ƒ Person() { }
console.log(Person.prototype.constructor) //ƒ Person() { }
如果我们修改了原型对象,直接给原型对象进行赋值(Person.peototype = {}),那么就必须手动添加constructor指回原来的构造函数
function Person() { }
Person.prototype.play = function () { console.log('速度上号!') }
Person.prototype = {
constructor: Person,
watch: function () {
console.log('我要看电影');
}
}
var zs = new Person()
console.log(zs.__proto__.constructor) //ƒ Person() { }
console.log(Person.prototype.constructor) //ƒ Person() { }
构造函数、原型(__proto__)、原型对象(prototype)三者之间的关系总结:
构造函数new出来对象实例,对象实例身上有__proto__原型指向构造函数的原型对象(prototype),prototype身上有constructor构造函数指向构造函数,因为实例.__proto__ ===实例.prototype ,所以实例.__proto__.constructor => 构造函数
原型链:
只要是对象里面都有一个原型(__proto__),原型对象(prototype)也是对象,那么它身上也有原型(__proto__),指向Object原型对象,Object原型对象的constructor指向构造函数function Object(){},Object原型对象身上也有__proto__指向null,这就是整个原型链
js的查找机制:
1.当访问一个对象的属性或者方法时,首先查找对象自身有没有该属性
2.如果没有就查找它的原型(实例对象的__proto__指向的原型对象)
3.如果还没有就查找原型对象身上的__proto__(找到Object的原型对象)
4.以此类推一直找到null为止
原型对象this指向问题:
1. 在构造函数中,里面this指向的是对象实例
2. 在原型对象函数里面的this指向也是对象实例
典型原型对象应用:
扩展内置对象的方法:在原型对象身上添加方法,那么在对象实例上也可以使用该方法:
function Person() {}
Person.prototype.play = function() {
console.log('玩联盟')
}
var zs = new Person()
zs.play() //玩联盟
call方法的作用:
语法:
call(m1,m2,m3.....) 第一个m1是改变后的this指向的对象,m2、m3...是传入的参数
用法:
1.调用函数
function fn() {console.log('打篮球')}
fn.call() // 打篮球
2.改变this指向
function fn(x,y) {
console.log(this)
console.log(x+y)
}
let zs = {
name:'hiphop先生'
}
fn.call(zs,10,8) //{name: 'hiphop先生'} 18
利用父构造函数继承属性
function Father(uname,uage) {
// 第三步:这里的this改变为了Son,所以在Son这个对象实例上添加了uname和age属性
this.uname = uname;
this.uage = uage
}
function Son(uname,uage) {
// 第二步: 这里的this指向的是son,进入Father构造函数
Father.call(this,uname,uage)
}
// 第一步: new Son 创建Son的实例对象
let son = new Son('hiphop先生',18)
console.log(son)