121 项目 025 笔记向 js 笔记

原创
2017/03/17 18:24
阅读数 62

函数

定义函数的三种方式

function 语句形式

    window.onload = function () {
        sayHello();

    };

    function sayHello(){
        console.log('Hello World');
    }

函数的直接量

    window.onload = function () {
        sayHello();

    };

    var sayHello = function () {
        console.log('Hello World');
    }

Function 函数构造函数

    window.onload = function () {
        console.log(sayHello(1,2));

    };

    var sayHello = new Function("a","b","return a + b ");

区别

输入图片说明

推荐使用 function语句或函数直接量形式

arguments 对象

arguments是函数的内部的一个数组对象,保存了函数的参数列表,arguments还有一个属性叫做:callee,该属性是一个指针,指向拥有这个arguments对象的函数

一个使用arguments的经典例子:

    window.onload = function () {
        console.log(factorial(5));

    };



    var factorial = function ( num ) {
        if( num <= 1 ){
            return 1;
        }else{
            return num * arguments.callee(num-1);
        }
    }

this 对象

this对象是在运行时基于函数的执行环境绑定的.在全局函数中this指向window,当函数被作为某个对象的方法调用时,this指向那个对象。

也就是说this总是指向调用者

length

函数的length属性表示函数希望接收的命名参数的个数

call 和 apply

每个函数都包含两个非继承而来的方法:callapply

calapply的第一个参数都是this,指向运行函数的作用域,apply的第二个参数可以是arguments,也可以是数组,call的其它参数必须完整的列出.

简单用法

  • 传递参数
    window.onload = function () {
        console.log(fTwo(1,2));

    };

    var fOen = function (a,b) {
        return a + b;
    }

    var fTwo = function (a,b) {
        return fOen.call(this,a,b);
    }

高级用法

扩充函数赖以运行的作用域

    window.authorName = 'laolang';

    var obj = {authorName:'xiaodaima'};

    function showAuthorName( ){
        console.log(this.authorName);
    }

    showAuthorName.call(window);
    showAuthorName.call(obj);

函数作参数

    var sum = function (a,b) {
        return a + b;
    }

    var minus = function (a,b) {
        return a - b;
    }

    var doSomething = function (calFunName,a,b) {
        return calFunName(a,b);
    }

    console.log(doSomething(sum,1,2));
    console.log(doSomething(minus,1,2));

执行环境和作用域链

执行环境(execution context)是javascript中最为重要的一个概念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每一个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。虽然我们的代码无法访问这个对象,但是解析器在处理数据时会在后台执行它。

全局执行环境是最外围的一个执行环境。根据ECMScript实现所在的宿主环境不同,表示执行环境的对象也不一样。 每一个函数都有自己的执行环境。当执行流进一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返还给之前的执行环境。当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。

var color1 = "blue";
function changeColor(){
	var color2 = "red";
	function swapColor(){
		var color3 = color2; 
		color2 = color1;
		color1 = color3;
		//这里可以访问:color1、2、3
	}
	//这里可以访问color1、color2、但不能访问color3
	swapColor();
}
//这里只能访问color1
changeColor();

闭包

三种概念

  1. 闭包与函数有着紧密的关系,它是函数的代码在运行过程中的一个动态环境,是一个运行期的、动态的概念。
  1. 所谓闭包,是指词法表示包括不必计算的变量的函数。也就是说,该函数能够使用函数外定义的变量.。
  1. 在程序语言中,所谓闭包,是指语法域位于某个特定的区域,具有持续参照(读写)位于该区域内自身范围之外的执行域上的非持久型变量值能力的段落。这些外部执行域的非持久型变量神奇地保留他们在闭包最初定义(或创建)时的值。

两段JS代码

    var name = "xiaodaima";
    var obj={
        name : "laolang",
        getName : function () {
            return function () {
                return this.name;
            }
        }
    }

    console.log(obj.getName()());

这段代码返回的是

xiaodaima

这是因为obj.getName()返回的函数是在window域中,所以返回值是外部定义的全局变量xiaodaima

    var name = "xiaodaima";
    var obj={
        name : "laolang",
        getName : function () {
            var o = this;
            return function () {
                return o.name;
            }
        }
    }

    console.log(obj.getName()());

这段代码返回的是

laolang

而这段代码因为将obj的引用o想用到的返回的函数中,所以返回的函数作用域在obj对象中,所以返回的是函数内部的局部变量laolang

闭包小例子

闭包特性:一个函数可以访问另一个函数作用域中的变量,直到一个保护变量的作用


对象

字面量形式

var person = new Object();
    person.name = "laolang";
    person.age= 23;
    person.job = "Software Engineer";
    person.sayName = function () {
        console.log(this.name);
    }

    var person ={
        name : 'laolang',
        age : 23,
        job : 'Software Engineer',
        sayName : function () {
            console.log(this.name);
        }
    }

