JavaScript 高程设计——引用类型(一)

引用类型的值(对象)是引用类型的一个实例。在 ECMAScript 中,引用类型是一种数据结构,用于将数据和功能组织在一起。

对象是某个特定引用类型的实例。新对象是使用 new 操作符后跟一个构造函数来创建的。构造函数本身就是一个函数,只不过该函数是出于创建新对象的目的而定义的。

var person = new Object();

Object 类型

创建 Object 实例的方式有两种。第一种是使用 new 操作符后跟 Object 构造函数,如下所示:

var person = new Object();
person.name = "Nicholas";
person.age = 29;

另一种方式是使用对象字面量表示法。对象字面量是对象定义的一种简写形式,目的在于简化创建包含大量属性的对象的过程。

var person = {    
    name : "Nicholas",    
    age : 29
};

Array 类型

创建数组的基本方式有两种。第一种是使用 Array 构造函数,如下面的代码所示。

var colors = new Array();
// 创建 length 值20的数组
var colors = new Array(20); 
// 码创建了一个包含3个字符串值的数组
var colors = new Array("red", "blue", "green");

// 创建一个包含3项的数组
var colors = Array(3);
// 创建一个包含1项,即字符串"Greg"的数组
var names = Array("Greg");

// 创建一个包含3个字符串的数组
var colors = ["red", "blue", "green"];

数组最多可以包含4 294 967 295个项,如果想添加的项数超过这个上限值,就会发生异常。

检测数组

对于一个网页,或者一个全局作用域而言,使用 instanceof 操作符就能得到满意的结果:

if (value instanceof Array){    
    //对数组执行某些操作
}

对于有还有多个框架的页面,需要使用 ECMAScript 5 新增的 Array.isArray() 方法:

if (Array.isArray(value)){    
    //对数组执行某些操作
}

如果数组中的某一项的值是 null 或者 undefined,那么该值在 join()toLocaleString()toString()valueOf()方法返回的结果中以空字符串表示。

栈方法

push() 方法可以接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组的长度。而 pop() 方法则从数组末尾移除最后一项,减少数组的 length 值,然后返回移除的项。

队列方法

shift() 能够移除数组中的第一个项并返回该项,同时将数组长度减1。unshift()shift() 的用途相反:它能在数组前端添加任意个项并返回新数组的长度。

重排序方法

数组中已经存在两个可以直接用来重排序的方法:reverse()sort()

比较函数接收两个参数,如果第一个参数应该位于第二个之前则返回一个负数,如果两个参数相等则返回0,如果第一个参数应该位于第二个之后则返回一个正数。

function compare(value1, value2) {    
    if (value1 < value2) {        
        return 1;     
    } else if (value1 > value2) {        
        return -1;     
    } else {        
        return 0;    
    }
}

var values = [0, 1, 5, 10, 15];
values.sort(compare);

// 15,10,5,1,0
alert(values);

reverse()sort() 方法的返回值是经过排序之后的数组。

操作方法

  • concat: concat() 方法可以基于当前数组中的所有项创建一个新数组。具体来说,这个方法会先创建当前数组一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。在没有给 concat() 方法传递参数的情况下,它只是复制当前数组并返回副本。如果传递给 concat() 方法的是一或多个数组,则该方法会将这些数组中的每一项都添加到结果数组中。如果传递的值不是数组,这些值就会被简单地添加到结果数组的末尾。

  • slice: slice() 能够基于当前数组中的一或多个项创建一个新数组。slice()方法可以接受一或两个参数,即要返回项的起始和结束位置。在只有一个参数的情况下,slice()方法返回从该参数指定位置开始到当前数组末尾的所有项。如果有两个参数,该方法返回起始和结束位置之间的项——但不包括结束位置的项。

  • splice: splice() 的主要用途是向数组的中部插入项,但使用这种方法的方式则有如下3种。

var colors = ["red", "green", "blue"];
// 删除第一项
var removed = colors.splice(0,1);
// green,blue
alert(colors);
// red,返回的数组中只包含一项
alert(removed);

// 从位置1开始插入两项
removed = colors.splice(1, 0, "yellow", "orange");
// green,yellow,orange,blue
alert(colors);
// 返回的是一个空数组
alert(removed);

// 插入两项,删除一项
removed = colors.splice(1, 1, "red", "purple");
// green,red,purple,orange,blue
alert(colors);
// yellow,返回的数组中只包含一项
alert(removed);

位置方法

