面向对象
想开一个车,你不需要自己去造一个车,只需要一把钥匙,车对于你来说就是一个"对象"。
在JavaScript中的"对象", 具有属性,当属性的值是一个函数时,那么此属性就是这个对象的方法.访问属性的方式参考Object介绍
如何创建对象?
首先想到的是字面量创建对象,然后又先创建一个对象实例,后一次对对象属性进行赋值.但是无论这两种那个方法都有个缺点,当需要重复创建大量相同的对象时,产生大量代码这是很不理想的,这时想到函数的一个功能就是讲重复代码进行简化,以便多次使用,那么构造函数形式创建多个对象方式就是理想的选择了.
什么是构造函数?
function outPut() {
console.log('hello')
}
function Person (name,age) {
this.name2 = name;
this.age2 = age;
this.sayName = function () {
console.log('this.name2')
};
}
var person2 = new person("Kangkang" , 20)
// 分别调用
这里有几点需要注意,首先是这个this是谁? new操作符有什么作用?
- this指向的创建的实例,这里指的是
person2
, 而new
操作符可以创建一个对象实例.
使用new通过构造函数方式创建对象的实际包含的步骤有:
- 创建一个新对象,
- 将构造函数的作用域赋给新对象,此时this已经指向新对象,操作this等同操作新对象
- 执行构造函数里的代码
- 返回创建的新对象
如何检测对象的类型?
使用instanceof
操作符,返回布尔值.
p1 instanceof Person;
p1 instanceof Object;
第二个检测,无论是自己创建的构造函数对象还是字面量对象,均为true,因为他们均继承自Object.
那么对于构造函数创建的对象里的方法,每次创建实例时都要重新创建,如何解决?
思路是将这个方法提到实例的外面去,如果是全局对象不就可以直接调用了吗?可是全局函数对于封装来说可没好处,所以原型模式也就有了用处.
原型
- 原型是定义能够被对象实例所使用的属性和函数的一种方式.原型的属性会变成实例的属性.
这段话不容易理解.一点点分析,对象的原型属性(prototype)指向的是一个对象,对象里存的属性和方法在对象实例后(即构造函数方法创建的新对象) 可以被实例对象访问使用.
function Person() {
}
Person.prototype.name = "Kangkang"
Person.prototype.age = 20
Person.prototype.sayName = function () {
console.log(this.name)
}
var person1 = new Person()
person1.sayName() // "Kangkang"
var person2 = new Person()
person2.sayName() //"Kangkang"
console.log(person1.sayName == person2.sayName) //true
var person3 = new Person()
person3.name = "Mary"
person3.sayName() //"Mary"
在函数创建的时候,prototype属性就被创建出来了,通过构造函数生成的实例在浏览器下支持一个属性proto 访问prototype(原型里的对象)
如图所示
显示的是当一个实例在自己属性中找不到相应的方法或者属性时往上一层寻找的情况。
检测某个属性属于实例还是原型:
-
person1.hasOwnProperty("name")
当这个属性是实例的返回true,是原型的返回false - 注意到在实例中重写某个属性会阻止访问原型中的这个属性,可是不会影响到原型中的属性。
单独操作的in操作符
- 使用这个操作符可以检测某个属性是否可以通过对象访问,这也就指的是无论这个属性是实例中的还是原型中的,要知道实例可以通过
__proto__
访问,由上图可知 -
"name" in Person1
可以访问返回true,否则返回false - 这个操作符可以和上面的
hasOwnperty()
结合使用判断某个属性是否存在且存在于哪里。
如何为原型添加属性和方法?
由上面介绍可知,通过Person.prototype.name = "kangkang"
这种方法甚是繁琐,多个属性、方法将会导致反复的输入,所以对象字面量的书写方式就能很好解决这个问题。
function Person () {
}
Person.prototype = {
name : "Kangkang",
age: 20,
job: 'student',
sayName = function() {
console.log(name)
}
}
防止实例与原型的链断开
谨慎在初始化原型后再将其改变为另外一个对象
function Person () {
}
var friend = new Person()
Person.prototype.sayHi = function() {
console.log('hi')
}
friend.sayHI();
// 在创建对象后再对原型添加方法依然有效
下面这种情况:
function Person () {
}
var friend = new Person()
Person.prototype = {
name : "Kangkang",
age: 20,
job: 'student',
friends: ["xiaojun","xiaoming"],
sayName : function() {
console.log(name)
}
friend.sayName();// error
这种情况相当于在原型初始化后又重新生成对象并且赋值,而原来的实例是指向初始化时的对象地址,故无法访问新的原型里的属性和方法.
原型对象和属性的区别?
如下程序:
function Person () {
}
Person.prototype = {
name : "Kangkang",
age: 20,
job: 'student',
friends: ["xiaojun","xiaoming"],
sayName : function() {
console.log(name)
}
}
var person6 = new Person()
var person7 = new Person()
// person6.name = "Mary"
// console.log(person6.name)
// console.log(person7.name)
// person6.sayName = function() {
// console.log('hi')
// }
person6.friends.push('xiaohong')
console.log(person6.friends)
console.log(person7.friends)
console.log(person6.sayName == person7.sayName)
由上面的代码得出:
当对原型中基本类型值属性和方法在实例中重新添加会阻止寻找到原型中的属性和方法,当用delete 操作符删除后依然可以找到原型中的属性和方法,也就是说在实例中重定义这两种情况不会影响原型中的属性和方法,但是如果原型中的属性对应的值是引用类型,由于引用类型的特性,实例得到了相同的这个属性的地址,又由于原型是实例公用的方法,所以当任一实例操作这个引用值属性时都会影响到其他这个对象的实例。所以组合使用构造函数和原型也就很好解决这个问题。
使用构造函数模式和原型模式组合
function Person (name, age, job) {
this.name = name
this.age = age
this.job = job
this.friends = ['kangkang','mary']
}
Person.prototype = {
constructor : Person,
sayName : function () {
console.log(this.name)
}
}
var person1 = new Person('xiaojun',22,'student')
var person2 = new Person('xiaowang', 21, 'student')
person1.friends.push('xiaozhang')
console.log(person1.friends) // ["kangkang", "mary", "xiaozhang"]
console.log(person2.friends) // ["kangkang", "mary"]
console.log(person1.friends === person2.friends) //false
console.log(person1.sayName === person2.sayName) //true