这种形式有个明显的缺点,:使用同一接口创建很多对象时,会产生大量的重复代码,于是就有了各种创建对象的替代方法.

工厂模式

    function createPerson(name,age,job){
        var o = new Object();
        o.name = name;
        o.age =  age;
        o.job = job;
        o.sayName = function () {
            console.log(this.name);
        };

        return o;
    }

    var p1 = createPerson('laolang',23,'Software Engineer');
    p1.sayName();
    var p2 = createPerson('xiaodaima',32,'Doctor');
    p2.sayName();

这种方式虽然解决了创建多个相似对象的问题,但是没有解决对象识别的问题,即怎样知道一个对象的类型

构造函数模式

    function Person(name,age,job){
        this.name = name;
        this.age =  age;
        this.job = job;
        this.sayName = function () {
            console.log(this.name);
        };
    }

    var p1 = new Person('laolang',23,'Software Engineer');
    p1.sayName();
    var p2 = new Person('xiaodaima',32,'Doctor');
    p2.sayName();

    console.log(p1.constructor == Person ); // true
    console.log(p2.constructor == Person ); // true

    console.log(p1 instanceof Person); // true
    console.log(p1 instanceof Object); // true
    console.log(p2 instanceof Person); // true
    console.log(p2 instanceof Object); // true

使用这种方式创建对象必须使用new操作符,会经历以下4个步骤:

  1. 创建一个对象

  2. 将构造函数的作用域赋给新对象【因此this就指向了这个新对象】

  3. 执行构造函数中的代码【为这个新对象添加属性】

  4. 返回对象

实际上任何函数都可以用new操作符来调用,任何函数如果不用new操作符来调用,那就是普通函数,用了new操作符就是构造函数.

而使用这种方式在不使用new操作符的时候,this就是指向了window对象,这是因为不用new的时候调用这个函数的就是window对象,如下方式就会使得对象o有了Person所有的属性和方法

    var o = new Object();
    Person.call(o,'laolang',23,'Software Engineer');
    o.sayName();
    console.log(o);

输入图片说明

这是因为此时函数是在对象o的作用域中调用的。

但是由于JS中的函数都是对象,所以每次实例化的时候,每个类的方法都重新创建了一次,也就是说每个实例的同名方法都是不同的,这会导致不同的作用域链和标识符解析

console.log(p1.sayName == p2.sayName ); // false

为了解决这个问题可以使用将类的方法的定义移到外面

    function Person(name,age,job){
        this.name = name;
        this.age =  age;
        this.job = job;
        this.sayName = sayName;
    }

    function sayName(){
        console.log(this.name);
    }

    var p1 = new Person('laolang',23,'Software Engineer');
    p1.sayName();
    var p2 = new Person('xiaodaima',32,'Doctor');
    p2.sayName();

    console.log(p1.sayName == p2.sayName ); // true

但是这种方式几乎没有封装性可言,而且如果需要定义很多方法的话,就要定义很多个全局函数

原型模式

    function Person(){}

    Person.prototype.name = 'laolang';
    Person.prototype.age = 23;
    Person.prototype.job = 'Software Engineer';
    Person.prototype.sayName = function () {
        console.log(this.name);
    }

    var p1 = new Person();
    p1.sayName();
    console.log(p1);

    var p2 = new Person();
    p2.sayName();
    console.log(p2);

    console.log(p1.sayName == p2.sayName ); // true
    console.log(p1.name == p2.name ); // true

这种方式虽然解决了实例化类中的方法是相同引用,但是也造成了一个问题,那就是如果使用构造函数之后,不修改实例的属性值 ,那么每个实例化的对象的所有属性也是相同的引用 。原因就是每个函数的prototype指向的是函数的原型对象。

关于原型对象我没有看明白,书上一个简洁明了的解释如下 :

当我们调用p1.sayName()时,解析器会问:实例p1中有sayName属性吗?答:没有。再问:p1的原型中有sayName属性吗?答:有。于是它就读取那个保存在原型对象中的函数。

于是这也就造成了所有实例化的类属性和方法都是相同的引用。而当我们对实例的属性赋值时,实例就具有了属性,这就把原型中的属性给屏蔽了,其实也就是实例中的属性的引用不再指向原型了,但是可以用delete将实例中的属性删除,这样又可以访问原型中的属性了。

    function Person(){}

    Person.prototype.name = 'laolang';
    Person.prototype.age = 23;
    Person.prototype.job = 'Software Engineer';
    Person.prototype.sayName = function () {
        console.log(this.name);
    }

    var p1 = new Person();
    var p2 = new Person();
    p1.name = 'xiaodaima';
    console.log(p1.sayName == p2.sayName ); // true
    console.log(p1.name == p2.name ); // false
    console.log(p1.name == p2.name ); // true