ECMAScript 5 为数组实例添加了两个位置方法:indexOf()lastIndexOf() 。这两个方法都接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。其中,indexOf() 方法从数组的开头(位置0)开始向后查找,lastIndexOf() 方法则从数组的末尾开始向前查找。

迭代方法

ECMAScript 5 为数组定义了5个迭代方法。每个方法都接收两个参数:要在每一项上运行的函数和(可选的)运行该函数的作用域对象——影响 this 的值。传入这些方法中的函数会接收三个参数:数组项的值、该项在数组中的位置和数组对象本身。

  • every():对数组中的每一项运行给定函数,如果该函数对每一项都返回 true,则返回 true
  • filter():对数组中的每一项运行给定函数,返回该函数会返回 true 的项组成的数组。
  • forEach():对数组中的每一项运行给定函数。这个方法没有返回值。
  • map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
  • some():对数组中的每一项运行给定函数,如果该函数对任一项返回 true,则返回 true

缩小方法

ECMAScript 5 还新增了两个缩小数组的方法:reduce()reduceRight()。这两个方法都会迭代数组的所有项,然后构建一个最终返回的值。

这两个方法都接收两个参数:一个在每一项上调用的函数和(可选的)作为缩小基础的初始值。传给 reduce()reduceRight() 的函数接收4个参数:前一个值、当前值、项的索引和数组对象。这个函数返回的任何值都会作为第一个参数自动传给下一项。第一次迭代发生在数组的第二项上,因此第一个参数是数组的第一项,第二个参数就是数组的第二项。

var values = [1,2,3,4,5];
var sum = values.reduce(function(prev, cur, index, array){    
    return prev + cur;
});

// 15
alert(sum);

Date 类型

在调用 Date 构造函数而不传递参数的情况下,新创建的对象自动获得当前日期和时间。如果想根据特定的日期和时间创建日期对象,必须传入表示该日期的毫秒数。ECMAScript 提供了两个方法:Date.parse()Date.UTC()

var now = new Date();

var someDate = new Date(Date.parse("May 25, 2004"));
var someDate = new Date("May 25, 2004");

// GMT时间2000年1月1日午夜零时
var y2k = new Date(Date.UTC(2000, 0));

// GMT时间2005年5月5日下午5:55:55
var allFives = new Date(Date.UTC(2005, 4, 5, 17, 55, 55));

var y2k = new Date(2000, 0);

ECMAScript 5 添加了 Data.now() 方法,返回表示调用这个方法时的日期和时间的毫秒数。

// 取得开始时间
// 在不支持now()的浏览器中使用 var start = +new Date(); 
var start = Date.now();

// 调用函数 doSomething();

// 取得停止时间
var stop = Date.now(),    
    result = stop – start;

日期格式化方法(略)

日期/时间组件方法(略)

JavaScript 高程设计——变量、作用域和内存问题

基本类型和引用类型的值

5种基本数据类型:Undefined、Null、Boolean、Number和String是按值访问的,因为可以操作保存在变量中的实际的值。

引用类型的值是保存在内存中的对象。与其他语言不同,JavaScript 不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。为此,引用类型的值是按引用访问的。

基本类型值的传递如同基本类型变量的复制一样,而引用类型值的传递,则如同引用类型变量的复制一样。参数只能按值传递。

虽然在检测基本数据类型时 typeof是非常得力的助手,但在检测引用类型的值时,这个操作符的用处不大。为此,ECMAScript 提供了 instanceof 操作符,其语法如下所示:

result = variable instanceof constructor

使用 typeof 操作符检测函数时,该操作符会返回 "function"。在 IE 和 Firefox 中,对正则表达式应用 typeof 会返回 "object",在 Safari 和 Chrome 中返回 "function"

执行环境及作用域

每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。

如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对象在最开始时只包含一个变量,即 arguments 对象(这个对象在全局环境中是不存在的)。

try-catch 语句的 catch 块和 with 语句可以延长作用域链。

垃圾收集

垃圾收集器必须跟踪哪个变量有用哪个变量没用,对于不再有用的变量打上标记,以备将来收回其占用的内存。用于标识无用变量的策略可能会因实现而异,但具体到浏览器中的实现,则通常有两个策略——标记清除和引用计数。

JavaScript 中最常用的垃圾收集方式是标记清除(mark-and-sweep)。当变量进入环境(例如,在函数中声明一个变量)时,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。

到2008年为止,IE、Firefox、Opera、Chrome 和 Safari 的 JavaScript 实现使用的都是标记清除式的垃圾收集策略(或类似的策略),只不过垃圾收集的时间间隔互有不同。

