javascript-继承

理解继承:

继承用于在创建子类时,可以调用父类的方法和使用父类定义的变量。 javascript本身没有提供继承的关键字, 而是通过原型链的方式,达到的继承这种效果(即调用父类方法,父类基本变量copy), 然而javascript 对于父类引用类型的,子类在使用时,使用的是同一份地址,所以如果需求是如此,就没问题, 否则,需要重新在子类中重新定义自己的引用类型,因为javascript没有static 这个含义。

下面会通过一系列演变,最终达到我们所希望的继承效果。

1. 原型链
e.g.
function SuperType() {
    this.property = true;
    this.colors = ['red','blue','green'];
}

SuperType.prototype.getSuperValue = function() {
    return this.property;
};    

function SubType() {
    this.subproperty = false;
}

SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function() {
    return this.subproperty;
};

var instance1 = new SubType();
alert(instance1.getSuperValue()); 

instance1.colors.push('black');
alert(instance1.colors);

var instance2 = new SubType();
alert(instance2.colors);

问题: 
    1. 父类定义的引用类型变量,子类在创建时会拥有改变量,但此时效果如何在子类上创建一个原型引用属性一样, 而通过子类创建的变量在修改时,会修改到同一份地址,这必然不是我们想要的
    2. 在创建子类同时,不能像父类构造函数传递参数    


2.借用构造函数
e.g.
function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
    this.sayColor = function() {
    return this.colors;
}

function SubType() {
    SuperType.call(this,' Nicholas');
    this.age = 29;
}

var instance1 = new SubType();
instance1.colors.push('black');
alert(instance1.colors);

var instance2 = new SubType();
alert(instance2.colors);

alert(instance1.name);
alert(instance1.age);
alert(instance1.sayColor == instance2.sayColor); //false


优点:
    1.通过 call 改变父类作用域,达到了对于引用类型不共用同一份的问题。

问题:
    1.但无法使用函数复用了,在实例子类时,父类定义的函数也会重新copy


3.组合继承
e.g.
function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}

SuperType.prototype.sayName = function() {
    alert(this.name);
};

function SubType(name, age) {
    SuperType.call(this,name);
    this.age = age;
}

SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType();
SubType.prototype.sayAge = function() {
    alert(this.age);
};

var instance1 = new SubType('Nicholas', 29);
instance1.colors.push('black');
alert(instance1.colors);
instance1.sayName();
instance1.sayAge();

var instance2 = new SubType('Greg', 27);
alert(instance2.colors);
instance2.sayName();
instance2.sayAge();    

优点:
    1.融合原型链和借用构造函数, 堪称完美的方式

问题: 
    1.美中不足是 SuperType() 会调用2


4.原型式继承
e.g.
function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

var person = {
    name:'Nicholas',
    friends: ['Shelby', 'Court', 'Van'],
    sayColor: function() {
        return 'blue';
    }
};

