引言
javascript里没有类,那么就使用构造函数来充当类
1.构造函数
构造函数 ,是一种特殊的方法。主要用来在创建对象时初始化对象。每个构造函数都有prototype(原型)属性
function fn () {
}
function Fn () {
}
var a = fn()
var b = new Fn()
console.log(a) // undefined
// 称 b 为 Fn 的实例,Fn 是b 的抽象
console.log(b) // 空对象
其实构造函数和普通的函数没有多大的区别,主要的区别在于一个可以直接调用,一个需要new 出一个实例,然后再进行调用
区别:
- 构造函数的函数名一般首字母要大写 如 : Fn Test ,普通函数则没有这个说法
- 构造函数需要通过 new 关键字创建实例,然后由实例去调用函数体内的属性和方法
进阶版
function Person(name, age) {
// 构造函数里的this指的是将来new的那个实例
// this是将来的实例,给实例新增一个name属性,属性值为参数name
this.name = name
this.age = age
// 给对象新增方法
this.say = function () {
console.log(`My name is ${this.name}`)
}
}
var xm = new Person('xiaoming', 18)
console.log(xm)
xm.say()
var xh = new Person('xiaohong', 20)
console.log(xh)
xh.say()
// 虽然say方法体是一样的,但是每一个实例里有一个自己的say,地址是不一样的
console.log(xm.say === xh.say) // false
当我们通过new运算符来构造函数的时候到底发生了什么(第一步和第三步咱们是看不见的)
- 构造函数里会创建一个空对象,然后把this指向这个对象
- 执行构造函数里的代码,给this对象新增属性
- 会把这个对象return出来
2.原型
每个函数都有prototype(原型)属性,这个属性是一个指针,指向一个对象,通俗的讲,这个对象是一个公共的位置可以为所有实例存放共享的属性和方法,是的每一个实例都可以调用上面的方法和属性,而每个实例内部都有一个指向原型对象的指针。
但是一般普通的函数我们不会在其原型上做过多的操作,这里我们主要操作构造函数的原型
例子
function Person(name, age) {
this.name = name
this.age = age
// this.say = function () {
// console.log(`My name is ${this.name}`)
// }
}
// 给Person原型上新增属性或者方法在外面写,因为这句话只需要执行一次,
//而构造函数时每次new的时候都会调用
// 给Person的原型对象上新增一个say方法
// 这个say只存一份,存在公共的位置,这样每一个实例就都可以调用同一个say了
// 在原型上写的方法
Person.prototype.say = function () {
console.log(`My name is ${this.name}`)
}
console.log(Person)
// console.dir可以看到一些对象的内部结构
console.dir(Person)
// new出第一个实例
var xm = new Person('xiaoming', 18)
console.log(xm)
// 首先看自己有没有say,如果有,那就直接调用
// 没有的话就去公共位置Person.prototype上找
xm.say()
// new出第二个实例
var xh = new Person('xiaohong', 20)
console.log(xh)
xh.say()
// xm和xh本身自己都没有say,在公共位置存了一份,所以他们是同一个函数
console.log(xm.say === xh.say) // true
// 面向对象编程的时候一般把属性写在构造函数里,方法写在原型上
面向对象打印乘法表
function Cfb(n) {
this.n = n
this.str = ""
this.init()
this.print()
}
// 对象合并,把新增的方法合并到 prototype
Object.assign(Cfb.prototype, {
// ES6的新增写法,跟写键值对是一样的
// init () {} ==>> init : function () {}
init() {
for (var i = 1; i < this.n; i++) {
for (j = 1; j <= i; j++) {
this.str += (`${j} X ${i} = ${i * j}`)
}
this.str += "<br>"
}
},
print() {
document.write(this.str)
}
})
new Cfb(9)
new Cfb(6)
这里 Object.assign(n1,n2,n…) 是对象合并,将后面的方法合并到第一个方法上,如果第一个是空的话,可以在不改变其他方法的情况下,将所有的方法合并到一个新的方法里,就可以调用所有的属性和方法.
原型链
原型链 : 一个对象在调用方法或者访问属性的时候沿着原型一层一层向上找的链式结构
通过例子可以更好理解原型链
function Dog(name) {
this.name = name
}
Dog.prototype.say = function () {
console.log(`my name is ${this.name}`)
}
var snoppy = new Dog("snoppy")
snoppy.say()
console.log(snoppy)
// 实例对象身上有一个 __proto__ 属性,这个属性是一个指针,指向构造函数的原型对象
// __proto__ 也叫隐式原型
console.log(snoppy.__proto__ === Dog.prototype) // true
// Dog.prototype 是一个普通的对象,所以他的构造函数是 Object
// Dog.prototype作为普通对象来看待,那么他的__proto__ 指向Object.prototype
console.log(Dog.prototype.__proto__ === Object.prototype) // true
console.log(Dog.prototype instanceof Object) // true
console.log(Object.prototype.__proto__) // null 到顶了
// 当snoopy调用toString方法的时候先找自己,自己没有沿着原型链一层一层往上找,
// 在Object.prototype找到了这个方法然后就调用,如果Object.prototype也没有,那就报错
console.log(snoppy.toString())
通俗的讲:
// 实例对象的__proto__ 指向构造函数的prototype
// snoopy是实例,Dog是他的构造函数
// Dog.prototype是实例,它的构造函数是Object
补充:
// js 中所有的数据都是 Object 的实例, 任意数据使用 instanceof 去验证 Object 都会得到 true
// js 中的任意数据都可以沿着原型链去找到 Object.prototype 因此,我们说 js 里一切皆为对象
instanceof 可以用来验证一个实例是否是某个对应的构造函数的实例,就上述例子而言,可以这样用:
console.log(snoopy instanceof Dog) // true
console.log(snoopy instanceof Object) // true
注意: 还可以通过 instanceof 来验证出数组
var arr = [1,2,4,62,21]
console.log(typeof arr) // object
var obj = {name : "zhangsan"}
console.log(typeof obj) // object
// 所以用typeof 不能验证出数组
console.log(arr instanceof Array) // true
console.log(obj instanceof Array) // false