javascript哲学思想

2,272 阅读11分钟

推荐好文:悟透javascript www.cnblogs.com/zhangshiwen…

编程世界

编程世界里只存在两种基本元素,一个是数据,一个是代码。编程世界就是在数据和代码千丝万缕的纠缠中呈现出无限的生机和活力。

javascript世界

要理解JavaScript,你得首先放下对象和类的概念,回到数据和代码的本原。前面说过,编程世界只有数据和代码两种基本元素,而这两种元素又有着纠缠不清的关系。JavaScript就是把数据和代码都简化到最原始的程度。

JavaScript中的数据很简洁的。简单数据只有 undefined, null, boolean, number和string这五种,而复杂数据只有一种,即object。这就好比中国古典的朴素唯物思想,把世界最基本的元素归为金木水火土,其他复杂 的物质都是由这五种基本元素组成。

JavaScript中的代码只体现为一种形式,就是function。

任何一个JavaScript的标识、常量、变量和参数都只是unfined, null, bool, number, string, object 和 function类型中的一种,也就typeof返回值表明的类型。除此之外没有其他类型了。

没有类

在我们学习编程语言时,都会遇到‘类’这个概念。我们知道学习就是掌握知识从无到有的过程,但是有没有想过为什么会有‘类’,一定要有它吗?

    var life = {};
    for (life.age = 1; life.age <= 3; life.age++)
    {
        switch(life.age)
        {
            case 1: life.body = "卵细胞";
                    life.say = function(){alert(this.age+this.body)};
                    break;
            case 2: life.tail = "尾巴";
                    life.gill = "腮";
                    life.body = "蝌蚪";
                    life.say = function(){alert(this.age+this.body+"-"+this.tail+","+this.gill)};
                    break;
            case 3: delete life.tail;
                    delete life.gill;
                    life.legs = "四条腿";
                    life.lung = "肺";
                    life.body = "青蛙";
                    life.say = function(){alert(this.age+this.body+"-"+this.legs+","+this.lung)};
                    break;
        };
        life.say();
    };

看这段代码一开始产生了一个生命对象life,经过后面的进化后变成了青蛙,当然继续进化可能变成其它你想不到的东西。

在现实世界里一开始也是没有”类“的,都是由分子原子组成,但为什么会出来哺乳类,飞禽类嘞?这就是人的聪明之处了,人为归类,为了方便我们后续处理事情。

计算机是人创造出来的,编程也是人的逻辑思想,对某一类事物进行归类,能为我们后续处理事物解决不少麻烦。

所以js中没有类这种概念上的强制,也能解决问题。

也可以有类(es6中已经推出了class概念),用了class后人在编程时可以更好的抽象编程里的世界了。

函数与对象

任何一个函数都可以为其动态地添加或去除属性,这些属性可以是简单类型,可以是对象,也可以是其他函数。也就是说,函数具有对象的全部特征,你完全可以把函数当对象来用。

其实,函数就是对象,只不过比一般的对象多了一个括号“()”操作符,这个操作符用来执行函数的逻辑。即,函数本身还可以被调用,一般对 象却不可以被调用,除此之外完全相同。

自从有了对象,编程世界就被划分成两部分,一个是对象内的世界,一个是对象外的世界。 对象天生具有自私的一面,外面的世界未经允许是不可访问对象内部的。对象也有大方的一面,它对外提供属性和方法,也为他人服务。

既然是对象这种具体的事物了,有没有想过”对象的意识“。

就在对象将世界划分为内外两部分的同时,对象的“自我”也就随之产生。“自我意识”是生命的最基本特征!正是由于对象这种强大的生命力,才使得编程世界充满无限的生机和活力。this就是对象的意识

一般编程语言的this就是对象自己,而JavaScript的this却并不一定!this可能是我,也可能是你,可能是他,反正是我中有你,你中有我。所以this就像意识一样,不是一层不变的,不同的时间,地点,场景你可能会有不同的意识。看,js多美。

    function WhoAmI()       //定义一个函数WhoAmI
    {
        alert("I'm " + this.name + " of " + typeof(this));
    };
    
    WhoAmI();   //此时是this当前这段代码的全局对象,在浏览器中就是window对象,其name属性为空字符串。输出:I'm of object

    var BillGates = {name: "Bill Gates"};
    BillGates.WhoAmI = WhoAmI;  //将函数WhoAmI作为BillGates的方法。
    BillGates.WhoAmI();         //此时的this是BillGates。输出:I'm Bill Gates of object
    
    var SteveJobs = {name: "Steve Jobs"};
    SteveJobs.WhoAmI = WhoAmI;  //将函数WhoAmI作为SteveJobs的方法。
    SteveJobs.WhoAmI();         //此时的this是SteveJobs。输出:I'm Steve Jobs of object

    WhoAmI.call(BillGates);     //直接将BillGates作为this,调用WhoAmI。输出:I'm Bill Gates of object
    WhoAmI.call(SteveJobs);     //直接将SteveJobs作为this,调用WhoAmI。输出:I'm Steve Jobs of object
    
    BillGates.WhoAmI.call(SteveJobs);   //将SteveJobs作为this,却调用BillGates的WhoAmI方法。输出:I'm Steve Jobs of object
    SteveJobs.WhoAmI.call(BillGates);   //将BillGates作为this,却调用SteveJobs的WhoAmI方法。输出:I'm Bill Gates of object

    WhoAmI.WhoAmI = WhoAmI;     //将WhoAmI函数设置为自身的方法。
    WhoAmI.name = "WhoAmI";
    WhoAmI.WhoAmI();            //此时的this是WhoAmI函数自己。输出:I'm WhoAmI of function
        
    ({name: "nobody", WhoAmI: WhoAmI}).WhoAmI();    //临时创建一个匿名对象并设置属性后调用WhoAmI方法。输出:I'm nobody of object

