欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JavaScript面向对象、闭包和函数式编程

程序员文章站 2024-03-16 11:46:28
...

面向对象

  • 面向对象三大特点
    封装:将客观事物封装成抽象的类,并且类可以把数据和方法让可信的类或者对象进行操作,对不可信的类或者对象进行隐藏。
    继承:能使用现有的类的所有功能,并无须重新编写原来的这些类的基础上对这些功能进行扩展。
    多态:对一个实例的相同方法在不同的情形下有不同的表现形式。
    JavaScript是面向对象的编程语言,基于对象是不能实现继承和多态的,例如DOM,只能使用现有对象的方法和属性。
  • 类和对象
    JavaScript对象的三种基本构造方法
//第一种构造方法:new Object,构造基本对象直接添加属性
var obja = new Object();
obja.x = 1,obja.y = 2;
//第二种构造方法:对象常量
var objb = {x:1,y:2};
//第三种构造方法:定义类型
function Point(x,y){
  this.x = x;
  this.y = y;
}
var p = new Point(1,2);
  • 属性和方法
function List(){
        var elements = []; //私有属性,在对象外无法访问
        this.x = 10;//动态公有属性
        elements = Array.apply(elements, arguments);
        //公有属性,可以通过"."运算符或下标访问
        this.length={
            getValue:function(){
                return elements.length;
            },
            getLength:function(){
                return elements.length;
            }//利用构造有自定义getValue和getLength方法的对象模拟getter
        }
        this.toString = function(){
            return elements.toString();
        }
        this.add = function(){
            elements.push.apply(elements, arguments);
        }
    }
    List.prototype.y = 20;//原型属性
    List.z = 30;//类属性
    var mylist = new List(1,2,3);
    alert(mylist);//1,2,3
    alert(mylist.length);//3
    mylist.add(4,5,6);
    alert(mylist);//1,2,3,4,5,6
    alert(mylist.length);
    alert(mylist.elements);//undefined
    alert(mylist.x);//10
    mylist.x=100;
    mylist.y=200;//动态公有属性覆盖原型属性
    alert(mylist.x);//100
    alert(mylist.y);//200
    delete(mylist.x);
    delete(mylist.y);
    alert(mylist.x);//undefined,动态公有属性x被删除后不存在
    alert(mylist.y);//20,动态公有属性y被删除后还原为原型属性
    alert(mylist.z);//undefined,类属性无法通过对象访问
    alert(List.z);//30
  • prototype
    prototype 属性允许为对象添加属性和方法,是一种管理对象继承的机制。
    注意: Prototype 是全局属性,适用于所有的Javascript对象。
    对于所有的对象,都有proto属性,这个属性对应该对象的原型.
    对于函数对象,除了proto属性之外,还有prototype属性,当一个函数被用作构造函数来创建实例时,该函数的prototype属性值将被作为原型赋值给所有对象实例(也就是设置实例的proto属性)
    参考链接:https://www.cnblogs.com/gulei/p/6733707.html
var Person = function(){};
var myperson = new Person();
console.log(myperson.__proto__);//Person()(即构造器function Person的原型对象)
console.log(myperson.__proto__.__proto__);//Object()(即构造器function Object的原型对象)
console.log(myperson.__proto__.__proto__._proto__);//null
console.log(myperson.__proto__ === myperson.constructor.prototype)//true