而这也是原型模式最大的缺点,即所有属性都是被很多实例共享的

组合使用构造函数模式和原型模式

    function Person(name,age,job){
        this.name = name;
        this.age = age;
        this.job = job;
    }

    Person.prototype = {
        constructor : Person,
        sayName : function (){
            console.log(this.name);
        }
    }

    var p1 = new Person('laolang',23,'Software Engineer');
    var p2 = new Person('xiaodaima',34,'Doctor');
    console.log(p1.sayName == p2.sayName ); // true
    console.log(p1.name == p2.name ); // fasle

这样把属性定义在构造函数中,把方法定义在构造函数的原型中,就可以解决问题,这种方式也是使用最广泛的方式

动态原型模式

    function Person(name,age,job){
        this.name = name;
        this.age = age;
        this.job = job;

        if ( typeof  this.sayName != "function" ){
            Person.prototype.sayName = function () {
                console.log(this.name);
            }
        }
    }

    var p1 = new Person('laolang',23,'Software Engineer');
    var p2 = new Person('xiaodaima',34,'Doctor');
    console.log(p1.sayName == p2.sayName ); // true
    console.log(p1.name == p2.name ); // fasle

使用这种方式时不能使用对象字面量生写原型,会切断现有实例与新原型之间的联系

寄生构造函数模式

稳妥构造函数模式

继承

许多OO语言都支持两种继承:接口继承和实现继承。接口继承只继承方法签名,而 实现继承则继承实际的方法。由于JS函数没有签名,所以JS只支持实现继承,而其实现继承主要是依靠原型链来实现的

原型链

其基本思想是利用原型主一个引用类型继承另一个引用类型的属性和方法.

    function Persion(){
        this.name = 'laolang';
        this.age = 23;
    }

    Persion.prototype.sayHello = function () {
        console.log(this.name);
    };

    var p = new Persion();
    p.sayHello();
    console.log(p);

    function Student(){
        this.name = 'stu';
    }
    
    Student.prototype = new Persion();

    Student.prototype.sayInfo = function () {
        console.log(this.name);
    };



    var stu = new Student();
    stu.sayInfo();
    console.log(stu);

输入图片说明

使用这种方式实现继承需要注意:

  • 子类定义方法需要在Student.prototype = new Persion();语句之后

  • 不能使用字面量的形式添加新方法

    Student.prototype = {
        sayInfo : function () {
            console.log(this.name);
        }
    };

这样会报一个找不到方法的错误,原因是把子类的原型指向父类实例之后,紧接着又把子类的原型替换成一个字面量

原型链继承的问题:

  1. 所有的子类实例都共享了所有的引用类型属性[比如数组]

  2. 创建子类实例时,不能向父类的构造函数中传递参数

借用构造函数

基本思想是在子类构造函数的内部调用父类构造函数

    function Persion(name){
        this.name = name;
    }

    function Student(){
        Persion.call(this,'stu');
        this.age = 2;
    }

    var stu = new Student();
    console.log(stu);

输入图片说明

借用构造函数的问题:

方法都在构造函数中定义,因此函数复用无从谈起,且父类的方法对子类也是不可见的

组合继承

基本思想是使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承.这样在原型上定义方法实现了函数利用,又能保证每个实例都有自己的属性

    function Persion ( name ){
        this.name = name;
        this.colors = ['red','blue','green'];
    }

    Persion.prototype.sayName = function () {
        console.log(this.name);
    };

    function Student(name,age){
        // 继承属性
        Persion.call(this,name);

        this.age = age;
    }

    // 继承方法
    Student.prototype = new Persion();

    Student.prototype.sayAge = function () {
        console.log(this.age);
    };

    var stu1 = new Student('stu1',21);
    stu1.colors.push('black');
    stu1.sayAge();
    stu1.sayName();
    console.log(stu1);

    var stu2 = new Student('stu2',22);
    stu1.sayAge();
    stu1.sayName();
    console.log(stu2);

输入图片说明

这样每个类可以有自己的属性而又共享方法

组合继承也是JS中最常用的继承模式,且instanceofisPrototypeOf()也能用于识别基于组合继承创建的对象

原型式继承

寄生式继承

寄生组合式继承

接口

建立JS接口的三种方式:

  1. 注释描述接口

  2. 属性检测接口

  3. 鸭式辩型接口

接口的利弊:

  • 对于一些中小型程序来说 使用接口很显然是不明智的,对项目来说接口的好处也不明显,只是徒增其复杂度而已。

  • 对于接口的好处,那么显而易见 首先促进代码的重用,对于开发来讲,还可以告诉程序员那些类都使用了什么方法,如果你事先知道接口那么就减少了你在编码的时候对类与类之间冲突,实现解耦。对于测试和调试也会变得轻松,用于javascript的弱类型语言,类型不匹配经常出现,那么使用接口,这一点会变得容易一些。

