设计模式学习(一)--面向对象

1.面向对象和面向过程
小白想要验证用户名、邮箱、密码等

function checkName(){
//验证姓名
}
function checkEmail(){
//验证邮箱
}
function checkPassword(){
//验证密码
}

像这种实现方式,添加了很多全局变量且不利于复用,是面向过程的。
面向对象就是把需求抽象成一个对象,分析其属性与方法,这个对象称作类。
2.面向对象--封装
创建一个,通过this添加属性和方法,也就是构造函数的方式。

var Book=function(id,name,price){
    this.id=id;
    this.name=name;
    this.price=price;
}

也可容易通过在类的原型上添加属性和方法,以下两种方法不能混用

Book.prototype.display=function(){
//展示书
}

或者

Book.prototype={
display:function(){
//展示书
}
}

通过this添加属性和方法和在prototype中添加的区别:
构造函数:每用new创建一次对象实例,都对类的this上的属性复制,新创建的对象都会有自己的一套复制的方法,造成了资源的浪费。
原型扩展创建对象实例,都会通过prototype原型寻找,找到的方法都是同一个,都绑定在Book上面。

3.属性和方法封装--公有和私有

var Book=function(id,name,price){
    //私有变量
    var num=1;
    //私有方法
    function checkID(){};
    //公共属性
    this.id=id;
    //公共方法
    this.getName=function(){console.log(name)};
    //特权方法
    this.setPrice=function(){};
    //构造器
    this.setPrice(price)
}
//类静态公有属性
Book.isChinese=true;

Book.prototype={
    //公有属性
    isJSBook:false
}
var b=new Book(10,'设计模式',20);
console.log(b.num); //undefined
console.log(b.isChinese);//undefined
console.log(b.isJSBook);//false
b.getName();//设计模式

通过以上例子,私有变量和方法外部是访问不到的。通过this创建的属性和方法是公有的。通过this创建的方法不但可以访问公有的还可以访问私有的变量和方法,权力比较大,又称作特权方法。在创建对象时调用的特权方法看作为类的构造器。

4.闭包
将闭包作为创建对象的构造函数,这样看起来更像是一个整体

var Book = (function() {
    //静态私有变量
    var bookNum = 0;
    //静态私有方法
    function checkBook(name) {};
    //创建类
    function _book(newId, newName, newPrice) {
        //私有变量
        var name, price;
        //私有方法
        function checkId(newId){};
        //特权方法
        this.getName = function(){};
        this.getPrice = function(){};
        this.setName = function(name){console.log(name)};
        //公有变量
        this.id = newId;
        //公有方法
        this.copy = function(){};
        //构造器
        this.setName(newName);
    }
    //构建原型
    _book.prototype = {
        //静态共有属性
        isJsBook: false,
        //静态公有方法
        display: function(){}
    }
    //
    return _book;
})()

5.创建对象的安全模式
避免忘记用new创建对象发生的问题

var Book = function(newId, newName, newPrice) {
    this.id = newId;
    this.name = newName;
    this.price = newPrice;
}
var newBook = Book(123,"dfg",444); 
console.log(newBook); //undefined

window.id //123

如果忘记用new来实例化,就相当于执行了这个函数,当前的this指向的是window。
解决方法

var Book = function(newId, newName, newPrice) {
    //判断this指向
    if(this instanceof Book){
        this.id = newId;
        this.name = newName;
        this.price = newPrice;
    }else{
        return new Book(newId, newName, newPrice);
    }
}

6.继承
1.类式继承

//声明父类
function ParentClass() {
    this.parentValue = true;
        this.obj = ["123","234"];
}
//父类添加公有方法
ParentClass.prototype.getParentValue = function() {
    return this.parentValue;
}
//声明子类
function ChildClass() {
    this.childValue = false;
}
//继承父类,子类的原型被赋予父类的实例
ChildClass.prototype = new ParentClass();
//ChildClass = new ParentClass(); //这样肯定不对,人家会问ChildClass是个什么东西?
var instance = new ChildClass();
console.log(instance.getParentValue()); //true

但是为什么要赋值给子类的原型上?
因为子类是不能直接访问父类的属性和方法的,所以需要通过prototype来访问。
用来检测是否属于一个类instanceof

console.log(instance instanceof ChildClass);//true
console.log(instance instanceof ParentClass);//true
console.log(ChildClass instanceof ParentClass);//false
console.log(ChildClass.prototype instanceof ParentClass);//true

缺点:所有的子实例共用一个父类引用类型,如果一个子类实例更改了父类构造函数中继承的公有属性会影响其他子类;在创建父类的时候,不能向父类传递参数,在实例化父类的时候也不能对父类构造函数内的属性初始化。

引用类型和基本类型
基本类型:Underfined ,Null, Boolean,Number,String ,在内存中占有固定大小的空间,值保存在栈空间中,按值访问。
引用类型:Object,Array,Date,Function ,值的大小不固定,栈内存中存放地址指向堆内存中的对象,是按引用访问的。当查询引用类型的变量时,先从栈中读取内存地址,然后再通过地址找到堆中的值。对于这种,我们把它叫做按引用访问。类似于指针但是跟指针不是同一个东西。

//再实例化一个子类
var instance1 = new ChildClass();
instance.parentValue  = false;
console.log(instance1.parentValue);//true,因为改变的并不是引用类型的属性
instance.obj.push("345");
console.log(instance1.parentValue);// ["123", "234", "345"]

2.构造函数继承
用call()的方法改变this指向,子类实例之间不会相互影响,但是违背代码复用原则

//声明父类
function ParentClass(id) {
    this.id = id;
    this.obj = ["123","234"];
}
//声明子类
function ChildClass(id) {
    ParentClass.call(this,id);
}
var instance = new ChildClass(1);
var instance1 = new ChildClass(2);
instance.obj.push("345");
console.log(instance.obj); //["123", "234", "345"]
console.log(instance1.obj); //["123", "234"]
console.log(instance.id); //1
console.log(instance1.id); //2

3.组合继承
将类式继承和构造函数继承结合起来,但是调用了两次父类构造函数。

//声明父类
function ParentClass(id) {
    this.id = id;
    this.obj = ["123","234"];
}
//声明子类
function ChildClass(id) {
    ParentClass.call(this,id);
}
ChildClass.prototype = new ParentClass()
var instance = new ChildClass(1);
var instance1 = new ChildClass(2);
instance.obj.push("345");
console.log(instance.obj);//["123", "234", "345"]
console.log(instance1.obj);//["123", "234"]
console.log(instance.id);//1
console.log(instance1.id);//2

4.原型式继承

function inheritObj(obj) {
    //过渡函数,相当于子类
    function F(){}
    //过渡函数原型继承父类对象
    F.prototype = obj ;
    //返回过渡函数实例
    return new F();
}

var obj = {"name":"zhangsan", "book":["css","js"]}
var Book1 = inheritObj(obj);
Book1.name = "Derek";
Book1.book.push("html");
var Book2 = inheritObj(obj); 
console.log(Book1.name);//Derek
console.log(Book1.book);//["css", "js", "html"]
console.log(Book2.name);//zhangsan
console.log(Book2.book);//["css", "js", "html"]

是类式继承的封装,有同样的问题,值类型的属性被复制,引用类型的属性被共用
5.寄生继承
二次封装原型继承
6.寄生组合继承

参考:《Javascript设计模式》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容