精读JavaScript模式(四),数组,对象与函数的几种创建方式
一、前言
放了个元旦,休息了三天,加上春运抢票一系列事情的冲击,我感觉我的心已经飞了。确实应该收收心,之前计划的学习任务也严重脱节了;我恨不得打死我自己。
在上篇博客中,笔记记录到了关于构造函数方面的一些写法和用法,这篇博客,会从原书中数组直接量开始,自己读了下之前的博客,还是有点照搬概念的意思,想了下,还是得简化概念,按照自己的思路去写,那么开始。
二、数组直接量
概要:创建数组的两种方式,Array()创建的隐性问题
在JS中,数组也是对象,我们可以通过内置构造函数Array()创建数组,也可以通过直接量方式创建。
使用Array()创建:
var arr = new Array('echo', '时间跳跃', "听风是风");
数组直接量创建:
var arr = ['echo', '时间跳跃', "听风是风"];
很明显,直接量的写法更为简单,所以在实际开发中,对于创建数组,是绝对推荐直接量写法。
为什么不推荐new Array()的写法呢,因为当我们为Array构造器传入一个数字,这个数字不会成为数组的元素,而是成了设置数组的length。
var arr = new Array(3); console.dir(arr[0]);//undefined
我们知道,数组的length是不存在浮点数的,当我们为Array()中设置一个浮点数时会直接导致错误。
var arr = new Array(3.14);//报错
三、判断是不是数组
概括:判断是不是数组的几种方式
判断是不是数组,是不是对象,首先想到的是typeof,但是很遗憾,typeof的判断较为粗糙,得到的只是object的结果。
var arr = [1,2,3]; typeof arr;//object
在ES5中提供了一个好用的数组检验方法Array.isArray(),它返回一个布尔值,如果你检验的对象是一个数组,它将返回true。
var arr = [1,2,3]; Array.isArray(arr);//true
但如果你的开发环境不支持ES5,最保险的做法就是使用Object.prototype.toString()方法来达到检验目的。
var arr = [1,2,3]; Object.prototype.toString.call(arr) === '[object Array]'//true
四、正则表达式直接量
概括:正则创建的2种方式,使用RegExp构造函数创建的情景
在js中正则表达式也是对象,可以通过正则直接量与RegExp()构造函数两种方式创建:
创建正则匹配一个字母a,使用直接量:
var re = /a/gm
使用构造函数创建:
var re = new RegExp('a','gm');
与构造函数写法相比,正则表达式直接量使用两个斜线包裹起来,正则匹配的主体部分不包括两端的斜线,修饰符gm不需要使用引号。
假设我们需要匹配一个斜线\,两者写法区别如下:
var re = /\\/gm //构造函数 var re = new RegExp('\\\\','gm');
在直接量中我们使用\对\进行转译,而在构造函数写法中,转译两次的行为导致使用了四个\,很明显对于阅读来说增加了复杂性。
js中提供了三个修饰符,且修饰符顺序随意:
g:全局匹配
m:多行匹配
i:忽略大小写
虽然推荐直接量的方式创建正则,但如果你创建的正则包含变量,则不得不使用构造函数创建。
const str = "abc"; const re = new RegExp(str, 'gm');
比较有趣的是,在使用构造函数创建正则时,不带new调用RegExp()和带new调用的结果是完全一样的。这与我在上篇文章中说不带new调用对象指向window有些不同。
其实写到这里,原书中第三章基本介绍完了,第四章开始介绍函数,这一章主要介绍函数几种声明方式,函数提前,以及一些模式,性能优化等。上面篇幅感觉有点少,顺带把第四章记录一点。
五、函数基础概念
概括:函数的一些特性
JS中的函数也是对象,且函数有两个主要的特性,第一,函数是一等对象(一等公民),第二是函数提供作用域支持。
函数的第一特性函数是对象,所以我们可以对函数做以下操作
1.在程序执行时动态创建函数。
2.可以将函数作为值赋予给一个变量,可以将函数的引用拷贝到另一个变量。
3.可以将函数作为参数传入到另一函数,也能作为函数返回值。
3.函数可以拥有自己的属性和方法。
当我们通过构造函数的形式来创建一个函数,很明显,这个函数属于构造器的一个实例,属于一个对象。
const func1 = new Function('a', 'b', 'return a+b'); func1(1,2);//3
这里func1就是一个对象,但和前面创建各类对象,字符串,正则一样,并不推荐构造器的形式创建,一是可读性差,其次执行代码块return a + b本质上属于一段字符串,在执行时也做了类似于eval()的操作。
函数的第二个特性是可以提供作用域,js中原本是没有块级作用域概念的(现在let可以声明块级作用域),也就是不能通过花括号来创建作用域,而创建一个函数,则会生成一个独立的作用域。
在函数体内凡是通过var声明的变量(let const都一样,毕竟书有点老)相对函数而言,都是局部作用域,在函数外是不可见的。
需要注意的是,虽然说花括号提供作用域,但是对于if while for这一类的花括号,在里面通过var创建的变量也不是局部作用域,除非外层有函数,否则就是一个全局变量。(有个疑问,如果if里面使用let const声明,外面依旧读不到,我暂时没了解,查阅了补全此问题)
if(true){ var a = 1 }; console.log(a);//1 function func1() { var b = 1 }; console.log(b);//报错
六、函数术语与函数的几种创建方式
概括:什么是函数表达式,什么是函数声明?函数name属性与函数声明提前
我们可以通过构造函数,函数表达式,函数声明三种形式来创建一个函数。构造函数的创建方式在上面已有举例,记住,不推荐这种写法。
通过函数表达式创建函数:
//带有命名的函数表达式 var func1 = function func(a, b){ return a +b; }; func1(1,2)//3 func1.name;//func //匿名函数表达式 var func2 = function (a, b){ return a +b; }; func2(1,2); func2.name;//func2
上述代码中函数一的name属性为func,但是调用它使用的是func1(),函数二虽然匿名,但依然可以找到name属性,是我们赋予的变量名func2。
两种函数作用完全相同,调用均为使用函数赋予的变量名进行调用,我个人在表达式创建上常用匿名表达式的写法。
通过函数声明创建函数:
function add(a, b){ return a + b; }; add(1,2)//3
可以说函数声明与带有命名的函数表达式的区别就是有没有将这个函数赋予给一个变量。
我们在前面的创建对象,字符串,正则时都有提到一个直接量创建的概念,其实函数也有'函数直接量的说法',它对应的就是函数表达式,但这个术语有歧义的,所以不推荐这么称呼它;但如果有人说函数直接量,你还是得知道对方说的是函数表达式的创建方式,这里顺带提一下。
函数声明提前:
关于函数声明与函数表达式的使用取舍,一般推荐在全局,或者函数体内创建函数时,使用函数声明的写法;因为函数声明是存在函数提升的,在全局作用域创建后,你可以在任意一个地方调用它。
同样全局作用域,使用函数表达式创建的函数,你只能在创建之后调用,因为函数表达式提升是被赋予的变量,函数赋值的操作停留在原地,所以在创建前调用函数会报错,这是也两者的区别所在。
//函数声明,创建一次,随处可用 add(1,2);//3 function add(a, b){ return a + b; }; add(1,2);//3 //函数表达式,只能在创建后使用 func(a,b);//报错 var func = function (a, b){ return a + b; }; func(1,2);//3 //上述代码等同于 var func; func(a,b)//此时func并不是一个函数,报错。 func = function (a, b){ return a + b; }; func(1,2);//3
函数name属性:
原书中提到,匿名函数表达式的name属性在火狐,safari中为'',也就是没有赋予具体的值,在IE中是无定义的。我本人测试name属性是被赋予的变量名,我的浏览器是谷歌,可能是因为浏览器版本的不同,对此差异简单提一下。
最后强调一下JS函数两大特性,函数是对象,函数提供局部变量作用域。
先记到这里吧。下一篇将从函数回调模式开始说起。