var anotherPerson = object(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');

var yetAnotherPerson = object(person);
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push('Barbie');
alert(person.friends);
alert(anotherPerson.sayColor == yetAnotherPerson.sayColor);

equals: 
ECMAScript5版本

var person = {
    name:'Nicholas',
    friends: ['Shelby', 'Court', 'Van'],
    sayColor: function() {
        return 'blue';
    }
};

var anotherPerson = Object.create(person);
anotherPerson.name = 'Greg';
alert(anotherPerson.name);
anotherPerson.friends.push('Rob');

var yetAnotherPerson = Object.create(person);
alert(yetAnotherPerson.name);
yetAnotherPerson.name = 'Linda';
alert(yetAnotherPerson.name);

yetAnotherPerson.friends.push('Barbie');
alert(person.friends);
alert(anotherPerson.sayColor == yetAnotherPerson.sayColor);


注: 原型式继承也是一种不错的继承方式, 子类变量的修改并不会影响父类的prototype属性,方法也达到了共用, 唯一有点影响的事, 引用类型会始终共享相应的值。


5. 寄生式继承
e.g.        
function createAnother(original) {
    var clone = Object.create(original);
    clone.sayHi = function() {
        alert("hi");
    };
    return clone;
}

var person = {
    name: 'Nichoals',
    friends: ['Shelby', 'Court', 'Van']
};

var anotherPerson = createAnother(person);
anotherPerson.sayHi();

注: 使用集成式函数,缺点是函数不能复用, 这一点同构造函数类似


6.寄生组合式继承
e.g.
function inheritPrototype(subType, superType) {
    var prototype = Object.create(superType.prototype);
    prototype.constructor = subType;
    subType.prototype = prototype;
}

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

SuperType.prototype.sayName = function() {
    alert(this.name);
};

function SubType(name, age) {
    SuperType.call(this, name);
    this.age = age;
}

inheritPrototype(SubType, SuperType);

SubType.prototype.sayAge = function() {
    alert(this.age);
};

var instance1 = new SubType('Nicholas', 29);
instance1.colors.push('black');
alert(instance1.colors);
instance1.sayName();
instance1.sayAge();

var instance2 = new SubType('Greg', 27);
alert(instance2.colors);
instance2.sayName();
instance2.sayAge();

注: 完美

javascript-对象与原型

理解对象:

对象是一种风格, 和其它语言的类的概念相似,类中主要定义了一些基本属性和方法。
当类被通过构造使用时,实例对象也会拥有一份新的属性和方法(但此处方法不会被重复创建,还是指向同一个地址)

why? 方法不需要被重复创建呢?
首先,我们理解一个概念,所有语言中最终回转化成汇编执行,即便是动态语言,底层也会有汇编入栈,出栈的这种操作。

当我们通过实例化的对象去调用的一个方法时,我们会取得当前方法的引用地址,并将参数一个个push进去,然后pop出结果即可, 所以说,完全不需要构造两个相同的方法,即使这样做了,也不会有什么影响,浪费内存空间而已。

但变量一般都是copy创建的,我们当然不希望多个实例修改同一个变量,最终得到的变量是一样的吧,如果真的有这样需求,在其它语言中该变量用static定义,而在javascript中,那得用到prototype原型了,说白了,就是只定义一处,大家都使用同一份的方法。

1.工厂模式
e.g.
function createPerson(name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function() {
        alert(this.name);
    };
    return o;
}

var person1 = createPerson('Nicholas', 29, 'Software Enginner');
var person2 = createPerson('Grey', 27, 'Doctor');

问题:
     1. 每次创建方法都是一份新的地址 (重复)
     2. 没有知道该对象的具体类型,也就缺乏了内省机制,好比如说,你问它,你是谁? 它说不知道。但如果它知道自身是什么,那么那就可以做很多事情了。 python和ruby就完全具备此特性


2.构造函数模式
e.g. 
function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function() {
        alert(this.name);
    };
}

var person1 = new Person('Nicholas', 29, 'Software Engineer');
var person2 = new Person('Greg', 27, 'Doctor');

问题:
    1. 每次创建方法都是一份新的地址 (重复)

注: 此时使用构造函数方式创建对象时,对象内部会有一个constructor(构造属性)指向Person,表明时通过谁来创建的。 但constructor并不是实例的属性,它是指向Person原型的属性(即共用一份内存地址的属性)


3.原型模式:
e.g.
function Person() {
}

Person.prototype.name = 'Nicholas';
Person.prototype.age = 29;
Person.prototype.job = 'Software Engineer';
Person.prototype.sayName = function() {
    alert(this.name);
};

var person1 = new Person();
person1.sayName();            //Nicholas

var person2 = new Person();
person2.sayName();            //Nicholas
alert(person1.sayName == person2.sayName); // true

好处:
    1.解决了多次创建同一份地址方法

问题: 
    1.无法通过构造进行传参


