一、工厂函数
其本质就是一个函数,调用时会返回一个对象,当你需要定义多个具有相同属性的的对象时可以使用工厂函数。
- 优点:解决了创建多个相同属性对象的问题
- 缺点:无法判断对象的类型,都是object
function fac(){
return{
name:'jqy',
sayName:function(){
console.log(this.name)
}
}
}
let info1 = fac()
let info2 = fac()
console.log(info1 === info2) //false,创建的对象互不影响
console.log(info1.sayName === info2.sayName)
//false,虽然方法名相同,但分别是各自的方法(不是共享的)
由于是函数,所以在使用工厂函数创建对象时可以为其传递参数。
function fac(n){
return{
name:n,
age:18
}
}
let info3 = fac('ooxx')
console.log(info3) //{name: "ooxx", age: 18}
//或
function fac(n='oo'){
return{
n,
age:18
}
}
let info4 = new fac()
console.log(info4) //{n: "oo", age: 18}
二、构造函数和原型
在 ES6之前是没有类的概念的,只有构造函数和原型。
构造函数和类都是用于创建多个对象的,里面存放对象的公共部分(模板)
function Person(name){
this.name = name;
this.sayName = function(){
console.log(this.name);
}
}
var person1 = new Person("oo")
var person2 = new Person("xx")
person1.sayName(); //oo
person2.sayName(); //xx
- 优点:解决对象类型识别的问题
- 缺点:如果构造函数中有方法,会存在内存浪费问题。由于方法属于引用数据类型,所以会在内存中为其单独开辟一块空间进行存储,这样当new多个对象实例时就会开辟多个内存空间去存储一个相同的方法,不但浪费内存,还拉低执行效率(用为开辟空间也要时间)。
- 注意:虽然标准没有严格规定首字母大写,但构造函数的首写字母通常会大写。
- 构造函数调用过程中实际会经历以下 4 个步骤:
①创建一个新的空对象
②将构造函数的作用域赋值给此对象(使 this 指向这个新对象)
③执行构造函数中的代码(为这个新对象添加属性)
④返回新对象 - 对比于工厂函数,构造函数使用时需要使用 new ,不需要显式的创建一个对象,需要使用 this ,不需要 return 。
- 构造函数与其他函数的唯一区别,就在于调用它们的方式不同。只要通过new操作符来调用,那它就可以作为构造函数;
- 构造函数调用过程中实际会经历以下 4 个步骤:
- 构造函数的属性和方法又被称为成员,成员分为两类:静态成员和实例成员。
- 实例成员:构造函数内部通过this添加的成员,只能通过实例化的对象访问,如上面的 person1.sayName();使用Person.sayName()会报错,not function。
- 静态成员:在构造函数本身添加的成员。如Person.hobbies=‘打游戏’;静态成员只能通过构造函数本身来访问。如 console.log(Person.hobbies);使用console.log(person1.hobbies)结果为undefined
原型: 每个函数都有 prototype 属性,指向一个对象,这个对象包含有所有实例共享的属性和方法。可以解决构造函数每用 new 创建一个对象,就会重新将实例的方法创建一次的问题:
function Person(){
this.age=17;
}
Person.prototype.age = 21;//原型属性
Person.prototype.name = 'ooxx';//原型属性
Person.prototype.sayHi = function () {//原型方法
console.log('Hi');
}
let person1 = new Person()
let person2 = new Person()
person1.sayHi();//Hi
console.log(person1.age);//17
console.log(person1.name);//ooxx
console.log(person1.sayHi === person2.sayHi);//true
//原型对象上的属性方法都可以被实例对象继承,如果原型对象上添加的属性与构
//造函数属性名一致,则实例对象以构造函数添加的属性为主。
//上面属于构造函数+原型的方案:属性是私有的,方法是共有的。
Person.show=function(){//静态方法
console.log('静态方法!')
}
Person.show();//静态方法!
console.log(Person.prototype);//{age: 21, name: 'ooxx', sayHi: ƒ, constructor: ƒ}
在构造函数的原型定义方法后为什么实例对象可以使用呢(即实例对象怎么使用原型的方法)?因为对象上会有一个__proto__属性(并不是只有实例对象才有,只要是对象就有)指向构造函数的prototype(原型对象)。即对象的__proto__与其构造函数的prototype是等价的。验证如下:
function Demo(){}
let oo = new Demo()
console.log(oo.__proto__ === Demo.prototype);//true
对象原型__proto__和构造函数原型prototype都会有一个constructor属性,我们称之为构造函数,因为其指向构造函数本身。作用:指出对象是由什么构造函数实例化出来的。还一个作用,如果给构造函数原型以
Person.prototype={
sing:finction(){},
dance:function(){}
}
形式添加方法,此时相当于Person.prototype重新指向,则其不在含有constructor属性了。此时可以手动给其添加constructor属性,让其重新指向原来的构造函数。
Person.prototype={
constructor:Person,
sing:finction(){},
dance:function(){}
}
三、原型链
函数的原型(prototype)也是一个对象,其有两个属性:constructor 和 __proto__,可以 console.log(函数.prototype) 输出查看。 其中 constructor 指向构造函数本身,__proto__指向Object.prototype;Object.prototype.constructor 指向Object()构造函数,Object.prototype.__proto__ 值为null。Object站在原型链最顶端。
下图为构造函数、实例对象、原型三者之间的关系图:
下图为原型链示意图:
js成员查找机制:按照原型链从下往上进行查找,如果原型链上都有则就近原则。
什么是原型:
1、每个函数都有一个 prototype 属性,该属性指向的是原型对象。其有两个值:constructor、__proto__。
2、每个实例对象都有一个 __proto__ 属性,该属性指向的是其构造函数的原型。即:构造函数.prototype === 其实例对象的.__proto__。
什么是原型链:
1、查找对象的属性时先在自身找,若没找到则沿着 __proto__ 找构造函数的原型对象。
2、如果构造函数的原型对象还没有,继续沿着 __proto__ 查找,直到找到 Object 的原型。
3、如果还没找到,则返回 undefined。
4、沿着 __proto__ 查找的就是原型链。
构造函数、实例对象、原型三者之间的关系以及原型链验证代码:
function Demo(){}
console.log(Demo.prototype) //{constructor: ƒ Demo(),__proto__: Object}
console.log(Demo.prototype.constructor) //ƒ Demo(){}
console.log(Demo.prototype.__proto__ === Object.prototype) //true
console.log(Demo.prototype.__proto__.__proto__) //null
let oo = new Demo()
console.log(oo) //Demo {}
console.log(oo.__proto__ === Demo.prototype) //true,对象的__proto__指向构造函数的prototype
console.log(oo.__proto__.constructor)//指向构造函数
构造函数的继承(ES6之前的写法)
function Father(){
this.name = 'fname'
this.age = 18
}
Father.prototype.money = function(){
console.log('两毛钱!')
}
function Son(){
this.name = 'Sname'
Father.call(this)
//调用之后 Father() 的this指向 Son() ,所以相当于son.name = 'fname',即子构造函数继承了父构造函数的name属性
this.age = 12 //上面继承,这里添加自己的。这样既可以继承父构造函数的属性又可以添加自己的属性
}
//上面只是继承了父构造函数的属性,但是还没继承父构造函数添加在原型上的方法
Son.prototype = new Father()//继承父构造函数添加在原型的方法
Son.prototype.constructor = Son//修改constructor指回原来的构造函数
let s1 = new Son()
console.log(s1) //Son {name: "fname", age: 12}
console.log(Son.prototype.constructor) //ƒ Son(){}