引用类型

在之前学习了变量及作用域,变量的值要么是基础类型值,要么是引用类型值。基础类型已经知道了,引用类型其实也是一些内建的数据类型,只是表现形式和行为不同。

每一个具体的引用类型的值(对象),也就是能够被赋给变量的值,都是一个引用类型的实例。由于JS的面向对象与传统语言不同,这里还是用对象,实例,引用类型来区分。对象就是实例,都是引用类型的实例。

在使用这些引用类型的时候,直接用对应的类型关键字就可以新建或者使用类型,不像Python使用某些类型的对象需要导入模块。

Object 类型

Object类型是使用最广泛的类型。

创建Object 类型的方法:

  1. var person = new Object(); person.name = "Nicholas"
  2. var person ={name:"Nicholas,age:29}

JS的对象初看有点像Python中的字典,不过二者还是有所不同,JS中定义对象,键名并不需要加引号,但依然是字符串类型。加上引号也可以。
新建对象如果花括号内留空,则会建立一个只包含内置属性的对象。
第二种方法不会调用构造函数Object()。

访问对象的方法:

  1. 键名加方括号,例如person["name"],键名可以是变量或者表达式
  2. 对象名.键名,这种方式不能使用表达式,没有第一种灵活。但大多数情况下是确定知道属性名称的,用该方法更易读,所以推荐使用这种方法

Array 类型

Array基本是除了Object之外使用最广泛的类型。与Python的列表类似,可以存储各种类型的数据。一般称其为数组类型。

创建数组类型的方法:

  1. var colors = new Array()
  2. var colors = ["red", "blue", "green"]

第一种方式还可以传参数,有如下几种效果:

  1. 传正整数表示数组长度
  2. 传多个项表示数组的项,比如var colors = new Array("red", "blue", "green")
  3. 传一个项目表示建立只包含那个项目的数组,如 var name = new Array("Greyson")
  4. new可以省略,如 var colors = Array()

第二种方法并不会调用构造函数Array()

访问数组的元素就像Python的列表一样用从0开始的索引访问。数组除了传递数值之外,也被当做一种常用的处理对象,有一系列内置属性与方法:

属性或方法名称 解释
.length 数组的项数保存在length属性内。length属性不是只读的,可以被设置,小于当前长度则会截短,大于则会添加undefined项。在数组末尾添加元素的技法是 array[array.length]
= new_item
,这可以保证每次都是在末尾追加元素。
Array.isArray(obj) 注意,这个方法只能由内置的Array对象使用,用途是判断参数是否为一个数组即Array对象。
字符串方法 指的是所有对象都有的.toString(), .toLocaleString(), .valueOf()方法,正常情况下,调用每个元素的字符串方法,然后返回逗号分割的各个元素的字符串。
.join(sep) 与python用法是反过来的,将数组内的元素用sep连接起来,如果不指定sep,则默认用逗号。
.push(values) 该方法接收任意数量的参数,将其逐个添加到数组末尾,返回添加元素后数组的长度
.pop() 无参数,从数组中删除最后一个元素,并且返回该元素,比较常用。.push 和 .pop()模拟的是栈的行为,因此也叫栈方法
.shift() 无参数,删除数组第一项并且返回该项。也很常用。
.unshift(values) 接受任意数量参数,将参数逐个添加到数组的开头,并且返回添加后数组的长度。.shift() 配合 .push()模拟队列行为,.unshift() 配合 .pop()模拟反向队列,因此也叫队列方法。
.sort(sort_function) 就地将数组按升序排序,返回排序后的数组,而且也改变原数组,默认会将所有元素转换成字符串进行排序。可以接受一个比较函数,比较函数需要接收两个参数,如果第一个参数需要排在第二个参数之前,就返回负数,如果相等则返回0,如果第一个参数排在第二个参数后边,就返回正数。
.reverse() 用法和.sort()一样。
.concat(values) 如果不传参数,返回的是新的数组,值是原来数组的复制。如果传参数,会返回一个新的数组,值是原数组加上各个参数。该方法返回的是新数组,可以用来深拷贝数组。
.slice([index] [,index2]) 切片方法,第一个参数表示开始索引,不传第二个参数表示切到数组末尾,第二个参数表示结束的索引。注意,索引左闭右开。该方法返回的也是新数组。如果传入负数,很简单,用.length的值加上负数就可以确定索引的位置。
.splice(index,num,values)
    values可以是任意数量的参数,根据num是否为0和是否传values,方法的功能有变化:

  1. 如果num不为0,没有传values,表示要删除的第一项的索引和删除的项目数。返回被删除的部分。
  2. 如果num为0,传入了values,表示从index的位置插入values。返回一个空数组。
  3. 如果num不为0,也传了values,表示从index开始删除num项数据,然后将删除的部分替换成values。返回被删除的部分
.indexOf(item [,index]) 返回查找的项在数组中的位置(索引),其中第二个参数表示开始的索引,可以不传,表示从头开始。判断相等是采用全等。返回的都是数组的绝对索引。找不到则返回-1。
.lastIndexOf(item [,index]) 从数组的末尾开始查找,找不到则返回-1,其他与.index()方法一样。

Array 高阶方法:迭代方法和归并方法

Array里还有类似Python的内置map,reduce等方法一样的,可以用一个函数作为参数,映射到每个数组元素的方法。其中分为迭代方法和归并方法。

迭代方法:

  1. .every(func):对数组内每一项运行函数,如果全部为true,则返回true
  2. .filter(func):对数组内每一项运行函数,只返回运行结果为true的项组成的数组
  3. .forEach(func):对数组内每一项运行指定函数,无返回值
  4. .map(func):对数组内每一项运行指定函数,返回每次函数运行的结果组成的数组
  5. .some(func):对数组内每一项运行指定函数,只要至少有一项结果为true,就返回true

这五个方法都需要接受一个函数作为参数,这个函数必须有三个参数,依次是数组的值,索引,数组对象本身。例如:

    var numbers = [1,2,3,4,5,4,3,2,1];
    var filterResult = numbers.filter(function(item,index,array){ return (item>3)});

这五个方法还有可选的第二个参数,暂且无需学习。

归并方法:

  1. .reduce(func,basevalue):从数组索引0开始执行归并
  2. .reduceRight(func,basevalue):从数组的最大索引开始执行归并

归并方法指的是用一个函数从最左或者最右的两项开始,逐个使用传入的函数,函数的结果再与下一个元素进行归并,直到执行完整个数组。

传入的函数必须接受四个参数,依次是:上一个元素,当前元素,索引,数组对象本身。例如:

    var numbers = [1,2,3,4,5,6,10,11,12,13];
    function myfunc(num1,num2,index,array){return num1+num2};
    total = numbers.reduce(myfunc)

Date 类型

Date 类型保存的是基于1970年 1月 1日 的毫秒数。

创建Date 类型的方法:

  1. var now = new Date()
  2. var date = new Date("May 25,2004")
  3. var date = new Date(12098321908)
  4. var date = new Date(Date.UTC(2018,5,21))
  1. 第一种方法不加任何参数,会得到生成该对象的时间对象
  2. 传入字符串,内部会调用 Date.parse() 产生一个毫秒数,然后再传给Date产生对象
  3. 直接传入数值,表示毫秒数,会从1970年1月1日开始计算
  4. 传入年,月,日,小时,分钟,秒,毫秒。年和月必须传,其他的可以不传。其中月份的索引开始于0,其他正常。.UTC()方法返回一个数值毫秒数。

Date的字符串方法用到的时候可以再学习,在页面显示的话常用.toLocaleString()

RegExp 类型

RegExp 是 JS 内部的正则表达式类型,就像Python里的RE模块一样,只是JS里都无需导入什么模块,直接用关键字使用。

创建RegExp 类型的方法:

  1. var rePattern = / pattern / flags
  2. var rePattern = new RegExp("[bc]cat", "i")
  1. 第一种方法pattern就是正则表达式,不需要用引号括起来,但是一样需要转义,而且JS里没有raw格式字符串,就需要双重转义.flags的三个标志为: g 全局模式,i 不区分大小写模式,m 表示多行模式
  2. 用RegExp的时候传入正则字符串及flag字符串作为参数。
方法或属性 解释
.test(string) 用于测试是否匹配,如果匹配成功返回true,匹配失败返回false,最常用
.exec(string) 按照正则表达式里边的分组进行捕获组,返回一个数组,第一项是匹配的整个字符串,第二项是捕获的第一个组,以此类推

正则对象的其他用法可以接触到再学习。前端经常用JS来验证用户输入,当用户输入都满足条件的时候,再执行其他动作比如向后端发送数据。Web开发中后端验证是一定需要的,前端验证最好也要做。

Function 类型

Function 类型是最常使用到的也是最重要的类型。就一切皆对象来说,Function类型也没有什么特别的,每一个函数都是Function 类型的实例,也有自己的方法和属性。函数名就是一个指向函数对象的指针。

创建Function 类型的方法:

  1. function sum (num1, num2) {return sum1 + sum2}
  2. var sum = new Function("num1","num2","return sum1 + sum2")

第一个方法叫做函数声明 ,第二个方法叫做函数表达式。两种方法里绝对不推荐使用第二种方法。第二种的构造函数Function接受任意数量的字符串参数,最后一个字符串作为函数体,之前的所有变量都作为参数标识符。

函数名称是指针,所以可以用任意变量去指代,可以多个函数名指向同一个函数,加上括号都可以调用同一个。

函数表达式只有在执行到该表达式的时候才会将其解释并把函数添加到当前环境中,但函数声明一开始就会被解释器读取,所以函数声明可以写在需要使用该函数的代码段之后,而函数表达式不可以,所以不要使用函数表达式。

由于函数名是指针,可以被赋值给变量,所以函数也可以作为值来使用和传递。和Python还有很多语言的高阶函数应用是一样的。

函数的属性和方法

函数有两个特殊的对象需要先说:

  1. arguments:类数组对象,保存了所有传入函数的参数,与函数声明里的参数无关。arguments对象有一个叫callee的属性,是一个指针,指向拥有这个arguments的函数。所以在函数中调用自身的时候,可以使用这个方法,还避免了在函数内部将调用的函数名写死的问题,比如阶乘的方法:
    function fact(num) {
        if(num<1){
            return 1
        }else {
            return num*arguments.callee(num-1);
        }
    }
  2. this:表示函数执行环境的对象。当函数被哪个环境调用,this就代表那个环境。看一个调用的示例:
    window.color = "red in window";
    function showcolor(color) {
        console.log("color is "+ this.color);
    }
    obj1 = {"color":"blue in obj1"};
    obj1.showcolor = showcolor;
    showcolor(color);
    obj1.showcolor();

    可见函数执行在window环境中this就代表window对象,作为obj1对象的方法执行的时候,其环境就是obj1,this.color 代表 ob1.color属性。函数两次运行,指向都是同一个函数对象(内存地址)。

    在严格模式下无法使用arguments.callee属性。不过即使写死函数名称,也没有问题,一样可以赋给其他函数然后执行递归。

再来看看其他方法

方法或属性 解释
.length 返回函数声明中定义的参数个数
.prototype 其实就相当于Python里函数类的基类,内置的方法都保存在这里。
.apply(field,array) field表示作用域,实际上相当于设置this的值。array是参数数组。这个方法就是在field内调用函数。
.call(field, arguments) 与.apply()不同的是参数arguments必须逐个传递,不传数组。其他都相同。
.bind(object) 将函数绑定到某个作用域(对象)上,再调用该函数,其作用域固定为绑定对象。

关于.apply() .call 和 .bind() 的区别,可以参考Python里对象绑定的方法和静态方法来理解。绑定的方法在使用的时候就已经限定好了作用域,而不绑定的方法则随意使用了,与具体对象没有关系,执行了才知道具体对象是谁。

基本包装类型

在最开始已经说了,基本数据类型不属于引用类型,因为基本数据类型的赋值方法与引用类型不同。但是对于 Boolean,Number 和 String来说,在实际创建这些类型的对象时候,会发现这些类型依然有属性和方法,这是因为在读取基本类型数据的时候,后台会自动创建一个基本类型数据的包装实例,然后再调用这个实例的方法,得到结果之后销毁这个实例。

可以发现,引用类型和包装类型的生存期不同,引用类型建立之后,只要不销毁,就一直有效,而基本包装对象在返回结果之后就不存在了,通过变量声明并赋值无法直接得到包装类型实例。在运行的时候无法对基本包装类型动态添加属性,因为那一行执行完毕之后,对象已经销毁,下一次执行又是一个新的包装对象产生。

创建基本包装对象:

  • var str1 = new Object("test string")

将基本类型数据传给Object构造函数,显式创建了基本包装类型实例。但是此时的str1不是一个字符串类型,而是Object类型,会带来很多麻烦,因此绝对不推荐直接创建基本包装类型。因为我们主要利用基本包装类型的方法去处理基本类型数据,而不是为了使用基本包装类型本身。在提到字符串,数值的方法的时候,需要知道其实说的是包装对象的方法。

Boolean 包装对象

创建Boolean 包装对象:

  • var boolObj = new Boolean(true or false)

绝对不推荐使用Boolean包装对象,因为用Boolean对象去进行布尔判断,由于这是一个对象,而不是布尔值,因此结果永远为true,因此布尔值必须直接使用变量声明和赋值。

Number 包装对象

创建Number 包装对象:

  • var numberObj = new Number(num)

基于和布尔包装对象同样的原因,也不要直接使用Number包装对象:比如用0生成Number包装对象,布尔判断的时候依然为true。

对于Number类型来说,主要的方法是输出字符串:

方法或属性 解释
.toFixed(num) 按照指定的小数位数返回数值的字符串表示。num表示小数位数,常用来统一格式化输出。
.toExponential(num) 以指数表示法输出数值的字符串格式,num表示小数位数,常用在科学表示法上。

String 包装对象

创建String 包装对象:

  • var stringObj = new String(string)

和其他基本包装类型同样的原因,用空串生成的String包装对象与空字符串在判断逻辑时不同,所以依然要直接使用字符串。

字符串类型的方法:

方法或属性 解释
.length 返回字符串内字符的长度,一个Unicode字符算1。
.charAt(index) 返回指定的索引的字符,JS中没有字符类型,返回的是只有一个字符的字符串类型。
.charCodeAt(index) 返回指定的索引的字符编码。
[index] 像访问数组元素一样访问字符串内的字符。
.concat(strings) 将参数拼接到调用方法的字符串上,但不改变原来字符串,必须用新变量接收结果。实践中用+操作符,不太使用该方法。
.slice(start_index,end_index) 与数组切片类似,返回切片的子字符串。如果不传第二个参数 ,默认为字符串长度。不改变原来字符串的值。
.substring(start_index,end_index) 与.slice方法相同。
.substr(start_index,num) 从指定的索引开始返回num个字符,不指定num则默认为字符串长度。不改变原来字符串的值。
.indexOf(substr) 在字符串中查找子串,返回索引,找不到则返回-1
.lastIndexOf(substr) 从末尾反向查找。
.trim() 去掉字符串开头和末尾的空格,然后返回新字符串。不修改原来字符串。
.toLowerCase() 将字符串转换为小写,不改变原字符串。还有一个类似的是.toLocaleLowerCase(),用于某些特殊语言
.toUpperCase() 将字符串转换为大写,不改变原字符串。还有一个类似的是.toLocaleUpperCase(),用于某些特殊语言
.match(RegExp) 该方法只接受一个参数,为正则表达式,结果和正则的.exec方法相同,返回数组。
.search(RegExp) 该方法只接受一个参数。用正则从头开始匹配字符串,返回匹配成功的第一个结果,如果没找到则返回-1。每次调用该方法都从对象字符串的开始处查找。
.replace(RegExp or string,expression) 替换字符串,第一个参数是正则表达式或一个字符串,第二个参数是表达式,表达式的值必须为字符串。注意,如果第一个参数是字符串,只会替换第一个;想要全部替换,第一个参数必须是指定了flag为g的RegExp对象。第二个参数如果是字符串,还可以用使用特殊字符序列。
.split(RegExp or sep [,length]) 第一个参数可以是正则或者字符串作为分隔符,用分隔符将字符串分割开,返回一个数组保存了所有分割的部分。length用来指定数组的长度,可以不传,默认返回全部结果构成的数组。
.localeCompare(string) 比较对象字符串和参数字符串,如果对象字符串排在参数字符串之前,返回-1,如果相等返回0,排在之后则返回1。
String.fromCharCode(values) 注意,这个是String类的静态方法(还记得之前说过Python的静态方法吗),用于接收一系列字符编码(数值),将其转换成字符串。

Global 对象

Global就是全局对象,所有不属于其他对象的方法和属性,都归到Global对象上。所以那些内置函数以及数据类型的构造方法其实都是Global对象的方法,比如isNaN(),isInfinite(),parseInt(),Object,Function等。除了这些属性和方法之外,Global对象还有一些方法:

Global 对象的方法

方法名 解释
.encodeURI(URI) 对通用资源标识符进行编码,以便发送给浏览器。该方法不会对所有的特殊字符进行编码。
.encodeURIComponent(URI) 与上一个方法的不同点是会对所有的非字母,数字和下划线字符进行转义。
.decodeURI(URI) 解码URI,只能用于.encodeURI(URI)生成的字符串。
.decodeURIComponent(URI) 解码URI,只能用于.encodeURIComponent(URI)生成的字符串。
.eval(string) eval就像一个JS解释器,对传入其中的字符串形式的JS语句进行解析并执行。其具体实现方式是遇到eval就会在当地实际替换成之后的语句执行,所以eval所在的环境也就是其中字符串代码执行的环境。严格模式下外部无法访问eval内部的变量和函数。正常编程中,不要使用eval方法。

这些Global 对象的方法,虽然在之前加了.表示是Global对象的方法,但使用起来就和内置函数一样,直接写方法名即可。

关于URI的方法可以看一个浏览器里直接执行的简单的例子:

a = encodeURI("http://conyli.cc/<test>")
"http://conyli.cc/%3Ctest%3E"
b = encodeURIComponent("http://conyli.cc/<test>")
"http%3A%2F%2Fconyli.cc%2F%3Ctest%3E"

Global 对象的属性

特殊的值,原生的数据类型的构造函数等全部都是Global对象的属性:

undefined NaN Infinity Object Array Function
Boolean String Number Date RegExp Error
EvalError RangeError ReferenceError SyntaxError TypeError URIError

后边的Error都是构造函数产生的Error类型。这些属性都不要进行赋值等操作,会引发错误。

window 对象

ECMA标准没有指出具体如何访问Global对象,在Web浏览器里,一般都将Global对象当做window对象的一部分加以实现,即上边的所有属性和方法,是window对象的属性和方法。在全局作用域里声明的方法和属性,也会成为window对象的方法和属性。

window对象除了承担了Global的职责以外,还有其他的职能,会在JS操作DOM与BOM的部分再学习。

取得Global对象的一种方法:

var color = "red";

var glo = function () {
    return this;
}()

在全局环境里执行该方法,就会得到全局变量。查看属性可以发现glo对象具有color属性,说明确实得到了Global 对象。同时执行window.color,也得到了”red”结果,再执行 glo === window,结果是true。

Math 对象

JS为数学计算和公式提供一个公共的对象叫做Math。

Math的属性和方法遇到的时候可以再查,经常使用的方法是:

方法名 解释
Math.min(values) 返回一系列参数的最小值
Math.max(values) 返回一系列参数的最大值
Math.ceil(value) 将一个数值向上取整。
Math.floor(value) 将一个数值向下取整
Math.round(value) 四舍五入为最近的整数
Math.random() 返回0-1之间的一个随机数

小技巧:找数组中的最大最小值,利用.apply()方法传入数组参数:

let array = [1,2,3,4,5,6];
let min_array = Math.min.apply(Math, array);
console.log(min_array)

小技巧2:自行编写类似Python的 random.randint或random.choices 函数:

function selectFrom(lowerValue, upperValue) {
    let choices = upperValue - lowerValue +1;
    return Math.floor(Math.random()*choices + lowerValue);
}