3.1 字面量方式-1:
e.g.
function Person() {
}

Person.prototype = {
    name: 'Nicholas',
    age: 29,
    job: 'Software Engineer',
    sayName: function() {
        alert(this.name);
    }
}

好处: 
    1.不用每次敲一遍Person.prototype

问题:
    1.无法通过构造进行传参
    2.由于重写了默认的prototype, 即通过{}方式重新定义的对象, 因此,constructor属性也就变成了新的constructor属性,(指向了Object构造属性),不在指向Person函数

3.2 字面量方式-2:
e.g.
funciton Person() {
}

Person.prototype = {
    constructor: Person,
    name: 'Nicholas',
    age: 29,
    job: 'Software Engineer',
    sayName: function() {
        alert(this.name);
    }
}

好处: 
    1.不用每次敲一遍Person.prototype

问题:
    1.无法通过构造进行传参


3.3 字面量方式-3:
e.g.
function Person() {
}

Person.prototype = {
    constructor: Person,
    name: 'Nicholas',
    age: 29,
    job: 'Software Engineer',
    friends: ['Shelby', 'Court'],
    sayName: function() {
        alert(this.name);
    }
}

var person1 = new Person();
var person2 = new person();

person1.friend.push('Van');
alert(person1.friends);   //'Shelby,Court,Van'
alert(person2.friends);   //'Shelby,Court,Van'
alert(person1.friends === person2.friends); //true


好处: 
    1.不用每次敲一遍Person.prototype

问题:
    1.无法通过构造进行传参
    2.由于在原型中使用了引用类型的属性,通过实例修改时,会影响到其它实例。但如果设计目的就是如何,便不再是问题


4.组合使用构造函数模式和原型模式
e.g.
function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ['Shelby', 'Count'];
}

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

var person1 = new Person('Nicholas', 29, 'Software Engineer');
var person2 = new Person('Greg', 27, 'Doctor');

person1.friends.push('Van');
alert(person1.friends);   //'Shelby,Count,Van'
alert(person2.friends);   //'Shelby,Count'
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true

优点:
    1.解决了构造函数传值的问题
    2.使用了简化版的字面量方式
    3.方法在内存中只存在一份
    4.引用类型不再共享一份。

这种模式堪称完美


5. 动态原型模式
e.g.
function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;

    if (typeof this.sayName != 'function') {
        Person.prototype.sayName = function() {
            alert(this.name);
        };
    }
}

优点: 
    1. 变量的定义和方法都存在一个函数内,和其它包含class风格的语言更加接近

缺点:
    1. 使用这种方式时, 无法使用字面量方式重写原型方法,如果已经创建了实例的情况下重写原型,就会先切现有实例和新原型之间的联系。(所以说,这种方式不用自面量就好了)

经典模式之一    


6. 寄生构造函数模式
e.g.
function Person(name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function() {
        alert(this.name);
    };
    return o
}

var friend = new Person('Nicholas', 29, 'Software Engineer');
friend.sayName(); //'Nicholas'

注: 对于此模式,目前还未充分认识其价值,或许其中也蕴含宝藏,暂不评论


7. 稳妥构造函数模式
e.g.
function Person(name, age, job) {
    var o = new Object();
    o.sayName = function() {
        alert(name);
    };
    return o;
}

var friend = Person('Nicholas', 29, 'Software Enginner');
friend.sayName();

注: 暂不评论

javascript-数据属性&访问器属性

作用:主要用于定义变量的权限 (可读,可写,可删除,可枚举)

在其它语言中,比如java, 对于class类中变量需要设置其get, set方法,
这样做的目的是出于安全考虑, 仅能调用由class暴露出来的方法。

并且可以在方法内对变量进行其它操作,修改其内容或者内部的其它变量。当然,这其实也是非必需的。如swift语言中新出的计算属性这个新词

