(一)设计模式-周边基础

原型模式

JavaScript通过原型委托的方式实现对象与对象之间的继承。

  • 静态类型语言 在编译时已经确定变量的类型,因此在编译时就能发现类型不匹配的错误。
  • 动态类型语言的变量类型要到程序运行的时候,待变量被赋予了某个值之后,才会具有某种类型。

(1)多态

多态的思想是将“不变的事物”与“可能改变的事物”分离开来,把不变的部分隔离开来,把可变的部分封装起来,使得程序是可扩展的,

   var makeSound = function(animal){
    if(animal instanceof Duck){
        console.log('111');
    }else if(animal instanceof Chicken){
        console.log('222');
    }
};
var Duck = function(){};
var Chicken = function(){};

makeSound(new Duck());
makeSound(new Chicken());

修改后:

var makeSound = function(animal){
    animal.sound();
};
var Duck = function(){};
Duck.prototype.sound = function(){
    console.log('111');
}
var Chicken = function(){};
Chicken.prototype.sound = function(){
    console.log('222');
}
makeSound(new Duck());
makeSound(new Chicken());

多态最根本的好处在于,你不必再向对象询问“你是什么类型”而后根据得到的答案调用对象的某个行为——你只管调用该行为就是了,其他的一切多态机制都会为你安排妥当。即通过把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句。

(2)封装

封装的目的是将信息隐藏,在js中通过依赖变量的作用域实现封装特性,

1)封装数据,除了使用let之外,可以通过函数创建作用域。

var my = (function(){
    var _name = 'chao';
    return {
        getName:function(){
            return _name;
        }
    }
})();
console.log( my.getName()); // chao
console.log( my._name); // undefined

2)封装实现

从封装实现细节来讲,封装使得对象内部的变化对其他对象而言是不可见的,其他对象或者用户不关心其内部的实现,对象之间只通过暴露API接口来通信,当修改一个对象的时候可以随意修改其内部实现,对外的接口没有变化,不会影响其他功能。

3)封装变化

考虑你的设计中哪些地方可能变化,这种方式与关注会导致重新设计的原因相反。它不是考虑什么时候会迫使你的设计改变,而是考虑你怎样才能够在不重新设计的情况下进行改变。这里的关键在于封装发生变化的概念,这是许多设计模式的主题。

(3)js中的原型继承

1)所有的数据都是对象

js包含两套类型机制:基本类型和对象类型,基本类型包括:undefined、number、boolean、string、function、object。js中的跟对象是Object.prototype对象,Object.prototype是一个空对象,js中的每个对象都是从Object.prototype对象克隆而来的,即为对象的原型,

2)要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它。

在js中不需要关心克隆的细节,只需要显式的调用var obja = new Object()或者var obj2 = {}

function Person(name){
    this.name = name;
};
Person.prototype.getName = function(){
    return this.name;
};
var a = new Person('sven');

console.log(a.name);  // sven
console.log(a.getName()); // sven
console.log(Object.getPrototypeOf(a) === Person.prototype); // true

当使用new运算法调用函数时i,此时函数是一个构造器。

3)对象会记住它的原型

对象把请求委托给它的构造器的原型,JavaScript给对象提供了一个名为__proto__的隐藏属性,某个对象的__proto__属性默认会指向它的构造器的原型对象,即{Constructor}.prototype。

var a = new Object();
console.log(a.__proto__ === Object.prototype); // true  

4)如果对象无法响应某个请求,它会把这个请求委托给它的构造器的原型。

在JavaScript中,每个对象都是从Object.prototype对象克隆而来的,

原型继承方式:

var obj = { name:'ss'};
var A = function(){};
A.prototype = obj;
var a = new A();
console.log( a.name ); //ss
  • 首先,尝试遍历对象a中的所有属性,然后没有找到name这个属性。
  • 查找name属性的这个请求被委托给对象a的构造器的原型,它被a.__proto__记录着并且指向A.prototype,而A.prototype被设置为对象obj
  • 在对象obj中找到name属性,并返回它的值。
var A = function(){};
A.prototype = { name: '11'};

var B = function(){};
B.prototype = new A();