从上面的代码可以看出,同一个函数可以从不同的角度来调用,this并不一定是函数本身所属的对象。this只是在任意对象和function元素结合时的一个概念。

看科幻电影经常有将自己的意识转移到其它事物上的身影。看,js就有这种能力。

创建对象

JSON创建对象

// 创建一个没有任何属性的对象:
var o = {};

// 创建一个对象并设置属性及初始值:
var person = {
    name: "Angel", 
    age: 18, 
    married: false
};

// 创建一个对象并设置属性和方法:
var speaker = {
    text: "Hello World", 
    say: function(){alert(this.text)}
};

// 创建一个更复杂的对象,嵌套其他对象和对象数组等:
var company = {
    name: "Microsoft",
    product: "softwares",
    chairman: {
        name: "Bill Gates", 
        age: 53, 
        Married: true
    },
    employees: [
        {name: "Angel", age: 26, Married: false}, 
        {name: "Hanson", age: 32, Marred: true}
    ],
    readme: function() {document.write(this.name + " product " + this.product);}
};

new操作符构造对象

function MyFunc() {};         //定义一个空函数
var anObj = new MyFunc();  //使用new操作符,借助MyFun函数,就创建了一个对象

这种写法如何理解

// 等价于这种形式(简陋版)
function MyFunc(){};
var anObj = {};     //创建一个对象
MyFunc.call(anObj); //将anObj对象作为this指针调用MyFunc函数

执行new命令会经过以下几个步骤

  • 创建一个空对象,这个对象将会是new Person()返回的对象实例;
  • 将这个空对象的原型指向构造函数的prototype属性;
  • 将构造函数的this指向空对象,并运行构造函数;
  • 判断构造函数返回的是不是对象,是的话返回默认对象,不是的话返回之前创建的空对象,没有返回值默认返回空对象
function _new (person, ...rest) {
    // 创建一个空对象,这个对象将会是返回的对象实例
    var obj = {};
    
    // 将这个空对象的原型指向person的prototype属性;
    obj.__prototype__ = person.prototype;
    
    // 上述两步可以合为一步 :  var obj = Object.create(person.prototype)  
   
    //将person的this指向空对象,并运行person函数,apply命令绑定this后就会运行person
    var res = person.apply(obj, rest);
    
    // 判断res返回的是不是对象,是的话返res,不是的话返回之前创建的obj,没有返回值默认返回obj
    return (typeof res === 'object' && res != null) ? res: obj;
}

构造函数

看前面知道我们可以根据函数new出很多对象。那为什么我们不能写一些生成实例对象的函数,作为对象的模板,描述实例对象的基本结构呢?很显然js就是这样的。

我们称可以生成多个实例对象,描述实例对象的基本结构的函数为构造函数。如果你愿意把构造函数当作“类”的话,她就是“类”,因为她本来就有“类”的那些特征。

function SayHello() {     // 先定义一份SayHello函数代码
    alert("Hello, I'm " + this.name);
};

function Person(name) {   // 带参数的构造函数
    this.name = name;     // 将参数值赋给给this对象的属性
    this.SayHello = SayHello;   //给this对象SayHello方法赋值为前面那份SayHello代码。
};

var BillGates = new Person("Bill Gates");   //创建BillGates对象
var SteveJobs = new Person("Steve Jobs");   //创建SteveJobs对象

alert(BillGates.SayHello == SteveJobs.SayHello); //显示:true

看这段代码虽然达到了共享了一份方法代码的目的,但是反映不出SayHello方法与Person类的关系,如果将SayHello方法写到Person构造函数里,那每次new出来的对象里都有各自的SayHello方法,在内存上是种浪费。

原型

Javascript创造者在创造时考虑到了这种情况。所以JavaScript的所有function类型的对象都有一个prototype属性。这个prototype属性本身又是一个object类型的对 象,因此我们也可以给这个prototype对象添加任意的属性和方法。