比如lua语言中,并没有要求要对变量设置其get, set属性, 在需要的时候,可以直接修改其内容,只要不进行一些我们自己都不能确定的操作,程序依然是健壮的。当然,这里完全是由人来控制。

object-c语言中, 如果想访问一个受保护的变量, 还可以通过kvc,kvo的方式

总值,javasript的数值属性,访问器属性只是一种对变量进行权限约定和对象的变量进行添加,修改的一种手段, 在合适的时候使用即可

1. 数值属性
    行为特征:
    1.1 [[Configurable]]  是否可删除
    1.2 [[Enumerable]]    是否可遍历
    1.3 [[Writable]]      是否可写
    1.4 [[Value]]          属性值, 默认undefined

    e.g.
    var person = {};
    Object.defineProperty(person, 'name', {
        writable: false,
        value: 'Nicholas'
    });

    alert(person.name); //'Nicholas'
    person.name = 'Greg';
    alert(person.name); //'Nicholas'


2. 访问器属性
    行为特征:
    2.1 [[Configurable]]  是否可删除
    2.2 [[Enumerable]]    是否可遍历
    2.3 [[Get]]              读取属性时调用的方法   默认 underfined
    2.4 [[Set]]              写入属性行调用的方法   默认 underfined

    e.g.
    var book = {
        _year: 2004,
        edition: 1
    };

    Object.defineProerty(book, 'year', {
        get: function() {
            return this._year;
        },
        set: function(newValue) {
            if (newValue > 2004) {
                this._year = newValue;
                this._edition += newValue - 2004;
            }
        }
    });

    book.year = 20005; 
    alert(book.edition); //2

注: 当通过数据属性或访问器的重新定义变量时, Configurable, Enumerable,Writable 默认值全部都重设为false, 不使用,默认是true

多属性定义方法
var book = {};
Object.defineProperties(book, {
    _year: {
        value: 2004
    },

    edition: {
        value: 1
    },

    year: {
        get: function() {
            return this._year;
        },

        set: function(newValue) {
            if (newValue > 2004) {
                this._year = newValue;
                this.edition += newValue - 2004;
            }
        }
    }
});


属性特征读取方式
var descriptor = Object.getOwnPropertyDescriptor(book, '_year');
alert(descriptor.value);        //2004
alert(descriptor.configurable); //false
alert(type description.get);    //'undefined'

var descriptor = Object.getOwnPropertyDescriptor(book, 'year');
alert(descriptor.value);         //undefined
alert(descriptor.enumerable);     //false
alert(typeof descriptor.get);   //'function'

javascript-函数属性callee, caller, apply, call, bind

callee

指向当前拥有arguments对象的函数指针

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

equals:

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


使用 callee 方法具有更好的应用场景。
e.g.
var trueFactorial = factorial;

factorial = function() {
    return 0;
};

alert(trueFactorial(5));
alert(factorial(5));

注: 如果使用 callee参数, 当factorial被指向 别处时, trueFactorial中的callee 也能指向正确的位置。

caller

指向调用当前函数的函数的引用,  即如果对该引用执行调用方法(),会陷入无限循环当中,然卵用

function outer() {
    inner();
}

function inner() {
    alert(inner.caller);
}

outer();

equals:

function outer() {
    inner();
}

function inner() {
    alert(arguments.callee.caller);
}

outer();


扩展: 
function outer() {
    inner();
}

function inner() {
    alert(outer.caller);
}

function A() {
    outer();
}

A();

当然也可以这么玩。 

apply,call

扩充作用域, 说白了,可以将其他对象注入到函数体内,当在函数内执行this. 操作时,操作对象其实是注入的对象。

1.apply 
    参数是可变的。

var a = "I'm come from window";

function sum(num1, num2) {
    alert(this.a);                 //"I'm come from window"
    return num1 + num2;
}

function callSum1(num1, num2) {
    return sum.apply(this, arguments);
}