另一种不太常见的垃圾收集策略叫做引用计数(referencecounting)。循环引用指的是对象A中包含一个指向对象B的指针,而对象B中也包含一个指向对象A的引用。

因此,确保占用最少的内存可以让页面获得更好的性能。而优化内存占用的最佳方式,就是为执行中的代码只保存必要的数据。一旦数据不再有用,最好通过将其值设置为 null 来释放其引用——这个做法叫做解除引用(dereferencing)。

JavaScript 高程设计——基本概念

语法

严格模式

"use strict";

关键字和保留字

关键字

break   case    catch   continue    default delete  
do  else    finally for function    if  in  instanceof  
new return  switch  this    throw   try typeof  
var void    while   with    debugger*

保留字

abstract    boolean byte    char    class   const   
debugger    double  enum    export  extends final   
float   goto    implements  import  int interface   
long    native  package private protected   public  
short   static  super   synchronized    throws  
transient   volatile    let yeid

数据类型

ECMAScript 中有5种简单数据类型(也称为基本数据类型):Undefined、Null、Boolean、Number和String。

还有一种复杂数据类型——Object。

鉴于 ECMAScript 是松散类型的,因此需要有一种手段来检测给定变量的数据类型—— typeof 就是负责提供这方面信息的操作符。

null == undefined
// true

永远不要测试某个特定的浮点数值。

ECMAScript 能够表示的最小值保存在 Number.MIN_VALUE 中——在大多数浏览器中,这个值是5e-324;能表示的最大数值保存在 Number.MAX_VALUE 中。超出此范围使用 Infinity-Infinity 值。使用 isFinite() 函数判断是否在此范围内。

NaN 本身有两个非同寻常的特点。首先,任何涉及 NaN 的操作都会返回 NaN,这个特点在多步计算中有可能导致问题。其次,NaN 与任何值都不相等,包括 NaN 本身。使用 isNaN() 函数。

parseInt()/parseFloat()parseFloat(),十六进制格式的字符串则始终会被转换为0。

ECMAScript 中的字符串是不可变的,也就是说,字符串一旦被创建,它们的值就不能改变。

Object 的每个实例都具有下列属性和方法:ConstructorhasOwnProperty(propertyName)isPrototypeOf(object)propertyIsEnumerable(propertyName)toLocaleString()toString()valueOf()

ECMA-262 不负责定义宿主对象,因此宿主对象可能会也可能不会继承 Object

操作符

逗号操作符还可以用于赋值。在用于赋值时,逗号操作符总会返回表达式中的最后一项。

// num的值为0
var num = (5, 1, 4, 8, 0);

语句

for-in 语句是一种精准的迭代语句,可以用来枚举对象的属性。以下是for-in 语句的语法:

for (property in expression) statement

for (var propName in window) {     
    document.write(propName);
}

with 语句的作用是将代码的作用域设置到一个特定的对象中。with 语句的语法如下:

with (expression) statement;

with(location){    
    var qs = search.substring(1);    
    var hostName = hostname;    
    var url = href;
}

可以在 switch 语句中使用任何数据类型(在很多其他语言中只能使用数值),无论是字符串,还是对象都没有问题。其次,每个 case 的值不一定是常量,可以是变量,甚至是表达式。

switch 语句在比较值时使用的是全等操作符,因此不会发生类型转换。

函数

关于 arguments 的行为,还有一点比较有意思。那就是它的值永远与对应命名参数的值保持同步。不过,这并不是说读取这两个值会访问相同的内存空间;它们的内存空间是独立的,但它们的值会同步。但这种影响是单向的:修改arguments[0]的值会自动同步到命名参数中;而修改命名参数不会改变 arguments 中对应的值。另外还要记住,如果只传入了一个参数,那么为 arguments[1] 设置的值不会反应到命名参数中。这是因为 arguments 对象的长度是由传入的参数个数决定的,不是由定义函数时的命名参数的个数决定的。

ECMAScript 中的所有参数传递的都是值,不可能通过引用传递参数。

JavaScript 高程设计——在HTML中使用 JavaScript

<script> 元素

HTML 4.01 为 <script> 定义了以下6个属性:

  • async:可选。表示应该立即下载脚本,但不应妨碍页面中的其他操作。只对外部脚本文件有效;
  • charset:可选。表示通过 src 属性指定的代码的字符集。很少有人用这个属性;
  • defer:可选。表示脚本可以延迟到文档完全被解析和显示之后再执行;
  • language:已废弃;
  • src:可选。表示包含要执行代码的外部文件;
  • type:可选。表示编写代码使用的脚本语言的内容类型;目前 type 属性的值依旧还是 text/javascript。不过这个属性并不是必须的,因为没有指定时默认值也是 text/javascript

