JavaScript总结(一)

JS(JavaScript):

        基于对象和事件驱动的脚本语言。它的作者是布兰登·艾奇(Brendan Eich),在1995年用了10天的时间设计完成,估计他也没想到这个脚本语言会受到认可和欢迎,并成为目前最重要的前端语言,关于他和JS的历史稍后再讨论。JS设计中借鉴了C语言的基本语法;借鉴了Java语言的数据类型和内存管理;借鉴了Scheme语言,将函数提升到"第一等公民"(first class)的地位;借鉴了Self语言,使用基于原型(prototype)的继承机制。所以JS包含了简化的函数式编程和简化的面向对象编程的思想。

基于原型的面向对象编程(prototype-based object-oriented programming):

    特点:

1.一切皆对象。对象继承对象。没有类的概念。

2.原型对象是新对象的模板,它将自身的属性共享给新对象。一个对象不但可以享有自己创建时和运行时定义的属性,而且可以享有原型对象的属性。

3.除了语言原生的顶级对象,每一个对象都有自己的原型对象,所有对象构成一个树状的层级系统。root节点的顶层对象是一个语言原生的对象,其他所有对象都直接或间接继承它的属性。

    一个字:用对象创建对象。

    比如利用对象A作为原型对象创建对象B


    当对象访问属性的时候,如果在内部找不到,那么会在原型对象中查找到属性;如果原型对象中仍然找不到属性,原型对象会查找自身的原型对象,如此循环下去,直至找到属性或者到达顶级对象。对象查找属性的过程所经过的对象构成一条链条,称之为原型链(prototype chain)。JS通过原型链实现继承,没有类(Class)的概念,更谈不上子类父类、类和实例等概念。

    上例中,对象B也具有name属性和getName()、toString()方法,当访问对象B的name属性时,在其内部找不到,就到原型对象A中查找,A中查找到了name属性,成功返回。

call和apply方法:

    call方法:

语法:call([thisObj[,arg1[, arg2[, [,.argN]]]]])

说明:调用一个对象(thisObj)的一个方法,以对象(thisObj)替换当前方法的上下文对象(this)。如果没有提供 thisObj 参数,那么 Global 对象(window)将作为 thisObj。

    apply方法:

语法:apply([thisObj[,argArray]])

说明:作用和call方法一样,只是参数的传递形式不同而已。如果 argArray 不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError。如果没有提供 argArray 和 thisObj 任何一个参数,那么 Global 对象将被用作 thisObj, 并且无法被传递任何参数。

    总之,这两个方法的作用就是把函数里面的this替换成thisObj对象。

注意!下面所说的创建对象和实现继承只是JS的设计思路,并不是真正的JS语法,并不是真正的JS语法,并不是真正的JS语法,重要的事要说三遍。

创建对象:

    先创建一个用来初始化对象的构造器(constructor),或者称为构造函数(是一个函数对象,用来创建和初始化对象的):

function People(name){  
    this.name = name;
    this.getName = function(){return this.name};
}
    然后,创建空对象,并利用这个构造器对它进行初始化(用这个新对象的引用替换掉this):
var frank = {};
People.call(frank,'Frank Shang');

    现在,通过frank.name='Frank Shang'等一系列操作,一个叫做Frank Shang的对象就创建出来了。

实现继承:

    继承肯定采用原型来实现。有一个topObject来代表顶层对象,那么创建people对象的时候,应该在people对象内部设置一个引用属性指向topObject;同理,创建student对象的时候,应该在student对象内部设置一个引用属性指向people对象,我们把这个引用原型对象的属性命名约定为"__proto__"。更进一步,为了构建一个对象的过程更自然,构建时候应该先在新对象中设置引用原型对象的属性,以表示先用模板制作出一个和模板一致的对象,然后再才执行构造函数初始化这个新对象自身的属性,以添加个性化的东西。具体实现代码如下【参考lazy_】:

var topObject = {
    __version__ : 1.0;
};

function People(name){
    this.name = name;
    this.getName = function(){return this.name};
}

var frank = {};  
frank.__proto__ = topObject;
People.call(frank,'Frank Shang');

function Student(name,major){
    this.name = name;  
    this.major = major;  
    this.getMajor = function(){return this.major};  
}
 
var xiaoming = {};
xiaoming.__proto__ = frank;
Student.call(xiaoming,'student xiaoming','music');

    现在,继承了人类属性的xiaoming对象就创建完成了,小明对象是用一个已经存在的人类对象Frank Shang作为模板创造出来的,它不单是人类,而且还是学生,有专业,是一个音乐专业的学生。

    但是创建一个继承对象要三行代码确实有点麻烦,我们创建对象时需要原型对象、构造函数、构造函数的参数,所以完全可以封装一下,将原型对象prototype作为属性放到构造器对象constructor中:

function newInstance(constructor){
    var obj = {};
    obj.__proto__ = constructor.prototype;
    constructor.apply(obj,arguments);
    return obj;
}  
function People(name){
    this.name = name;
    this.getName = function(){return this.name};
}
function Student(name,major){
    this.name = name;
    this.major = major;
    this.getMajor = function(){return this.major};
}
var frank = newInstance(People,'Frank Shang');
Student.prototype = frank;
var xiaoming = newInstance(Student,'student xiaoming','music');


    但是,JS为了模仿Java创建对象的形式,强行装了一波,非要用new关键字,创建对象时这样创建:

var xiaoming = new Student('student xiaoming');

就是Java中的构造函数创建对象啊,但是,蛋疼的是原型对象需要构造器单独指定,如果不指定会指向默认的原型对象,那默认的原型对象又是什么呢?

默认的原型对象有两个属性:__proto__和constructor。__proto__指向Object.prototype,目的是为了使生成的实例对象继承顶层对象Object.prototype;
而constructor指向函数本身,目的是为了使生成的实例对象newObject可以直接通过newObject.constructor访问到构造函数,同时构造函数和原型对象可以互相访问也是个良好的设计。建议在更改原型对象时也更改原型对象中对构造器的引用,即f.prototype.constructor=f;

Notes:

  • 所有对象都有自己的原型对象。所有构造函数的原型对象都是Function.prototype,Object.prototype是最顶层的对象。我们可以在Function.prototype上增加方法,那么在原型链下方的函数,就可获得这些方法,同理我们可以在Object.prototype上增加方法,那么js所有对象都拥有了这个方法。
  • 方法最好放在原型对象中,让每个实例对象都共享同一个方法。如果方法放在构造函数中,那么每个对象都有自己独立的一份方法代码,浪费内存。
  • 字段变量最好放在构造函数中,让每个实例对象都具有一份自己的字段。除非要在所有子类中共享,实现类似静态变量的效果,才把字段放在原型中。
  • 继承层次不宜过深,原型链查找会耗费时间。例如上面学生对象也有name属性,会覆盖人类对象的name属性,也有利于提高属性查找的效率。

JS中的__proto__、prototype、constructor:

    任何由构造器产生的对象的__proto__属性都指向该构造器的prototype对象。

    所有构造器的__proto__都指向Function的prototype。也就是说所有的构造器都继承了Function.prototype的方法。

    __proto__最终的指向都是Object.prototype,这也就是js中的原型链。

    每个函数都有一个默认的prototype属性,而这个prototype的constructor默认指向这个函数。

    每个具有原型的对象都会自动获得constructor属性。除了arguments、Enumerator、Error、Global、Math、RegExp、Regular Expression等一些特殊对象之外,其他所有的JavaScript内置对象都具备constructor属性。例如:Array、Boolean、Date、Function、Number、Object、String等。


弄明白prototype和constructor的关系太不容易了,花了一两天的时间,这里仅仅做个笔记。


JS数据类型:

js究竟分为哪些类型是个比较蛋疼的问题,ECMAScript规范(JS遵循ECMA262规范)虽然总是统称为types,但有时也无法回避描述true、false、0-9这类数据时该用什么术语,经常看到用Primitive values来描述这类数据。JavaScript有两套系统操作数据,下图是周爱民的一张图:


原语类型/基本类型(Primitive Type):String、Boolean、NumberUndefined、Null

    直接代表语言实现的最底层的数据。

对象类型/引用类型(Object Type):Object

    属性的集合,包含一个原型对象。原型可以是空值。

String类型就是引号包围的字符串;Boolean类型只有两个值:true、false;Number类型就是普通数字,浮点用IEEE754表示。

Undefined类型只有一个值那就是undefined。默认情况下,未声明变量的值、声明但未初始化的变量的值、函数未指定的返回值都会是undefined。

Null类型只有一个值那就是null。null用来表示不存在的对象。

typeof运算符会返回变量或值的类型:

  • undefined - 如果变量是 Undefined 类型的
  • boolean - 如果变量是 Boolean 类型的
  • number - 如果变量是 Number 类型的
  • string - 如果变量是 String 类型的
  • object - 如果变量是一种引用类型或 Null 类型的

typeof null会返回object本来是当初设计者的一个错误,该错误对整个系统没什么影响,但改过来在引擎上运行发现会导致更多错误,所以干脆不改了,null虽然是原生类型的,但也表示一个空对象(object),所以可以勉强接受这个错误。

值undefined实际上是从值null派生来的,因此 ECMAScript 把它们定义为相等的,undefined == null的值是true。尽管这两个值相等,但它们的含义不同。正如上面所说的,undefined 是声明了变量但未对其初始化时赋予该变量的值,null 则用于表示不存在的对象。如果函数或方法要返回的是对象,那么找不到该对象时,返回的通常是 null。所以undefined===null的值是false。


用于比较原始值的等号(==)和非等号(!=):
  比较相等性之前先进行类型转换。
  执行类型转换的规则如下:
    如果一个运算数是 Boolean 值,在检查相等性之前,把它转换成数字值。false 转换成 0,true 为 1。
    如果一个运算数是字符串,另一个是数字,在检查相等性之前,要尝试把字符串转换成数字。
    如果一个运算数是对象,另一个是字符串,在检查相等性之前,要尝试把对象转换成字符串。
    如果一个运算数是对象,另一个是数字,在检查相等性之前,要尝试把对象转换成数字。
  在比较时,该运算符还遵守下列规则:
    值 null 和 undefined 相等。
    在检查相等性时,不能把 null 和 undefined 转换成其他值。
    如果某个运算数是 NaN,等号将返回 false,非等号将返回 true。
    如果两个运算数都是对象,那么比较的是它们的引用值。如果两个运算数指向同一对象,那么等号返回 true,否则两个运算数不等。
用于比较对象的全等号(===)和非全等号(!==):
  不执行类型转换。
  ===只有在无需类型转换运算数就相等的情况下,才返回 true。
  !==只有在无需类型转换运算数不相等的情况下,才返回 true。

引用类型,也就是对象。

Object 对象:
ECMAScript 中的 Object 对象与 Java 中的 java.lang.Object 相似,ECMAScript 中的所有对象都由这个对象继承而来,Object 对象中的所有属性和方法都会出现在其他对象中,所以理解了 Object 对象,就可以更好地理解其他对象。


built-in:内置,内建

构造器 (constructor):创建和初始化对象的函数对象。构造器的“prototype”属性值是一个原型对象(prototype object),它用来实现继承和共享属性。

原型 (prototype):为其它对象提供共享属性的对象。当构造器创建一个对象,为了解决对象的属性引用,该对象会隐式引用构造器的“prototype”属性。通过程序表达式 constructor.prototype 可以引用到构造器的“prototype”属性,并且添加到对象原型里的属性,会通过继承与所有共享此原型的对象共享。另外,可使用 Object.create 内置函数,通过明确指定原型来创建一个新对象。

