上一篇JS的多种写法介绍中,也涉及到了JS面向对象的写法
传送门:JavaScript设计模式(1)-灵活的JS写法
JS的两种编程方式:
1,面向过程JS:
按照传统流程编写若干函数解决业务需求
2,面向对象JS:
将需求抽象成对象,分析其特征(属性)和功能(方法)
面向过程式的JS编程会有诸多弊端,例如:会在全局作用域添加若干全局变量,函数可拓展性较差,
不利于团队开发和代码复用,因此建议使用面向对象JS开发
面向对象语言(如Java)具有三大特性:继承,封装,多态
但是JavaScript属于解释性弱类型语言,只能通过各种手段来模拟面向对象特征
这一篇主要介绍面向对象JS的实现原理以及封装特性的实现
JS设计模式的基础就是面向对象JS,在介绍JS设计模式之前,有必要先了解面向对象JS
在后续的介绍中,我们可以逐步认识和了解面向对象编程的强大威力
声明一个函数保存到变量中,此函数即为一个类
类的变量名命名方式:首字母大写
var User = function(id, username, age){
this.id = id;
this.username = username;
this.age = age;
}
如上,声明了一个User类,包含id,username,age三个属性
this为函数内部自带的一个变量,指向当前对象,通过this变量对类添加属性或方法
还可以通过在类的原型上添加属性和方法
// 为原型添加单个属性或方法
User.prototype.getName = function(){
}
或
// 替换原型对象
User.prototype = {
getName : function(){
}
}
如上,将需要的属性和方法封装到了类中
类不可以直接使用,和面向对象语言一样,需要先用new关键字进行实例化(创建实例)
var user = new User(10, 'Brave', 30);
user.username; // Brave
this为函数内部自带的一个变量,指向当前对象,通过this变量对类添加属性或方法
JS是基于原型prototype的语言,每创建一个对象,都有一个原型prototype指向其继承的属性和方法
通过this定义的属性或方法:
是当前对象自身拥有的,每通过类new一个对象,this指向的属性和方法都会被创建
通过prototype继承的属性或方法:
并不是对象自身的,是每个对象通过prototype逐级查找访问到的,
创建新对象时这些属性和方法不会再次创建
当创建一个函数或对象时,都会为其创建一个原型对象prototype,
prototype对象中也会像创建this一样,为prototype创建一个constructor属性
constructor属性指向拥有整个原型对象的函数或对象
按照User类的例子,constructor属性指向的就是User类对象
var User = function(id, username, age){
this.id = id;
this.username = username;
this.age = age;
}
类不能直接使用,需要先用new关键字进行实例化创建实例
var user = new User(10, 'Brave', 30);
但是,如果使用者不知道这是一个类,或是忘记使用new关键字
var user = User(10, 'Brave', 30);
console.log(user); // undefined
console.log(window.id); // 10
console.log(window.username); // Brave
console.log(window.age); // 30
由于没有使用new关键字创建对象,导致user=undefined,原因如下:
没有使用new,导致函数在全局作用于中被直接执行了,
this指向的当前对象即为全局作用于(window),属性被添加到了window成为全局变量
由于定义的类没有return语句,所以User类不会告诉user变量函数的执行结果,user变量值为undefined
这里引入一种类的安全模式写法,解决没有使用new关键字导致的创建实例失败问题
var User = function(id, username, age){
if(this instanceof User){
this.id = id;
this.username = username;
this.age = age;
}else{
return new User(id, username, age);
}
}
// 不使用new关键字进行类实例化
var user = User(10, 'Brave', 30);
console.log(user); // User
console.log(user.id); // 10
console.log(user.username); // Brave
console.log(user.age); // 30
console.log(window.id); // undefined
console.log(window.username); // undefined
console.log(window.age); // undefined
类的安全模式,通过判断执行过程中this是否为当前对象,
从而判断类是否是通过new关键字进行实例化,
如果没有使用new关键字,内部使用new关键字对该类进行实例化
这样一来及时忘记使用new关键字创建对象也不会有问题了
面向对象语言可以控制属性和方法的对外暴露的隐藏
例如:私有属性,私有方法,公有属性,公有方法,保护方法等
在JS中可以使用一些技巧来对这些属性和方法的封装进行模拟以实现面向对象特征
由于JavaScript的函数级作用域,声明在函数内部的变量以及方法在外界不能被访问
利用JS的这个特性可以创建类的私有变量和私有方法
通过类创建对象时,类在通过new关键字实例化对象时执行了一次,
函数内部通过this声明的属性和方法被复制到新创建的对象上,
在每个对象自身都会拥有一套并且可被外部访问
通过this创建的属性,可以看作对象的公有属性和公有方法
通过this创建的方法,既可访问公有属性和公有方法,也能访问类或对象的私有属性和私有方法
由于这些方法的权限较大,又称作特权方法
在对象创建时,可以使用特权方法对实例的属性进行初始化,此时的特权方法可以看作是类的构造器
var User = function(id, username, age){
// 私有属性
var count = 1;
// 私有方法
function jump(){};
// 特权方法
this.setUsername = function(){};
this.getUsername = function(){};
this.setAge = function(){};
this.getAge = function(){};
// 对象公有属性
this.id = id;
// 对象公有方法
this.run = function(){};
// 构造器
this.setUsername(name);
this.setAge(age);
}
类的外部通过点语法定义属性和方法
new对象时,类外部通过点语法添加的属性和方法没有被执行到,所以新创建的对象中无法获取到
但可以通过类来获取到,所以这部分可以作为类的静态公有属性和静态公有方法
// 类的静态公有属性
User.isChinese = true;
// 类的静态公有方法
User.resetCount = function(){}
类通过prototype创建的属性和方法
可以通过this访问,所以将prototype对象中的属性和方法看作公有属性和公有方法
User.prototype = {
// 公有属性
isMan : true,
// 公有方法
show : function(){}
}
通过new关键字创建的对象实质是对新对象this的不断赋值,
并将prototype指向类的prototype所指向的对象,
类的构造函数外通过点语法定义的属性方法不会添加到新创建的对象上
类的prototype原型上定义的属性在新对象中可直接使用,
var user = User(10, 'Brave', 30);
console.log(user.count); // undefined 私有属性外部不能直接访问
console.log(user.isMan); // true 对象user和类User的prototype指向同一引用
console.log(user.id); // 10 公有属性
console.log(user.isChinese); // undefined User类通过.定义的静态私有属性
console.log(User.isChinese); // true
JS中比较难掌握的一个知识点就是闭包
一句话理解闭包:函数在本身词法作用域以外的地方执行,就会产生闭包
闭包有权访问另外一个函数作用域中的变量,即在一个函数中创建另外一个函数
因此我们可以使用这个闭包作为类的构造函数,它可以访问到类函数作用域中的变量
var User = (function(id, username, age){
// 静态私有变量
var count = 1;
// 静态私有方法
function jump(){};
// 使用闭包创建类
function _user(id, username, age){
// 私有变量
var name, age;
// 私有方法
function do(){};
// 特权方法
this.setUsername = function(){};
this.getUsername = function(){};
this.setAge = function(){};
this.getAge = function(){};
// 公有属性
this.id = id;
// 公有方法
this.run = function(){};
count++
if(count > 100){
throw new Error("超出100限制")
}
// 构造器
this.setUsername(name);
this.setAge(age);
}
// 创建原型
_user.prototype = {
// 静态公有属性
isMan : true,
// 静态公有方法
show : function(){}
}
// 返回完整的类
return _user;
})();
此时,count变量为静态私有变量,jump方法为静态私有方法
可供多个实例共享,外部不能直接对其进行访问
在闭包内部的name为自身的私有变量,do为自身的私有方法
通过this.创建的变量和方法为公有属性和公有方法,每个实例都回单独创建一套
随后为类创建原型,类的多个实例引用同一原型,
原型中的变量和方法为静态公有属性和方法
这样在闭包内部实现了一个完整的类,最后将其返回