JavaScript面向对象、闭包和函数式编程
JavaScript面向对象、闭包和函数式编程
- 继承与多态
实现继承的方法:构造继承法、原型继承法、实例继承法、拷贝继承法、混合继承法(常见的是构造和原型组合使用)
实现继承的主要作用是:
① 子类实例可以共享超类属性和方法。
② 子类可以覆盖和扩展超类属性和方法。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>构造继承法</title>
</head>
<script type="text/javascript">
    function dwn(s){
        document.write(s+"<br/>");
    }
    //定义Collection类
    function Collection(size){
        this.size = function(){return size};//公有方法,可以被继承
    }
    Collection.prototype.isEmpty = function(){ return this.size() == 0;}//静态方法,不能被继承
    function ArrayList(){
        var elements = []; //私有属性,在对象外无法访问
        elements = Array.apply(elements, arguments);
        //ArrarList类型继承Collection
        this.base = Collection;
        this.base.call(this,elements.length);

        this.toArray = function(){return elements;}
        this.add = function(){return elements.push.apply(elements, arguments);}
        this.size = function(){return elements.length;}
    }
    ArrayList.prototype.toString = function(){return this.toArray().toString();}
    //定义SortedList类型,继承ArrayList类型
    function SortedList(){
        this.base = ArrayList;
        this.base.apply(this,arguments);
        this.sort = function(){
            var arr = this.toArray();
            arr.sort.apply(arr,arguments);
        }
    }
    //构造一个ArrayList
    var list = new ArrayList(1,2,3);
    dwn(list);//1,2,3
    dwn(list.size());//3
    dwn(list.isEmpty);//undefined list没有继承此方法

    //构造一个SortedList
    var sortlist = new SortedList(4,2,3);
    sortlist.add(6,0);
    dwn(sortlist.toArray());//4,2,3,6,0
    sortlist.sort();
    dwn(sortlist.toArray());//0,2,3,4,6
    dwn(sortlist);//[object Object]
    dwn(sortlist.size());
    /*3,此处没有因为add添加元素而改变size,因为Collection中size()返回的是一个外部环境的参数,
    要维持size()正确性,ArrayList要重写size方法,即可返回5*/
</script>
<body>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>原型继承法</title>
</head>
<script type="text/javascript">
    function dwn(s){
        document.write(s+"<br/>");
    }
    //定义Point类型
    function Point(dimension){
        this.dimension = dimension;
    }
    function Point2D(x,y){
        this.x = x;
        this.y = y;
    }
    Point2D.prototype.distance = function(){return Math.sqrt(this.x*this.x+this.y*this.y);}
    Point2D.prototype = new Point(2);//Point2D 继承Point类型
    function Point3D(x,y,z){
        this.x = x;
        this.y = y;
        this.z = z;
    }
    Point3D.prototype = new Point(3);
    var p1 = new Point2D(0,0);
    var p2 = new Point3D(0,1,2);
    dwn(p1.dimension);//2
    dwn(p2.dimension);//3
    dwn(p1 instanceof Point2D);//true p1是一个Point2D
    dwn(p1 instanceof Point);//true p1是一个Point
    dwn(p2 instanceof Point);//true p2是一个Point
</script>
<body>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>实例继承法</title>
</head>
<script type="text/javascript">
    function dwn(s){
        document.write(s+"<br/>");
    }
    function myDate(){
        var date = new Date();
        date.printDate = function(){
            document.write("<p>"+date.toLocaleString()+"</p>");
        }
        return date;
    }
    var mydate = new myDate();
    dwn(mydate.toGMTString());//Wed, 13 Jun 2018 08:48:57 GMT
    mydate.printDate();//2018/6/13 下午4:48:57
</script>
<body>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>拷贝继承法</title>
</head>
<script type="text/javascript">
    Function.prototype.extends = function(obj){
        for(var each in obj){
            this.prototype[each] = obj[each];
            //对对象的属性进行一对一复制,但是会慢,容易引起问题,不推荐使用
        }
    }
    var Point2D = function(){......}
    Point2D.extends(new Point()){......}

</script>
<body>
</body>
</html>
  • 多态
    面向对象的继承和JavaScript的原型都可以用来实现多态
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>实现多态</title>
</head>
<script type="text/javascript">
    function dwn(s){
        document.write(s+"<br/>");
    }
    function Animal(){this.bite=function(){dwn("animal bite!");}}
    function Cat(){this.bite=function(){dwn("cat bite!");}}
    Cat.prototype =new Animal();
    function Dog(){this.bite=function(){dwn("dog bite!");}}
    Dog.prototype =new Animal();
    //定义一个多态方法AnimalBite
    function AnimalBite(animal){
        if(animal instanceof Animal){
            animal.bite();
        }
    }
    var cat = new Cat();
    var dog = new Dog();//Cat和Dog都是Animal
    AnimalBite(cat);//cat bite!
    AnimalBite(dog);//dog bite!
</script>
<body>
</body>
</html>
  • this迷宫
    一般,在对象方法调用过程中,“this”总是指代当前对象。js中不一定只有对象方法的上下文才有this,全局函数调用和其他几种不同的上下文中也有this代词,是基于上下文环境的(定义时上下文,运行时上下文,上下文是变化的)。this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象。