普通对象(ordinary object):基本的内部方法都有默认的行为的对象。

奇异对象(exotic object):基本的内部方法没有默认的行为的对象。普通对象之外的对象都是奇异对象。

标准对象(standard object):其语义符合该规范定义的语义的对象。

原生对象 (native object):ECMAScript 实现中,并非由宿主环境,而是完全由本规范定义其语义的对象。标准的原生对象由本规范定义。一些原生对象是内置的,其他的可在 ECMAScript 程序执行过程中构建。

宿主对象 (host object):由宿主环境提供的对象,用于完善 ECMAScript 执行环境。任何对象,不是原生对象就是宿主对象。

内置对象 (built-in object):由 ECMAScript 实现提供,独立于宿主环境的对象,ECMAScript 程序开始执行时就存在。标准的内置对象由本规范定义,ECMAScript 实现可以指定和定义其他的。所有内置对象是原生对象。一个内置构造器 (built-in constructor) 是个内置对象,也是个构造器。

函数 (function):对象类型的成员,标准内置构造器 Function 的一个实例,并且可做为子程序被调用。函数除了拥有命名的属性,还包含可执行代码、状态,用来确定被调用时的行为。函数的代码不限于 ECMAScript。

内置函数 (built-in function):作为函数的内置对象。如 parseInt 和 Math.exp 就是内置函数。一个实现可以提供本规范没有描述的依赖于实现的内置函数。

属性 (property):作为对象的一部分联系名和值。属性可能根据属性值的不同表现为直接的数据值(原始值,对象,或一个函数对象)或间接的一对访问器函数。

方法 (method):作为属性值的函数。当一个函数被作为一个对象的方法调用,此对象将作为 this 的值传递给函数。

内置方法 (built-in method):作为内置函数的方法。标准内置方法由本规范定义,一个 ECMAScript 实现可指定,提供其他额外的内置方法。

特性 (attribute):定义一个属性的一些特性的内部值。

自身属性 (own property):对象直接拥有的属性。

继承属性 (inherited property):不是对象的自身属性,但是是对象原型的属性 ( 原型的自身属性或继承属性 )。

字符串值 (String value):原始值,它是零个或多个 16 位无符号整数组成的有限有序序列。一个字符串值是字符串类型的成员。通常序列中的每个整数值代表 UTF-16 文本的单个 16 位单元。然而,对于其值,ECMAScript 只要求必须是 16 位无符号整数,除此之外没有任何限制或要求。


property:属性        attribute:特性

Object 是一个属性的集合。每个属性既可以是一个命名的数据属性,也可以是一个命名的访问器属性,或是一个内部属性:
命名的数据属性(named data property):由一个名字与一个 ECMAScript 语言值和一个 Boolean 属性集合组成。
命名的访问器属性(named accessor property):由一个名字与一个或两个访问器函数,和一个 Boolean 属性集合组成。访问器函数用于存取一个与该属性相关联的 ECMAScript 语言值。
内部属性(internal property):没有名字,且不能直接通过 ECMAScript 语言操作。内部属性的存在纯粹为了规范的目的。

属性的特性(Property Attributes):本规范中的特性(Attributes)用于定义和解释命名属性(properties)的状态。命名的数据属性由一个名字关联到一个下表中列出的特性 (attributes):


命名的访问器属性由一个名字关联到一个下表中列出的特性 (attributes):