在使用 <script> 元素嵌入 JavaScript 代码时,不要在代码中的任何地方出现 "</script>"字符串。按照接卸嵌入式代码的规则,当浏览器遇到字符串 "</script>" 时,就会认为是结束的 </script>标签。而通过转义字符 “\” 可解决这个问题:"<\/script>"。例如:

<script>
    function sayScript(){
        alert("<\/script>");
    }
</script>

在引入外部文件时,只能像下面这样写:

<script type="application/javascript" src="example.js"></script>

而不能使用如下形式:

<script type="application/javascript" src="example.js"/>

引入了外部文件的 <script> 元素不能再嵌入代码,嵌入的代码将会被忽略,只外部文件起作用。

无论如何包含代码,只要不存在 deferasync 属性,浏览器都会按照 <script> 元素在页面中出现的先后顺序对它们依次进行解析。

标签的位置

现代 Web 应用程序一般都把全部的 JavaScript 引用放在 元素中页面内容的后面。如下:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title></title>
</head>
<body>


<script src="example.js"></script>
</body>
</html>

延迟脚本

使用 defer 属性,相当于告诉浏览器立即下载,但延迟执行。IE4/Firefox3.5/Safari5/Chrome 是最早支持此属性的浏览器,其他浏览器会忽略此属性,因此把延迟脚本放在页面底部仍然是最佳选择。使用例子:

<script defer src="example.js"></script>

异步脚本

HTML 5 为 <script> 元素定义了 async 属性。这个属性与 defer 属性类似,都用来改变处理脚本的行为;但与 defer 属性不同的是,标记为 async 脚本并不保证按照指定它们的先后顺序执行。指定 async 属性的目的不是让页面等待两个脚本下载和执行,从而异步加载页面其他内容。因此,建议异步脚本不要在加载期间修改 DOM。异步脚本一定会在页面的 load 事件前执行。

<script async src="example.js"></script>

文档模式

IE 5.5 引入了文档模式的概念,而这个概念是通过使用文档类型(doctype)切换实现的。最初的两种文档模式是:混杂模式(quirks mode)和标准模式(standards mode)。这两种模式主要影响 CSS 内容的呈现,但在某些情况下也会影响 JavaScript 的解释执行。在此之后,IE 又提出一种所谓的准标准模式(almost standards mode)。

如果文档开始处没有发现文档类型声明,则所有的浏览器都会默认开启混杂模式;对于标准模式可以通过使用下面任何一种文档类型来开启:

<!-- HTML 4.0.1 严格型 html:4s -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<!-- HTML 5 !-->
<!DOCTYPE html>

而对于准标准模式,则可以使用过渡型(transitional)或框架集型(frameset)文档类型来触发:

<!-- HTML 4.0.1 过渡型 html:4t -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
    "http://www.w3.org/TR/html4/loose.dtd">
<!-- HTML 4.0.1 框架集型 -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" 
    "http://www.w3.org/TR/html4/frameset.dtd">

<noscript> 元素

<body> 中使用此元素。

一次让我瞬间长大的经历

原文链接

今天,我突然对人情世故、世态炎凉有了非常深刻的理解。一瞬间,觉得自己成长了许多。

一直有关注我博客的人都应该知道,因为长期编码导致我的身体吃不消了。去年年底从国企离职后就没有上班了,虽然靠着一些投资和理财收入能够勉强维持生活,但是在家还是闷得慌,得找份工作有点事做,有点人际交流。但是又不太想从事繁重的编码工作,正好政府在招聘编制人员,有比较适合我的职位(与计算机相关的,具体就不指明部门和职位了),于是我就屁颠屁颠地报考了。毕竟政府编制内的工作,轻松待遇又好,还有时间业余继续摆弄自己的项目,何乐而不为呢。

凭借着扎实的计算机功底,再稍微看看公共基础题,哥笔试从数十号报考同一职位的人中杀了出来,拿了笔试第一名,比第二名还高了 6 分多。

后来就收到面试通知,面试是结构化面试,然后就看了一些相关的书,自我感觉相当良好。今天早上面试,有另外两个人跟我竞争同一职位,就叫他们A和B吧。最初先和A交谈,A说他昨晚才收到面试通知,什么都没准备,就是裸考。然后他是粤西那边的人,带着浓重的粤西腔调,“是不是”都说成“系不系”那种水平的普通话。哥一听,稳了。B是个笑面郎君,是个湖南人,做手机游戏的。暂时不知道他什么水平。反正就一路都是笑眯眯的。