注释描述接口

    /**
     * interface Composite{
     *  function add(obj);
     *  function remove(obj);
     *  function update(obj);
     * }
     */

    // CompositeImpl implements Composite
    function CompositeImpl(){

    }


    CompositeImpl.prototype.add = function (obj) {
        console.log('add...');
    };

    CompositeImpl.prototype.remove = function (obj) {
        console.log('remove...');
    };

    CompositeImpl.prototype.update = function (obj) {
        console.log('update...');
    };
  • 优点:

程序员可以有一个参考

  • 缺点:

太松散,无法检查接口是否实现

属性检测


    /**
     * interface Composite{
     *  function add(obj);
     *  function remove(obj);
     *  function update(obj);
     * }
     * interface FormItem{
     *  function select(obj);
     * }
     */

    // CompositeImpl implements Composite , FormItem
    var CompositeImpl = function(){
        // 显示的在类的内部,接收所实现的接口
        // 是一个规范:在类的内部定义一个数组,名字要固定
        this.implementsInterfaces = ['Composite','FormItem'];
//        this.implementsInterfaces = ['Composite'];
    }


    CompositeImpl.prototype.add = function (obj) {
        console.log('add...');
    };

    CompositeImpl.prototype.remove = function (obj) {
        console.log('remove...');
    };

    CompositeImpl.prototype.update = function (obj) {
        console.log('update...');
    };

    CompositeImpl.prototype.select = function (obj) {
        console.log('select...');
    };


    function CheckCompositeImpl(instance){
        // 判断当前对象是否实现了所有接口
        if( !IsImplements(instance,'Composite','FormItem')){
             throw new Error('Object does not implements a require interface');
        }
    }

    // 公用的具体的检测方法,返回 boolean
    function  IsImplements(obj){
        // 从接口名字开始
        for( var i = 1; i < arguments.length; i++ ){
            // 接收接口的名字
            var interfaceName = arguments[i];

            // 判断此方法是成功还是失败
            var interfaceFound = false;

            for ( var j = 0; j < obj.implementsInterfaces.length; j++ ){
                if( interfaceName == obj.implementsInterfaces[j]){
                    interfaceFound = true;
                    break;
                }
            }

            if( !interfaceFound ){
                return false;
            }
        }

        return true;
    }

    var c1 = new CompositeImpl();
    CheckCompositeImpl(c1);
    c1.add(1);

鸭式辨型法

    /**
     * 接口类
     * @param {string} name 接口名
     * @param {string[]} methods 接收方法名称的集合[数组]
     */
    var Interface = function (name, methods) {
        if( arguments.length != 2 ) {
            throw new Error('This instance interface constructor must be 2 length!');
        }

        this.name = name;
        this.methods = [];// 内置的空数组对象,用来接收参数methods里的元素
        for ( var i = 0 ; i < methods.length; i++ ){
            if( !typeof methods[i] === 'string' ){
                throw new Error('The name of interface method\'s type must be string!');
            }
            this.methods.push(methods[i]);
        }
    };

    // 接口实例
    var CompositeInterface = new Interface('CompositeInterface',['add','remove']);
    var FormItemInterface = new Interface('FormItemInterface',['update','select']);

    // 实现类
    var CompositeImpl = function(){};

    CompositeImpl.prototype.add = function (obj) {
        console.log('add...');
    };

    CompositeImpl.prototype.remove = function (obj) {
        console.log('remove...');
    };

    CompositeImpl.prototype.update = function (obj) {
        console.log('update...');
    };

    CompositeImpl.prototype.select = function (obj) {
        console.log('select...');
    };



    // 检测方法
    Interface.ensueImplements = function (obj) {
        // 如果参数长度小于2则失败
        if( arguments.length < 2 ){
            throw new Error('Interface.ensueImplements method constructor arguments\'s length must be >= 2');
        }

        for( var i = 1; i < arguments.length; i++ ){
            var instanceInterface = arguments[i];
            // 判断参数是否是接口类的类型
            if( instanceInterface.constructor !== Interface ){
                throw new Error('The arguments constructor not be Inrerface Class');
            }

            for( var j = 0; j < instanceInterface.methods.length; j++ ){
                // 方法名
                var methodName = instanceInterface.methods[j];
                if( !obj[methodName] || typeof obj[methodName] !== 'function'){
                    throw new Error('The method:' + methodName + ' is not found');
                }
            }
        }
    };

    // 实现类实例
    var c1 = new CompositeImpl();

    // 检测是否实现接口
    Interface.ensueImplements(c1,CompositeInterface,FormItemInterface);

    c1.add(1);

完美的接口实现

展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部
返回顶部
顶部