面向过程和面向对象
区别与意义
面向过程:分析解决问题所需要的步骤,用函数将这些步骤一步一步的实现,在主函数中将这些函数一个一个调用即可
面向对象:把构成问题的事务(事务:一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)),分解成各个对象,建立对象的目的是为了描述某个事物在整个解决问题的步骤中的行为
举一个例子:五子棋
==面向过程==:设计思路就是首先分析问题的步骤:1、开始游戏,2、黑子执行,3、绘制画面,4、判断输赢,5、白子执行,6、绘制画面,7、判断输赢,8、返回步骤2,9、输出最后结果。把上面每个步骤用不同的方法来实现。
==面向对象==:设计思路,整个五子棋可以分为1、黑白双方,这两方的行为是一模一样的,2、棋盘系统,负责绘制画面,3、规则系统,负责判定诸如犯规、输赢等。第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。
总结:面向过程是以步骤划分问题,面向对象是以功能来划分问题。
面向对象
封装
new创建的对象,实质是对新对象this的不断赋值,并且将prototype指向类的prototype所指向的对象,由于prototype指向相同,因此在prototype声明的属性和方法都可以被实例使用,但是在类中使用点方法声明的属性和方法,由于this指向不同,因此无法使用
var Book = function(name) {
this.set = true;
this.name = name;
};
Book.inChinese = true;
Book.reset = function() { console.log('reset'); }
Book.prototype = {
isJSBook : false,
dispaly: function() {}
}
var b = new Book('javascript');
console.log('b', b) // b Book{set: true, name: "javascript",__proto__: {dispaly: ƒ (),isJSBook: false,__proto__: Object}
console.log('set', b.set) // set true
console.log('inChinese', b.inChinese) // inChinese undefined
console.log('reset', b.reset) // reset undefined
console.log('isJSBook', b.isJSBook) // isJSBook false
console.log('dispaly', b.dispaly) // dispaly ƒ () {}
需要注意的是,prototype 对 Book 是不可见的,也就是说,Book 不会查找 prototype 中的属性和方法。
由于通过new关键字实例化对象时,会对类执行一次,所以类内部this上定义的属性和方法可以复制到新创建的对象上,因此类中声明的this.set在实例中也可以直接被调用
new
new运算符是用来实例化一个类,从而在内存中分配以个实例对象,以上面的代码为例
var b = new Book('javascript');
js引擎执行这句代码时,在内部做了很多工作,伪代码模拟其工作流程如下:
new Book('javascript') = {
var obj = {};
obj.__proto__ = Book.prototype;
Book.call(obj, 'javascript');
return obj;
}
解释一下上面的代码
- 创建一个空对象obj
- 把obj的proto指向Book的原型对象prototype,此时便建立了obj对象的原型链:==obj->Book.prototype->Object.prototype->null== 有关原型链的概念在下文有详细描述
- 在obj对象的执行环境调用Book函数并传递参数'javascript'。执行完此句后,obj便产生了name属性,并且赋值为'javascript'
- 则将obj返回作为新对象。
因此代码中的b就是第四步中返回的对象值,在查找信息或调用方法时会沿着原型链查找。由于Book.inChinese的信息不在原型链上,因此在b中调用时不会拿到该属性的信息。
原型链
js里的对象可以大致分成两类,普通对象Object和函数对象Function,一般而言,通过 new Function 产生的对象是函数对象,其他对象都是普通对象。
function f1() {}
var f2 = function () {};
var f3 = new Function('x', 'console.log(x)');
var o1 = {};
var o2 = new Object();
var o3 = new f1();
console.log('typeof f1:', typeof f1) // typeof f1: function
console.log('typeof f2:', typeof f2) // typeof f2: function
console.log('typeof f3:', typeof f3) // typeof f3: function
console.log('typeof o1:', typeof o1) // typeof o1: object
console.log('typeof o2:', typeof o2) // typeof o2: object
console.log('typeof o3:', typeof o3) // typeof o3: object
f1属于常见的函数定义方式,f2属于函数表达式的形式,也比较常见,f3的用法并不常见,但是是一种函数对象,而且在创建函数对象时,js会自动通过new Function()的方式构建对象。
变量o1,o2是常见的创建普通对象的形式,重点在o3,不难看出o3是f1的实例对象,按照默认思路o3应该与f1同一类型,但是,并非这样,o3 不是通过 new Function 产生,在new模块 中也说到了o3产生的过程,他只是一个普通对象
下面解释一下原型和原型链:
对象中都包含proto 属性,但prototype属性只有函数对象才有,即普通对象包括实例对象都不含有prototype属性
prototype属性对于当前函数对象是不可见的
function f(){}
f.prototype.foo = "abc";
console.log(f.foo); // undefined
当前函数中不能调用prototype上的方法,prototype 的主要作用就是继承 ,在prototype中定义的方法和变量都是留给自己的后代使用的,对于prototype 如何将方法和变量留给后代使用,就需要了解js中的原型链,此时就用到了上文说的proto属性。
对象中都包含proto属性,无论是普通对象还是函数对象,他的作用是引用父类的prototype 对象。在js中使用new操作符创建对象时就会将父类的prototype 赋值给新对象的proto属性,达到继承作用。(
JavaScript规定了Object.prototype.__ proto __ === null,
Function.prototype.__ proto __ === Object.prototype)
function f() {}
f.prototype.foo = "abc";
var obj = new f();
console.log('obj.foo:', obj.foo);
console.log('f.prototype', f.prototype);
console.log('obj.__proto__:', obj.__proto__);
结果如下:
综上原型链如下:
继承
instanceof能够检测某个对象是否是某个类的实例,以A instanceof B为例,原理是通过判断B的prototype是否在对象A的原型链上,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回false。
继承的方式有以下几种:
- 类式继承
- 构造函数继承
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生组合式继承
类式继承
也叫原型式继承,
function SuperClass(value) {
this.superValue = value;
}
SuperClass.prototype.getSuperValue = function () {
return this.superValue;
}
function SubClass() {
this.subValue = false;
}
SubClass.prototype = new SuperClass(true);
SubClass.prototype.constructor = SubClass; // 要写,此时SubClass的constructor 属性丢失。
SubClass.prototype.getSubValue = function () {
return this.subValue;
}
var sub = new SubClass();
console.log('SuperClass.prototype', SuperClass.prototype)
console.log('SubClass.prototype', SubClass.__proto__)
console.log('sub', sub)
缺点:
- 由于子类通过原型prototype对父类实例化,继承了父类,所以说父类中的共有属性要是引用类型,就会在子类中被所有实例共用,因此一个子类的实例更改子类原型从父类构造函数中继承来的共有属性就会直接影响到其他子类