var b = new B();
console.log(b.name); // 11
  • 首先,尝试遍历对象b中的所有属性,没有找到name这个属性。
  • 查找name属性的请求被委托给对象b的构造器的原型,它被b.__proto__记录着并且指向B.protoype,,而B.protoype被设置为一个通过new A()创建出来的对象,
  • 在该对象中依然没有找到name属性,于是请求被继续委托给这个对象构造器的原型A.prototype.
  • A.prototype.中找到了name属性,并返回它的值。

this、call和apply

(1)this

js中的this总是指向一个对象,具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而Fri函数被声明时的环境。

this的指向大致可以分为以下四种:

  • 作为对象的方法调用
  • 作为普通函数调用
  • 构造器调用
  • Function.prototype.call或者Function.prototype.apply调用。

1)作为对象的方法调用,this指向该对象

var obj = {
    a:1,
    getA: function(){
        console.log(this === obj); // true
        console.log(this.a); //1
    }
}
obj.getA();

2)作为普通函数调用

当函数不作为对象的属性被调用的时候,此时的this指向全局对象。

window.name = "aaa";
var getName = function(){
    return this.name;
}
console.log(getName()); // aaa

3)构造器调用
当使用new运算符调用函数时该函数总是返回一个对象,通常情况下,构造器里的this执行返回的这个对象。

var myClass = function(){
    this.name = '222';
};
var obj = new myClass();
console.log(obj.name); // 222

但如果构造器显式返回了一个object类型的对象,此次运算结果最终会返回这个对象,不是之前的this’。

var myClass = function(){
    this.name = '222';
    return {
        name:'111';
    }
};

var obj = new myClass();
console.log(obj.name); //111

4)Function.prototype.call或者Function.prototype.apply调用。可以动态的改变传入函数的this

var obj1 = {
    name:'123',
    getName: function(){
        return this.name;
    }
};
var obj2 = {
    name:'456'
};
console.log(obj1.getName()); // 123
console.log(obj1.getName.call(obj2)); // 456

(2)call和apply

1)区别

  • apply接受两个参数,第一个参数指定了函数体内this对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组或者类数组,

var func = function(a,b,c){
    console.log([a,b,c]);
}
func.apply(null,[1,2,3]);
  • call传入的参数数量不固定,第一个参数是代表函数体内额this指向,第二个参数开始往后,每个参数被依次传入函数。

var func = function(a,b,c){
    console.log([a,b,c]);
}
func.call(null, 1 ,2, 3 )

call是包装在apply上的一颗语法糖,如果明确知道函数接受多少个参数,并且想一目了然表达形参和实参的对应关系,可以使用call传递参数、

2)用途

  • 改变this的指向
  • Function.prototype.bind 用来指定函数内部的this指向,
  • 借用其他对象的方法

闭包和高阶函数

(1)闭包

1)变量作用域:是指变量的有效范围

函数可以用来创建函数作用域,当在函数中搜索一个变量的时候,如果函数内并没有声明这个变量,那么此次搜索的过程会随着代码执行环境创建的作用域链往外层逐层搜索,一直搜索到全局对象为止,变量的搜索是从内到外。

2)变量的生存周期

对于在函数内声明的局部变量来说,退出函数时,这些变量会随着函数调用的结束被销毁,


多态:同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果,换句话说给不同的对象发送同一个消息的时候,这些对象可以根据这个消息分别给出不同的反馈。
封装:

原型模式

(1)Object.create();
对象的_proto_会属性会默认指向他的构造器的原型对象。

this

(1)函数作为对象的方法被调用,this指向该对象。
(2)普通函数调用,this指向全局对象。
(3)构造器调用:当用new运算符调用函数时,该函数会返回一个对象,通常情况下,构造器中的this会指向返回的对象。

var myClass = function(){
    this.name = "chao";
}
var obj = new myClass();
console.log(obj.name); //chao

如果构造器显示返回了一个object类型的对象,那么会返回该对象。

var myClass = function(){
    this.name = "chao";
    return {
        name:"llal"
    }
}
var obj = new myClass();
console.log(obj.name); //llal

如果构造器不显示返回任何数据,或者返回一个非对象类型的数据,则不会有上述现象。
(4)function.prototype.call或者function.prototype.apply调用
可以动态的改变this的指向。

var obj= {
    name :"se",
    getName:function(){
        return this.name;
    }
}
var obj2 = {
    name:"ssss"
}
console.log(obj.getName()); // se
console.log(obj.getName.call(obj2));  //ssss

你可能感兴趣的:(设计模式,js设计模式)