Object内部属性和内部方法:

    本规范使用各种内部属性来定义对象值的语义。这些内部属性不是 ECMAScript 语言的一部分。本规范中纯粹是以说明为目的定义它们。ECMAScript 实现必须表现为仿佛它被这里描述的内部属性产生和操作。内部属性的名字用闭合双方括号 括起来。如果一个算法使用一个对象的一个内部属性,并且此对象没有实现需要的内部属性,那么抛出TypeError 异常。


    “any”指值可以是任何 ECMAScript 语言类型;“primitive”指 Undefined, Null, Boolean, String, , Number;“SpecOp”指内部属性是一个内部方法,一个抽象操作规范定义一个实现提供它的步骤。“SpecOp”后面跟着描述性参数名的列表。如果参数名和类型名一致那么这个名字用于描述参数的类型。如果“SpecOp”有返回值,那么这个参数列表后跟着“→”符号和返回值的类型。

ECMA-262 6th Edition文档:http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf

ECMA-262 5.1中文:http://lzw.me/pages/ecmascript/#287


布兰登·艾奇(Brendan Eich)和JavaScript的故事

90年代,浏览器霸主Netscape的崛起无疑是件大事,当然,先说一下当时的“国际环境”:

1992年,Nombas 公司开发了一种叫做 C 减减(C-minus-minus,简称 Cmm)的嵌入式脚本语言,后改名为ScriptEase,捆绑在CEnvi共享软件中,并随后开发出嵌入网页中的CEnvi的版本:ESPRESSO PAGE(浓咖啡般的页面)。

1994年10月13日,Netscape公司发布了Mosaic Netscape 0.9。第一个比较成熟的浏览器,但没办法跟用户互动。

1994年SUN公司的团队所设计的Java 1.0a版本已经提供下载。并许诺这种语言可以"一次编写,到处运行"(Write Once, Run Anywhere),还没正式推出就吸引了无数追捧,并计划1995年正式推出。

1995年4月,Netscape公司录用了34岁的系统程序员Brendan Eich,让他研究将Scheme语言作为网页脚本语言的可能性,他也是通过《Structure and Interpretation of Computer Programs》这本书学会了Scheme。

1995年5月,由于Netscape公司管理层大部分都成了Java语言的粉丝,最终Netscape公司做出决策,未来的网页脚本语言必须"看上去与Java足够相似",但是比Java简单,使得非专业的网页作者也能很快上手,并将任务交给布兰登·艾奇。

好了,背景就这些。4月刚入职的布兰登·艾奇本来应聘Scheme工程师的,结果刚到公司一个月要求写一门类似Java的面向对象的语言,现在,求当时布兰登·艾奇的心理阴影面积。或者你可以想想你本来面试的职位是Android工程师,结果到公司不久老大分给你IOS的任务,不用想最开始你是拒绝的,总不能你让我写我就写吧,我总要先试一下吧,但老大说给你加特技,加特技,不对,是加鸡腿,然后你就“勉强”地去学了。好了,回到1995年,可布兰登·艾奇认对Java一点兴趣也没有,认为Java并不适合被业余程序员或新手使用,所以他最后设计的JavaScript与Java没有多少共同点,而由于与Sun的合作,JavaScript实际上是Sun的注册商标(今天该商标由Oracle继承),而Netscape获得JavaScript的永久使用权(今天由Mozilla继承)。他设计用了10天,你没有听错,就是10天,先不要用你那崇拜的小眼神惊呼,这么短时间内设计出来的脚本语言难免存在一些问题,这也是为什么很多前端程序员甚至作者布兰登·艾奇自己都不满意的原因了,PS:我也觉得写JS真的是蛋疼。最开始的JS程序混乱不堪,但后来由于程序员们对程序设计的全新理解以及新技术加入,让JS看起来也没那么糟糕。

由于后来微软的加入,让脚本分为三大阵营:Netscape Navigator 3.0 中的 JavaScript、IE 中的 JScript 以及 CEnvi 中的 ScriptEase。为了统一规范,欧洲计算机制造商协会(ECMA)第 39 技术委员会(TC39)被委派来“标准化一个通用、跨平台、中立于厂商的脚本语言的语法和语义 ECMA-262。也就是说JS遵循ECMA-262规范。

发布了48 篇原创文章 · 获赞 168 · 访问量 56万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览