function callSum2(num1, num2) {
    return sum.apply(this, [num1, num2]);
}

alert(callSum1(10, 10));
alert(callSum2(10, 10));

此处this 传入的是window对象

2. call
    参数是固定数值

var a = "I'm come from window";

function sum(num1, num2) {
    alert(this.a);
    return num1 + num2;
} 

function callSum(num1, num2) {
    return sum.call(this, num1, num2);
}

alert(callSum(10, 10));

bind

和apply, call类似, 但会创建一个新的函数实例, this 指向会通过bind()传入的值
e.g.    
window.color = 'red';
var o = { color: 'blue' };

function sayColor() {
    alert(this.color);
}

var objectSayColor = sayColor.bind(o);
objectSayColor();

javascript-作用域&变量申明提升

javascript 没有块级作用域,只有函数作用域

var color = 'red';
function A() {
    console.log(color);  //undefined
    var color = 'blue';
}
A();

equals:

var color = 'red';
function A() {
    var color;
    console.log(color);
    color = 'blue';
}

A();

注: 变量color在函数题内被重新定义时,会进行申明提升, 提升到函数作用域顶部。

javascript-值传递

公理:

1. 基础类型变量的直接赋值是值传递
e.g.
var num1 = 5;
var num2 = num1;
num1 = 10;
alert(num2);   //5
2. 引用类型变量的直接赋值是引用传递
e.g.
var obj1 = new Oject();
var obj2 = obj1;
obj1.name = 'Nicholas';
alert(obj2.name);     //"Nicholas"
3. javascript 中所有函数的参数都是按值传递的。
e.g.

基础类型:
function addTem(num) {
    num += 10;
    return num;
}

var count = 20;
var result = addTem(count);
alert(count);        //20
alert(result);        //30


对象类型
function setName(obj) {
    obj.name = 'Nicholas';
}

var person = new Object();
setName(person);
alert(person.name);   //'Nicholas'

分析: 
    setName(person) 此处时值传递。
    obj对象是person的指针地址的copy(栈中的指针地址copy)。 所以说两者还是指向同一份堆地址, 即同一份对象

    当obj.name 改变时,改变的是同一份对象。

    所以说此处的值传递 = 对象引用地址的copy !

在证
function setName(obj) {
    obj.name = 'Nicholas';
    obj = new Object();
    obj.name = 'Greg';
}

var person = new Object();
setName(person);
alert(person.name);      //"Nicholas"

分析:
    当执行obj = new Object();时, obj会指向堆空间中一份新的object对象

    当执行obj.name = 'Grey';时,实际操作的是一个新的对象

    而 person 还是指向原先在堆中的地址, 
    在setName 方法内,obj.name 将堆中的数值进行了改变,
    此后obj又指向一个新的对象,再次改变其属性时,对原先对象没有影响。

注意:

1.当复制保存对象的某个变量时,操作的是对象的引用。
2.当在为对象添加属性时,操作的是实际的对象

javascript堆栈浅析

类型:

1.    基础类型   
2.    引用类型

堆空间:

1.  存储对象 e.g.  Object()

栈空间:

1.  存储变量标示符  e.g.  var a;  var o = Object()  
    a -> 基础类型变量标示符
    o -> 对象引用变量标示符

2.  存储变量的值    e.g.  var a = 100
    100 -> 基础类型变量的值

人生若只如初见

人生如梦亦如幻

花开花落,卿为谁待?

流水落花春去也,天上人间。

无情不似多情苦,一寸还成千万缕。

天长地久有时尽,此恨绵绵无绝期。

长于春梦几多时,散似秋云无觅处。

一枝红艳露凝香,云雨巫山枉断肠。

幽兰露,如啼眼。无物结同心,烟花不堪

剪此去经年,应是良辰好景虚设,便纵有,万种风情,更于何人说?

野有蔓草,零露穰穰。有美一人,婉如清扬。邂逅相遇,与子偕臧。