JavaScript学习笔记(面向对象编程一)

我们先回忆一下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

在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章