目录
检测属性在实例上还是在原型对象上的方法——hasOwnProperty()
使用Object 构造函数或对象字面量可以方便地创建对象-----这些方式具有很大的缺陷:创建具有同样接口的对各对象需要重复编写大量代码
---------------------------------------------本节不讨论-------------------------------------------
工厂模式
function creatPerson(name,age){
let o = new Object();
o.name = name,
o.age = age,
o.sayname = function()
{
console.log(this.name);
};
return o;
}
let person1 = creatPerson("jack",18);
let person2 = creatPerson("jerry",19);
需要创建几个实例对象,就调用几次这个函数,
这种工厂模式虽然也可以解决创建多个类似对象的问题,但是没有解决对象标识的问题(新创建的对象是什么类型)
构造函数模式
实现上述相同的功能‘
function Person(name,age){
this.name = name;
this.age = age;
this.sayName = function()
{
console.log(this.name);
}
}
let person1 = new Person("jack",18);
let person2 = new Person("jerry",19);
perosn1.sayName();
person2.sayName()
可以看书这段代码根上述代码内部基本上是一样的,只有下列区别:
- 没有显式地创建对象
- 属性和方法直接赋值给了this;
这段代码使用了new 关键字,就执行了以下操作
在内存中创建一个新对象;
这个新对象内部的[[Prototype]]特性被赋值为构造函数的prototype属性
这个使用双括号的属性,可以理解为开发人员不能直接访问的属性,但是一些浏览器比如firefox\chrom等会在每个对象上暴露_proto_属性,通过这个属性可以访问到对象的原型,总之就可以把它看作实例对象上的_proto_属性。
构造函数内部的this被赋值为这个新对象(this的指向对象)
执行构造函数内部的代码(给新对象添加属性和方法)
如果构造函数返回一个非空对象,那么就返回该对象,如果没有返回,就返回刚刚创建的新对象;
上段代码中,构造函数就没有返回值,那么返回的就是我们通过new创建的新对象。
这个例子中,我们知道person1 person2 都是Person的实例对象,同时它们也是Object的实例,这是因为所有自定义函数都继承自Object.
可以通过instanof来验证
console.log(person1 instanceof Person) //true
console.log(person2 instanceof Person) //true
console.log(person1 instanceof Object) //true
console.log(person2 instanceof Object) //true
关于构造函数的说明以及存在的问题:
首先,构造函数也是函数,它与普通函数的唯一区别就是调用方式不同,任何函数只要使用了new操作符就是构造函数,否则就是普通函数。
如果函数使用了new 操作符,属性和方法就挂载到新创建的对象上,如果不使用new 操作符,属性和方法就被挂载到window 对象上,
//使用new
let person1 = new Person("jack",18)
person1.sayName(); //"jack"
//不使用new
Person("jerry",19);
window.sayName(); //"jerry"
//在另外一个对象的作用域中调用----改变this指向
let o = new Object();
Person.call(o,"kristic",40);
o.sayName(); //"kristic"
构造函数模式存在的问题
构造函数的主要问题就是 其定义的方法会在每个实例上都创建一遍,相当于以下代码:
function Person(name,age){
this.name = name;
this.ob = job;
tis.sayName = new Function('conoloe.log(this.name)')
}
在ES中,函数是对象,因此每次定义函数时,都会初始化一个对象。这样每个Person实例都会有自己的Function实例。以这种方式创建函数会带来不同的作用域链。
要解决这个问题,可以把函数定义转移到构造函数外部:
function Person(name,age){
this.name = name;
this.age = age;
this.sayNmae = sayName;
}
function sayName(){
console.log(this.name)
}
let person1 = new Person("jack",19)
let person2 = new Person("jerry",20)
person1 和 person2 共享了定义在全局作用域上的sayName函数
这样就解决了相同逻辑的函数重复定义的问题,如果这个对象需要多个方法,那么就要在全局作用域中定义多个函数。这个问题可以通过原型模式来解决
原型模式
每个函数都会创建一个prototype属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。使用原型对象的好处是,在它上面定义的属性和方法可以被对象实例共享。
function Person(){}
Person.prototype.name = "jack";
Person.prototype.age = 19;
Person.protitypr.sayName = function()
{
console.log(this.name)
};
let person1 = new Person();
person1.sayName(); //"jack"
let person2 = new Person();
person1.sayName(); //"jack"
console.log(person1.sayName == person2.sayName) //true
深入理解原型
无论何时,只要创建一个函数,就会创建一个prototype属性,这个属性指向原型对象。所有原型对象自动获取一个名为constructor的属性,指回与之关联的构造函数
每次调用构造函数函数创建一个新实例,这个实例内部的[[Prototypr]]指针就会被赋值为构造函数的原型对象。
这里的[[Prototype]]相当于_proto_ Person.prototype === person1._proto_
记住一句话:
实例与构造函数的原型对象有直接关系,实例与构造函数直接没有直接关系
原型模式存在的问题:
原型上的属性可以通过在实例上添加同名属性来简单地遮蔽原型上的属性。真正的问题来自引用值属性。也就是说:由于原型上的属性所有实例共享,也就是一个实例改变了这个引用值属性,也会影响其他实例。
举个栗子:
function Person(){}
Person.prototype = {
constructor :Person, //原型上默认有一个属性constroctor指回构造函数
name:"jack",
age:18,
colors:["red","blue","pink"],
}
let person1 = new Person();
let person2 = new Person();
person1.colors.push("yellow");
console.log(person1.colors); //["red","blue","pink","yellow"]
console.log(person2.colors); //["red","blue","pink","yellow"] ---- 也影响到了person2
如果有意需要所有实例共享数据,这是最好的,但是在实际开发中,每一个实例应该都有属于自己的属性副本,因此实例开发中不会单独使用原型模式。
原型层级
当我们通过对象访问属性时,会先在对象实例本身开始搜索,如果没有,就会访问原型对象,
在调用person1.sayName 时发生了什么?
首先JS引擎会问:person1实例有sayName属性吗?答案是没有。
继续搜索,person1的原型上有sayName属性吗?于是返回结果。
这就是所说的原型链
我们可以通过实例读取原型上属性的值,但是不可以通过实例对象重写这些值。如果在实例对象上添加了一个和原型上同名的属性,就会在实例上创建这个属性,这个属性会遮住原型对象上的属性。
function Person(){}
Person.prototype.name = "jack";
Person.prototype.age = 18;
let person1 = new Person();
let person2 = new Person();
person1.name = "jerry"
console.log(person1.name); //"jerry"
console.log(person2.name); //"jack"
在实例上添加同名属性,会遮蔽住原型对象上的同名属性,注意不是修改它,而是屏蔽了对它的访问而已,只能通过delete操作符完全删除掉实例上的这个属性,原型上的这个同名属性才可以再次被搜索。
检测属性在实例上还是在原型对象上的方法——hasOwnProperty()
该方法继承自Object ,会在属性存在于调用它的对象实例上时返回true
function Person(){}
Person.prototype.name = "jack";
Person.prototype.age = 18;
let person1 = new Person();
let person2 = new Person();
person1.name = "jerry"
console.log(person1.hasOwnProperty("name")) //true 来自实例
console.log(person2.hasOwnProperty("name")) //false 来自原型
对象实例上有被检测的属性,就返回true 否则false.
hasOwnProperty()方法和 in操作符 的区别:
in 操作符就是说,无论这个属性在实例上还是在原型上,只要可以被访问到,就返回true. 而hasOwnProperty()方法只有属性存在于实例上才返回true.