面向对象
JavaScript对每个创建的对象都会设置一个原型,指向它的原型对象。
当我们用obj.xxx
访问一个对象的属性时,JavaScript引擎先在当前对象上查找该属性,如果没有找到,就到其原型对象上找,如果还没有找到,就一直上溯到Object.prototype
对象,最后,如果还没有找到,就只能返回undefined
。
例如,创建Array
一个对象
let arr = [1,2,3]
那么,arr的原型链就是
arr ----> Array.prototype ----> Object.prototype ----> null
Array.prototype
定义了indexOf()
、shift()
等方法,因此你可以在所有的Array对象上直接调用这些方法。
而当我们创建一个函数的时候
function fun(){
return 1;
}
那么,函数fun的原型链就是
fun ----> Function.prototype ----> Object.prototype ----> null
由于Function.prototype
定义了apply()
等方法,因此,所有函数都可以调用apply()
方法。
构造函数
除了直接用{ ... }
创建一个对象外,JavaScript还可以用一种构造函数的方法来创建对象。它的用法是,先定义一个构造函数:
function Student(name){
this.name = name
this.say = function(){
console.log('Hello' + ',' + this.name)
}
}
看到这个函数,你可能会想,这不就是一个普通的函数吗?
这确实是一个普通函数,如果调用它是得这样的
function Student(name){
this.name = name
this.say = function(){
console.log('Hello' + ',' + this.name)
}
return this
}
let xiaoming = Student('小明')
console.log(xiaoming)//小明
xiaoming.say()//Hello,小明
普通函数的调用一定要return this
,因为此时Student里的this环境变量指向的是window,于是无意间创建了全局变量name,并且返回undefined
而构造函数就是用new
字符(关键字)来调用函数Student,并返回一个对象,如下:
function Student(name){
this.name = name
this.say = function(){
console.log('Hello' + ',' + this.name)
}
}
let xiaohong = new Student('小红')
console.log(xiaohong)//小红
xiaohong.say()//Hello,小红
所以,如果不写new
,Student就是一个普通函数,如果不return this
的话就会抛出undefined
;如果写了new,它就变成了一个构造函数,它绑定的this指向新创建的对象,并默认返回this,也就省了return this
.
新创建的xiaohong
的原型链就是
xiaohong ----> Student.prototype ----> Object.prototype ----> null
也就是说,xiaohong
的原型指向的是函数Student
的原型。同理,如果你创建又new出了xiaojun xiaopeng
结果也都是一样的
xiaojun ----> Student.prototype ----> Object.prototype ----> null
xiaopeng ----> Student.prototype ----> Object.prototype ----> null
用new Student()
创建的对象还从函数原型上或得了一个constructor
的属性,它指向函数Student
本身
xiaohong.portotype.constructor == Student.portotype.constructor //true
xiaohong.prototype.constructor == Student //true
Object.getgetPrototypeOf(xiaohong) == Student.prototype //true
xiaohong instanceof Student //true
Student.prototype
指向的就是xiaojun xiaopeng
的原型对象,这个对象有个属性constructor
指向函数Student
本身;函数Student
也有自己的一个属性prototype
指向xiaojun xiaopeng
这些对象的原型,但是xiaojun xiaopeng
可没有属性直接指向函数Student
本身,不过可以用__proto__
这个非标准用法来查看. 所以我们就可以认为xiaojun xiaopeng
是继承于Student
new的好处
xiaoming.name; // '小明'
xiaohong.name; // '小红'
xiaoming.say; // function: Student.say()
xiaohong.say; // function: Student.say()
xiaoming.say === xiaohong.say; // false
为了我们区别,xiaoming
和xiaohong
有着各自的name
xiaoming
和xiaohong
也有着各自的say()
方法,虽然有着同一个方法,函数名称和代码都是相同的,但它们是两个不同的函数
所以,如果我们通过new Student()
创建了很多对象,这些对象的say()
函数实际上只需要共享同一个函数就可以了,这样可以节省很多内存
要让创建对象共享一个say()
函数,根据对象属性的查找原则,我们只需要把say()
移动到xiaoming
和xiaohong
对象的原型上就可以,也就是Student.prototype
修改代码如下:
function Student(name){
this.name = name
}
Student.prototype.say = function() {
console.log('Hello' + ',' + this.name)
}
用new
创建基于原型的JavaScript的对象就是这么简单!
忘记写new怎么办
关于new
的重要性上述已经说过了
我们还可以编写一个CreateStudent()
函数,在内部封装所有的new
,代码如下:
function Student(data){
this.name = data.name || '匿名'
this.age = data.age || 22
}
Student.prototype.say = function () {
console.log('Hello' + ' ' + this.name + '!')
}
function CreateStudent(data){
return new Student(data || {})
}
这个CreateStudent()
函数有几个巨大的优点:一是不需要new来调用,二是参数非常灵活,可以不传,也可以这么传:
let xiaoming = CreateStudent({
name:'小明'
})
console.log(xiaoming.age)//22
xiaoming.say()//Hello 小明!
如果创建的对象有很多属性,我们只需要传递需要的某些属性,剩下的属性可以用默认值。由于参数是一个Object
,我们无需记忆参数的顺序。如果恰好从JSON拿到了一个对象,就可以直接创建出xiaoming
本文链接:https://www.liaoxuefeng.com/wiki/1022910821149312/1023022043494624