<script type="text/javascript">
    function Foo(){
        /*在函数内部,有两个特殊的对象:arguments 和 this。其中, arguments 的主要用途是保存函数参数,
        但这个对象还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 arguments 对象的函数。
        如果this引用的构造函数是arguments.callee引用的对象,说明是通过new操作符执行的构造函数。*/
        if(this.constructor == arguments.callee){
            alert("Object Created");
        }else if(this == window){
            alert("Normal call");
        }else{
            alert("called by "+ this.constructor);//否则是作为其他对象的方法来调用
        }
    }
    Foo();//Normal call 全局函数调用中,this值为window
    Foo.call(new Object());
    /*called by function Object() { [native code] } 作为一个Object对象的成员方法来调用
    使用call()和apply()改变this的指向,两者都是在特定的作用域中调用函数,
    等于设置函数体内this对象的值,以扩充函数赖以运行的作用域。
    obj1.apply(obj2[,arrArg]) 参数是数组
    obj1.call(obj2[,param1,param2,...])
    用obj2对象来代替obj1,调用obj1的方法。即将obj1应用到obj2上
    */
    new Foo();//Object Created 被new操作符调用,执行对象构造
</script>

闭包和函数式编程

  • 闭包
    闭包是能够读取其他函数内部变量的函数,闭包可以理解成“定义在一个函数内部的函数“。看下面这段代码:
    JavaScript面向对象、闭包和函数式编程
    这样在执行完var c=a()后,变量c实际上是指向了函数b,再执行c()后就会弹出一个窗口显示i的值(第一次为1)。这段代码其实就创建了一个闭包,当函数a的内部函数b被函数a外的一个变量引用的时候,就创建了一个闭包。
  • 闭包的作用
    如上代码,闭包的作用就是在a执行完并返回后,闭包使得Javascript的垃圾回收机制GC不会收回a所占用的资源,因为a的内部函数b的执行需要依赖a中的变量。
    闭包有两个用途,一个是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
  • 闭包内的微观世界
    如果要更加深入的了解闭包以及函数a和嵌套函数b的关系,我们需要引入另外几个概念:函数的执行环境(excution context)、活动对象(call object)、作用域(scope)、作用域链(scope chain)。以函数a从定义到执行的过程为例阐述这几个概念。借用下图说明:
    JavaScript面向对象、闭包和函数式编程
    (1)执行环境(execution context),也叫执行上下文,每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。执行环境是在执行代码的时候才存在。执行环境会随着函数的调用和返回,不断的重建和销毁。但变量对象在有变量引用(如闭包)的情况下,将留在内存中不被销毁。
    (2)作用域分为词法作用域和动态作用域。javascript使用词法作用域,就是定义在词法阶段的作用域,函数的作用域是在定义函数时候就已经确定,而不是在执行的时候确定。
    (3)当执行一段JavaScript代码(全局代码或函数)时,JavaScript引擎会创建为其创建一个作用域又称为执行上下文,在页面加载后首先创建一个全局作用域,然后每执行一个函数,会建立一个对应的作用域,从而形成一条作用域链。每个作用域都有一条对应的作用域链,链头是全局作用域,链尾是当前函数作用域。
    作用域链作用是用于解析标识符,当函数被创建时(不是执行),会将this、arguments、命名参数和该函数中的所有局部变量添加到该当前作用域中,当JavaScript需要查找变量X的时候(这个过程称为变量解析),它首先会从作用域链中的链尾也就是当前作用域进行查找是否有X属性,如果没有找到就顺着作用域链继续查找,直到查找到链头,也就是全局作用域链,仍未找到该变量的话,就认为这段代码的作用域链上不存在x变量,并抛出一个引用错误(ReferenceError)的异常。
    注:通过eval()函数、try-catch()、with语句可以对作用域进行动态修改。
    (4)函数执行时,传递的值按照它们出现在函数中的顺序被复制到运行期上下文的作用域链中。它们共同组成了一个新的对象,叫“活动对象(activation object)”,该对象包含了函数的所有局部变量、命名参数、参数集合以及this,然后此对象会被推入作用域链的前端,当运行期上下文被销毁,活动对象也随之销毁。
    如下代码:
function add(num1,num2){
    var sum = num1 + num2;
    return sum;
}

JavaScript面向对象、闭包和函数式编程