我们先回忆一下JS中的数据类型
String 字符串
Number 数值
Boolean 布尔值
Null 空值
Undefined 未定义类型
以上类型属于基本数据类型,以后我们看到的如果不是以上的5种,全都是对象类型
JavaScript的所有数据都可以看成对象,那是不是我们已经在使用面向对象编程了呢?
当然不是。如果我们只使用Number、Array、string以及基本的{…}定义的对象,还无法发挥出面向对象编程的威力。
面向对象的两个基本概念:
1.类:类是对象的类型模板,例如,定义Student类来表示学生,类本身就是一种类型,Student表示学生类型,但不表示任何具体的某个学生;
2.实例:实例是根据 类型创建 的对象,例如,根据Student类可以创建出xiaoming、xiaolan等多个实例,每个实例表示一个具体的学生,他们全都属于Student类型。
对象的分类:
1.内建对象
由ES标准中定义的对象,在任何的ES的客观中都可以使用
例如:Math String Number Boolean Function Object…
2.宿主对象
由JS的运行环境提供的对象,目前来讲主要指由浏览器提供的对象
比如 BOM DOM
3.自定义对象
由开发人员自己创建的对象
但是在JavaScript中,不区分类和实例的概念,而是通过原型(prototype)来实现面向对象编程。
原型是指当我们想要创建xiaoming这个具体的学生时,我们并没有一个Student类型可用。那怎么办?恰好有这么一个现成的对象:(对象的基本操作)
//创建一个Object对象
var obj = new Object();
//放入属性
obj.name = '孙悟空';
obj.gender = '男';
obj.age = 18;
console.log(obj);
//取出对象的属性
console.log(obj.name+','+obj.gender+','+obj.age);
//修改对象的属性,也就是覆盖值
obj.name = 'hk';
//删除对象的属性
delete obj.name;
console.log(obj.name);
var obj = new Object();
//放入属性,使用的是变量的形式去操作属性,这样做会更加的灵活
obj['name'] = '孙悟空';
obj['gender'] = '男';
obj['age'] = 18;
console.log(obj);//{ name: '孙悟空', gender: '男', age: 18 }
var n = 'name';
var g = 'gender';
var a = 'age';
console.log('obj[n]:'+obj[n]);//孙悟空
console.log('obj[g]:'+obj[g]);//男
console.log('obj[a]:'+obj[a]);//18
var obj2 = new Object();
obj2.name = '小张三';
obj.test = obj2;//给obj里面赋值为另一个对象
console.log(obj.test);//{ name: '小张三' }
console.log(obj.test.name)//小张三
JS中的变量都是保存到栈内存中的
基本数据类型的值直接在栈内存中存储
值与值之间是独立存在,修改一个变量不会影响其他的变量
对象是保存在堆内存,这里两个对象指的是同一个堆内存空间所以obj1改变时,obj2也会改变!
var c = 10;
var d = 10;
//如果两个变量的值相等,那么这两个变量也是相等的
console.log(c === d);
var obj3 = new Object();
var obj4 = new Object();
obj3.name = '沙和尚';
obj4.name = '沙和尚';
//两个对象虽然值是一样的,但还是两个对象
//新建一个对象就相当于在堆内存中开辟一个内存块,每创建一个对象都会新建
console.log(obj3 === obj4);
这两个对象的内存地址不一样!
使用对象字面量来定义对象:(边定义边赋值)
var Student = {
name:'Robot',
height:150,
run: function () {
console.log(this.name+' is running....');
}
};
var hk = {
name:'gg'
};
//把hk的原型指向了对象Student
hk.__proto__ = Student;
console.log(hk.name);
console.log(hk.run());
hk有自己的name属性,但并没有定义run()方法。不过,由于hk是从Student继承而来,只要Student有run()方法,hk也可以调用:
JavaScript的原型链和Java的Class区别就在,它没有“Class”的概念,所有对象都是实例,所谓继承关系不过是把一个对象的原型指向另一个对象而已。
如果你把hk的原型指向其他对象:
var Bird = {
fly: function () {
console.log(this.name + ' is flying...');
}
};
var hk = {
name:'gg'
};
//把hk的原型指向了对象Bird
hk.__proto__ = Bird;
console.log(hk.name);//gg
console.log(hk.fly());//gg is flying...
创建 对象
JS对每个 创建的对象都会设置现代战争原型,指向 它的原型对象。
当我们用Obj.xxx访问一个对象的属性时,JS引擎先在当前 对象上查找该属性,如果没有找到,就到其原型对象上找,如果没有找到,就一起上溯到Object.prototype对象,最后,如果还没有找到,就只能返回undefined。
例如,创建一个Array对象:
var arr = [1,2,3];
其原型链是:
arr --->Array.prototype --->Object.prototype ------>null
Array.prototype定义了indexOf()、shift()等方法,因此你可以在所有的Array对象上直接调用这些方法。
function foo(
return 0;
}
函数的原型链:
arr --->Function.prototype --->Object.prototype ------>null
注意:
如果原型链很长,那么访问一个对象的属性就会因为花更多的时间查找而变得更慢,因此要注意不要把原型链搞得太长。
构造函数
除了直接用{…}创建一个对象外,JS还可以用一种构造函数的方法来创建对象,它的用法 是,先定义一个构造函数:
function Student(name) {
this.name = name;
this.hello = function(){
console.log("hello baby ");
}
}
//在js中,用关键字new可以来调用函数 ,并返回一个对象
var hk = new Student('凯凯');
console.log(hk.name);//凯凯
console.log(hk.hello());//hello baby
新创建的hk的原型链是:
hk --->hk.prototype --->Object.prototype --->null
function Student(name) {
this.name = name;
this.hello = function(){
console.log("hello baby ");
}
}
//在js中,用关键字new可以来调用函数 ,并返回一个对象
var hk = new Student('凯凯');
var zs = new Student('zs');
console.log(hk.name);//凯凯
console.log(hk.hello());//hello baby
console.log(zs.name);
console.log(zs.hello());
console.log(hk.constructor === Student.prototype.constructor);//true
console.log(Student.prototype.constructor === Student);//true
console.log(Object.getPrototypeOf(hk) === Student.prototype);//true
console.log(hk instanceof Student);//true
红色箭头是原型链,Student.prototype指向的对象就是hk、zs的原型对象,这个原型对象自己还有个属性constructor,指向Student函数本身。
另外,函数Student恰好有个属性prototype指向hk、zs的原型对象,但是hk、zs这些对象没有prototype这个属性,不过可以用__proto__这个非标准用法 来看。
现在我们就认为hk、zs这些对象“继承”自Student。
不过还有一个小问题,注意观察:
xiaoming.name; // '小明'
xiaohong.name; // '小红'
xiaoming.hello; // function: Student.hello()
xiaohong.hello; // function: Student.hello()
xiaoming.hello === xiaohong.hello; // false
hk和zs各自的name不同,是为了区分
但是它们的hello函数,代码和名称都是一样的!
如果我们通过new Student()创建了很多对象,这些对象的hello函数实际上只需要共享同一个函数 ,这样可以节省很多的内存。
要让创建的对象共享一个hello函数,根据对象的属性查找原则,我们只要把hello函数移动到hk、zs这些对象共同的原型上就可以了,也就是Student.prototype:
修改后的代码:
function Student2(name) {
this.name = name;
}
//直接指定Student2.prototype
Student2.prototype.hello = function () {
console.log("hello Student2.prototype.hello ");
}
var ls = new Student2('ls');
var ww = new Student2('ww');
console.log(ls.name);//ls
console.log(ls.hello());//hello Student2.prototype.hello
console.log(ww.name);//ww
console.log(ww.hello());//hello Student2.prototype.hello
console.log(ww.hello === ls.hello);//true
js中的工厂模式:(可以不用new对象,直接在内部封装所有的new操作).
function Student(props) {
this.name = props.name || '匿名';//默认值 为'匿名'
this.grade = props.grade || 1;//默认值为1
}
Student.prototype.hello = function () {
console.log('Hello ,'+this.name +'!');
}
function createStudent(props) {
return new Student(props || {})
}
//创建对象
var hk = createStudent({
name:'凯凯',
grade: 20
});
console.log(hk.grade+','+hk.name);//20,凯凯
如果创建的对象有很多属性,我们只需要传递的某些属性,剩下的属性可以用默认值。由于参数是一个Object, 我们无需记忆参数的顺序。如果恰好从JSON拿到了一个对象,就可以直接创建出hk。
例子:
function Cat(name) {
this.name = name;
}
Cat.prototype.say = function () {
return `Hello,${this.name}`
}
var kitty = new Cat('kitty');
console.log(kitty.say());//Hello,kitty
原型继承
在传统的基于Class的语言如Java、C++中,继承的本质是扩展一个已有的Class,并生成新的Subclass.
但是在JS中由于采用的是原型继承,无法直接扩展一个Class,根本不存在Class这种类型。
现在我们要基于Student扩展出PrimaryStudent,可以先定义出PrimaryStudent:
function Student(props){
this.name = props.name || 'Unnamed';
}
Student.prototype.hello = function () {
console.log('hello'+this.name+'!');
}
function PrimaryStudent(props) {
//调用Student构造函数,绑定this变量:
Student.call(this.props);
this.grade = props.grade || 1;
}
但是,调用了Student构造函数不等于继承了Student,PrimaryStudent创建的对象的原型是:
new PrimaryStudent() ----》 PrimaryStudent.prototype -----》Object.prototype ----->null
必须把原型修改为:
new PrimaryStudent() ----> PrimaryStudent.prototype ----> Student.prototype ----> Object.prototype ----> null
我们可以通过一个空函数 F来实现:
function Student(name) {
this.name = name;
}
Student.prototype.hello = function () {
console.log('hello'+this.name+'!');
}
function PrimaryStudent(props) {
//调用Student构造函数,绑定this变量:
Student.call(this.props);
this.grade = props.grade || 1;
}
//空函数F
function F() {
}
//把F的原型指向Student.prototype:
F.prototype = Student.prototype;
//把PrimaryStudent的原型指向一个新的F对象,F对象的原型正好指向 Student.prototype
PrimaryStudent.prototype = new F();
//把PrimaryStudent原型的构造函数修复为PrimaryStudent
PrimaryStudent.prototype.constructor = PrimaryStudent;
//继续在PrimaryStudent原型(就是new F()对象)上定义方法
PrimaryStudent.prototype.getGrade = function () {
return this.grade;
};
//创建hk
var hk = new PrimaryStudent({
name:'凯凯',
grade:18
});
console.log(hk.name);
console.log(hk.grade);
console.log(hk.hello());//
//验证原型:
console.log(hk.__proto__ === PrimaryStudent.prototype);//true
console.log(hk.__proto__.__proto__ === Student.prototype);//true
//验证继承关系
console.log(hk instanceof PrimaryStudent);//true
console.log(hk instanceof Student);//true