我们将prototype属性称之为原型

function Person(name)
{
    this.name = name;   // 设置对象属性,每个对象各自一份属性数据
};

Person.prototype.SayHello = function()  // 给Person函数的prototype添加SayHello方法。
{
    alert("Hello, I'm " + this.name);
}

var BillGates = new Person("Bill Gates");   // 创建BillGates对象
var SteveJobs = new Person("Steve Jobs");   // 创建SteveJobs对象

BillGates.SayHello();   // 通过BillGates对象直接调用到SayHello方法
SteveJobs.SayHello();   // 通过SteveJobs对象直接调用到SayHello方法

alert(BillGates.SayHello == SteveJobs.SayHello); // 因为两个对象是共享prototype的SayHello,所以显示:true

看这段代码,我们将SayHello方法写到Person的原型上,这样能体现出SayHello方法与Person类的关系。

原型的多态

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

Person.prototype.company = "Microsoft";   // 原型的属性

Person.prototype.SayHello = function() {  // 原型的方法
    alert("Hello, I'm " + this.name + " of " + this.company);
};

var BillGates = new Person("Bill Gates");
BillGates.SayHello();   // 由于继承了原型的东西,规规矩矩输出:Hello, I'm Bill Gates.

var SteveJobs = new Person("Steve Jobs");
SteveJobs.company = "Apple";                // 设置自己的company属性,掩盖了原型的company属性
SteveJobs.SayHello = function() {           // 实现了自己的SayHello方法,掩盖了原型的SayHello方法
    alert("Hi, " + this.name + " like " + this.company + ", ha ha ha ");
};

SteveJobs.SayHello();   // 都是自己覆盖的属性和方法,输出:Hi, Steve Jobs like Apple, ha ha ha 

BillGates.SayHello();   // SteveJobs的覆盖没有影响原型对象,BillGates还是按老样子输出

这与面向对象里的“多态”很像,可以实现自己的方法。

原型的扩展

看原型的多态我们想到,可以随时给原型对象动态添加新的属性和方法,从而动态地扩展基类的功能特性,这是其它语言所不能具备的。

原型的继承-原型链

 function Person(name) {    // 基类构造函数
     this.name = name;
 };
 
 Person.prototype.SayHello = function() {   // 给基类构造函数的prototype添加方法
     alert("Hello, I'm " + this.name);
 };
 
 function Employee(name, salary) {  // 子类构造函数
     Person.call(this, name);   // 调用基类构造函数
     this.salary = salary;
 };
 
 Employee.prototype = new Person(); // 建一个基类的对象作为子类原型的原型,这里就是原型链继承了
 
 Employee.prototype.ShowMeTheMoney = function() {   //给子类添构造函数的prototype添加方法
     alert(this.name + " $" + this.salary);
 };

 var BillGates = new Person("Bill Gates");   // 创建基类Person的BillGates对象
 var SteveJobs = new Employee("Steve Jobs", 1234);   // 创建子类Employee的SteveJobs对象

 BillGates.SayHello();       // 通过对象直接调用到prototype的方法
 SteveJobs.SayHello();       // 通过子类对象直接调用基类prototype的方法,关注!
 SteveJobs.ShowMeTheMoney(); // 通过子类对象直接调用子类prototype的方法

 alert(BillGates.SayHello == SteveJobs.SayHello); //显示:true,表明prototype的方法是共享的

看这段代码,我们知道Employee子类中没有SayHello方法,而Person基类中有SayHello方法,因为Employee原型上建一个基类的对象作为子类原型的原型,所以用Employee类new出来的对象可以访问到父亲的父亲的SayHello方法。

这种对象的属性和方法追溯机制是通过所谓的prototype链(原型链)来实现的。

在原型链的最末端,就是Object构造函数prototype属性指向的那一个原型对象。这个原型对象是所有对象的最老祖先,这个老祖宗实现了诸如 toString等所有对象天生就该具有的方法。其他内置构造函数,如Function, Boolean, String, Date和RegExp等的prototype都是从这个老祖宗传承下来的,但他们各自又定义了自身的属性和方法,从而他们的子孙就表现出各自宗族的那些 特征。

ES6中的class(语法甘露)

我们前面说的都是ES5中的构造函数,在ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板,通过class关键字,可以定义类。

基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

更多详细介绍请看阮一峰的ECMAScript 6 入门 es6.ruanyifeng.com/#docs/class

javascript的整体框架就是这样的,就类似于我们对于地球这个世界的认知一样,整体的世界框架我们知道了,但这个世界里还有很多有趣的事物等着我们去发现。



谨以此文膜拜李战(leadzen)的悟透javascript www.cnblogs.com/zhangshiwen… 希望我们对javascript世界都所有了解