1.面向过程与面向对象
传统流程中我们编写一个一个函数来解决需求,这是一种面向过程的实现方式,使用这种方式,页面中会增加很多全局变量,而且不利于别人重复使用,一旦别人使用以前提供的方法,就不能轻易地修改,这不利于团队代码维护。
面向对象编程就是将需求抽象成一个对象,然后针对这个对象分析其特征(属性)与动作(方法)。这个对象我们称之为类。
面向对象编程思想中有一个特点,封装,就是说把你需要的功能放在一个对象里。对于JavaScript这种解释性的弱类型语言来说,没有经典强类型语言中那种通过class等关键字实现的类的封装方式,JavaScript中都是通过一些特性模仿实现的,但这也带来了极高的灵活性。
2.封装
2.1 创建一个类
首先声明一个函数保存在一个变量里(一般首字母大写)。
然后在这个函数(类)的内部对this变量添加属性和方法:
var Book = function(id, bookname, price) {
this.id = id;
this.bookname = bookname;
this.price = price;
}
也可以在类的原型上添加属性和方法:
Book.prototype.display = function() {
//展示这本书
};
或者
Book.prototype = {
display: function() {}
};
我们不能直接使用这个Book类,需要用new关键字来实例化新的对象,使用实例化对象的属性或方法时,通过点语法访问。
var book = new Book(10, 'JavaScript', 50);
console.log(book.price); //50
通过this定义的属性或方法是该对象自身拥有的,每次通过类创建一个新对象时,this指向的属性和方法都会得到相应的创建,而通过prototype继承的属性或方法是每个对象通过prototype访问到,所以我们每次通过类创建一个新对象时这些属性和方法不会再次创建。
2.2 属性与方法封装
由于JavaScript的函数级作用域,声明在函数内部的变量以及方法在外界是访问不到的,通过此特性即可创建类的私有属性以及私有方法。
而在函数内部通过this创建的属性和方法可看作对象公有属性和对象公有方法。
通过this创建的方法,不但可以访问对象的公有属性和公有方法,还能访问到类或对象自身的私有属性和私有方法,由于这些方法权利比较大,所以我们又将它看作特权方法。
在对象创建时通过使用这些特权方法我们可以初始化实例对象的一些属性,因此这些在创建对象时调用的特权方法还可以看作是类的构造器。
var Book = function(id, name, price) {
//私有属性
var name = 1;
//私有方法
function checkId() {}
//特权方法
this.getName = function() {};
this.getPrice = function() {};
this.setName = function() {};
this.setPrice = function() {};
//对象公有属性
this.id = id;
//对象公有方法
this.copy = function() {};
//构造器
this.setName(name);
this.setPrice(price);
};
在类外面通过点语法定义的属性以及方法被称为类的静态公有属性和类的静态公有方法。
而类通过prototype创建的属性或者方法在类实例的对象中是可以通过this访问到的,所以我们将prototype对象中的属性和方法称为公有属性和公有方法。
//类静态公有属性(对象不能访问)
Book.isChinese = true;
//类静态公有方法(对象不能访问)
Book.resetTime = function() {
console.log('new Tiem')
};
Book.prototype = {
//公有属性
isJSBook: false,
//公有方法
display: function() {}
};
要想在新创建的对象中使用isChinese需要通过Book类使用而不能通过this,如Book.isChinese
,而类的原型prototype上定义的属性在新对象里可以直接使用,这是因为新对象的prototype和类的prototype指向的是同一个对象。
var b = new Book(10, 'JavaScript', 50);
console.log(b.num); //undefined
console.log(b.isJSBook); //false
console.log(b.id); //10
console.log(b.isChinese); //undefined
console.log(Book.isChinese); //true
Book.resetTime(); //new Tiem
2.3 闭包实现
闭包是有权访问另外一个函数作用域中变量的函数,即在一个函数内部创建另外一个函数。
我们将这个闭包作为创建对象的构造函数,这样它既是闭包又是可实例对象的函数,即可以访问到类函数作用域中的变量,此时这个变量叫静态私有变量,而可访问的方法叫静态私有方法。当然闭包内部也有其自身的私有变量以及私有方法。
在闭包外部添加原型属性和方法看上去像是脱离了闭包这个类,所以我们有时候在闭包内部实现一个完整的类然后将其返回。
//利用闭包实现
var Book = (function() {
//静态私有变量
var bookName = 0;
//静态私有方法
function checkBook(name) {}
//创建类
function _book(newId, newName, newPrice) {
//私有变量
var name, price;
//私有方法
function checkID(id) {}
//特权方法
this.getName = function() {};
this.getPrice = function() {};
this.setName = function() {};
this.setPrice = function() {};
//公有属性
this.id = newId;
//公有方法
this.copy = function() {};
bookNum++;
if (bookNum > 100)
throw new Error('error');
//构造器
this.setName(newName);
this.setPrice(price);
}
//构建原型
_book.prototype = {
//静态公有属性
isJSBook: false,
//静态公有方法
display: function() {}
};
//返回类
return _book;
}) ();
2.4 创建对象的安全模式
//图书类
var Book = function(title, time, type) {
this.title = title;
this.time = time;
this.type = type;
}
//实例化一本书
var book = Book('JavaScript', '2017', 'js');
console.log(book); //undefined
console.log(window.title); //JavaScript
console.log(window.time); //2017
console.log(window.type); //js
new关键字的作用可以看作是对当前对象的this不停的赋值,然而例子中没有用new,所以就会直接执行这个函数,而这个函数是在全局作用域中执行的,所以属性被添加到了window上面。又因为函数中没有return语句,所以得到了undefined。
为了避免这种情况,我们可以使用安全模式。
//图书安全类
var Book = function(title, time, type) {
//判断执行过程中this是否是当前这个对象(如果是说明是用new创建的)
if(this instanceof Book) {
this.title = title;
this.time = time;
this.type = type;
} else {
return new Book(title, time, type);
}
}
var book = Book('JavaScript', '2017', 'js');
console.log(book); //Book
console.log(book.title); //JavaScript
console.log(book.time); //2017
console.log(book.type); //js
console.log(window.title); //undefined
console.log(window.time); //undefined
console.log(window.type); //undefined