然后就开始面试了,我是这个报考职位最后一个面试。题目类型完全猜中,就是综合分析+组织管理+应急应变,哥对答如流,有首有尾,中间饱满,层次分明,逻辑清晰。走出面试考室时,哥心里想,这次真的稳了。

考完后,我走到侯分室,A和B都在那里,我问B考得怎么样,他说答了 10 分钟都没有,慌张得要命。确认这一点之后,哥心里笑了,这次真的真的稳了。于是便淡定地跟别人聊天,等待分数的公布。

等了大概二十多分钟吧,一个工作人员拿着我报考职位的分数单走过来,贴了在墙上。我信心满满地一看,但是却傻眼了…… 我面试竟然最低分,只有 75 分。而那个裸考的A是 76 分,笑面郎君B竟然高达 89 分。哥心里那个震惊,怎么成绩会这样?我自认为答得这么好,为什么成绩会比那个连普通话都说不好并且裸考的的A要低?一个好职位就这样擦肩而过了,虽然心里很不甘,但也没办法,灰溜溜地先回家了。

正好,我有个朋友跟我报考的那个局里有点门道,我觉得有点死不瞑目,于是就拜托他去打听一下。具体过程就不表了,最终真相就是,我的面试成绩被打压了。因为那个笑面郎君B正好是背后各种关系需要照顾的人,而我笔试成绩太高了,只能通过打压我的面试成绩,并且要保证,B的分数要稍微拉开我和A一定的距离,所以B的面试分数必须要那么高,而我的面试分数则必须要打压到这么低。

呵呵。

可笑,突然明白这面试其实就是形同虚设,连分数都是可以随意设置的。现在想起笑面郎君B的那副笑脸,俨然就是:呵呵,你们这两个陪考员,铁定进不去的还来跟我抢,白费工夫吧。

其实我一直都没考过这种试,也第一次参加结构化面试。但是这次我总算是懂了,面试成绩为什么要占 50%,甚至 60%,其实都是别有用心的。

哥总算经历一回了,也算死得瞑目了。所以在这里奉劝大家一句,如果你还有报考公务员、事业单位的想法,除非有好运气,除非有好背景,否则最好都别想了。那些好职位大部分应该都有预订的。顺别说一句,我那个职位,中级技术职称就能拿到大约副镇长级别的工资待遇,而且蛮轻松的。我还是太年轻了,这样的职位不早被人觊觎就怪了。

人情世故、世态炎凉。现实就是这样,我们的社会就是这样。记得大学时,一位教授跟我讲过这么一句话:

看着自己有什么样的资源,利用好这些资源就好了。不要看着别人的资源流口水,比如说不要看到人家公务员的待遇多好,你就去报考公务员。能够做官的,很快就能做官,因为别人有做官的资源。

我突然懂了。

这个社会里,没有拥有社会资源的,只能苦逼地每天辛辛苦苦地打工赚那几个辛苦钱。而有社会资源的,则在轻松的岗位上,拿着丰厚的待遇。前者就是我们通常所说的屌丝,社会中大部分没有关系、没有背景的普通劳动百姓。比如我。屌丝们只能在那些“某些群体”不屑一顾的蛋糕屑里,辛辛苦苦地劳动以分一杯羹。某些屌丝想进入“某些群体”的圈子,这个是在太难太难了,更多时候只是当当陪考员而已。

突然想起在斗鱼直播的 sol 君,他说过这么一句话:

“我做斗鱼主播的这几年可能会是我一生中最赚钱的几年了,我也不知道以后这个行业会怎么样。我不会像有些主播一样挂支付宝让大家打钱,我不会问你们要钱的,缺钱了我就去帮别人打点广告。等以后如果不做主播了,我就去卖震动棒(PS:有玩笑性质)”

sol 说,他家里只有两个房间,剩下一个房间给妹妹住了,所以他爸给他在天台搭了个房间,也就是他直播的地方。他和他家人的关系都比较一般,做主播赚的钱都要留下,希望可以买个屋子搬出去接亲生妈妈回来一起住。

这就是我们普通人的生活,普通人的梦想。未来大家都不知道,踏踏实实走好现在脚下的每一步吧。

所以,假如你没有背景、没有关系,那么只有也只能完全依靠自己了。看看自己的天赋在哪,优势在哪,资源在哪,发挥好自己的资源优势,才能够在这个人情世故的社会中活得更好一些,也能让自己爱的人活得更舒服些。

共勉吧。