Javascript-引用类型

一、Object类型

1、创建Object实例的两种方法:

  • 构造函数法
var person = new Object();
person.name = "Tom";
person.age = 21;
  • 对象字面量表示法
var person = {
  name:"Tom",
  age:21
}

注意事项:
1、在使用对象字面量语法时,属性名也可以使用字符串,如下面这个例子所示。但这里的数值属性名会自动转换为字符串

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

2、使用对象字面量语法时,如果留空其花括号,则可以定义只包含默认属性和方法的对象,如下所示:

var person = {}; //  与new Object()相同
person.name = "Tom";
person.age = 23;

2、访问对象属性的方法

  • 点表示法
    person.name
    -方括号表示法
    要将要访问的属性以字符串的形式放到[]中,与点表示法[]的优势是可以通过变量访问属性
    person["name"]

二、Array类型

1、创建数组的基本方式

  • 构造函数法
    var colors = new Array();
  • 数组字面量表示法
    var color = []

2、数组的属性

  • length
    length可以获取和设置数组的长度

3、检测数组

instanceof
Array.isArray(value)

4、Array的方法

  • 转换方法

toString()这个方法返回的是把数组的每一项转换成字符串,然后用逗号隔开拼接的字符串。

var colors = ['red','blue','green'];
console.log(colors.toString()); // "red,blue,green"

toLocaleString() 调用的是每一项的toLocale-String()方法
join() 此方法返回的是以此方法接收的残守为分隔符拼接成的字符串。默认以逗号(,)为分隔符。

var colors = ['red','blue','green'];
console.log(colors.join(","));    //"red,blue,green"
console.log(colors.join("||"));  //"red||blue||green
  • 栈方法

栈是一种LIFO(Last-In-First-Out,后进先出)的数据结构,也就是最新添加的项最早被移除。而栈中项的插入(叫做推入)和移除(叫做弹出),只发生在一个位置——栈的顶部。

push() 接收任意数量的参数,把它们逐个添加到数组的末尾。返回值是修改后数组的长度
pop() 从数组末尾一处最后一项。返回值是移除的项

var colors = new Array();
var count = colors.push("red","green"); //推入两项
console.log(count);     //返回值是改变后数组的length:2
count = colors.push("black"); //推入一项
console.log(count); //3
var item = colors.pop();  //移除最后一项
console.log(item);  //返回值是一处的项:"black"
console.log(colors.length);    //2
  • 队列方法

队列数据结构的访问规则是FIFO(First-In-First-Out,先进先出)。队列在列表的末端添加项,从列表的前端移除项

shift() 从数组的前端移除一项,返回值是移除的项
unshift() 从数组的前端添加一项,返回值是新数组的长度

var colors = new Array();
var count = colors.push("red","green"); //推入两项
console.log(count);  //2
count = colors.push("balck"); //推入另一项
console.log(count);  //3
var item = colors.shift(); //取得第一项
console.log(item);  //"red"
console.log(colors.length); //2
  • 重排序方法

reverse() 反转数组项的顺序,返回值是经过排序后的数组

var values = [1,2,3,4,5];
var values2 = [1,2,'34','我'];
values.reverse();
console.log(values); //[5, 4, 3, 2, 1]
console.log(values2.reverse()); //["我", "34", 2, 1]

sort()返回值是经过排序后的数组
1、默认情况下,sort()方法按升序排列数组项——即最小的值位于最前面,最大的值排在最后面。为了实现排序,sort()方法会调用每个数组项的toString()转型方法,然后比较得到的字符串,以确定如何排序

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

2、sort()方法可以接收一个比较函数作为参数,以便我们指定哪个值位于哪个值的前面

function compare(value1, value2){
   if(value1 < value2){
       return -1;
   }else if(value1 > value2){
       return 1;
   }else{
       return 0;
   }
}
var values = [0,1,5,10,
values.sort(compare);
console.log(values);  //[0, 1, 5, 10, 15]
  • 操作方法

contanct() 此方法会先创建当前数组的一个副本,然后将接受到的参数添加到副本的末尾

var colors = ["red",'green','blue'];   ["red", "green", "blue"]
var colors2 = colors.concat("yellow",["black", "brown"]);  
console.log(colors);  // ["red", "green", "blue"]
console.log(colors2);  //["red", "green", "blue", "yellow", "black", "brown"]

slice()此方法能基于当前数组中的一个或者多个项创建一个新数组。且不会影响原始数组
当接受一个参数时:返回从参数指定位置到数组末尾的所有项
当接受两个参数时:返回起始和结束位置的项--但不包括结束位置的项。
注意:当splice()的参数有负数时,则用数组的长度加上该数来确定相应的位置 如数组长度是5,slice(-2,-1)和调用slice(3,4)结果相同。
splice(start[, deleteCount][, values])通过这个方法可以对数组的项进行删除、插入、替换。返回值是删除的项组成的数组
三个参数分别是起始位置、要删除的个数、要插入的值

var colors = ["red","green","blue"];
var removed = colors.splice(0,1); //删除第一项
console.log(colors); //["green","blue"]
console.log(removed);   //["red"]
removed = colors.splice(1,0,"yellow","orange"); 
console.log(colors);    //["green","yellow","orange","blue"]
console.log(removed);   //[]
removed = colors.splice(1,1,"red","purple"); //["green","red","purple","orange","blue"]
console.log(colors);
console.log(removed); //["yellow"]
  • 位置方法
    indexOf(targetElement[, startIndex])
    lastIndexOf(targetElement[, startIndex])

ECMAScript 5 为数组实例添加了两个位置方法:indexOf()和lastIndexOf()。这两个方法都接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。其中,indexOf()方法从数组的开头(位置0)开始向后查找,lastIndexOf()方法则从数组的末尾开始向前查找。
这两个方法都返回要查找的项在数组中的位置,或者在没找到的情况下返回-1。在比较第一个参数与数组中的每一项时,会使用全等操作符;也就是说,要求查找的项必须严格相等(就像使用===一样)。

var numbers = [1,2,3,4,5,4,3,2,1];
console.log(numbers.indexOf(4)); //3 从位置0开始向后查找4这个number
console.log(numbers.lastIndexOf(4)); //5    从数组末尾开始向前查找4这个number
console.log(numbers.indexOf(4,4)); //  从索引为4的位置向后查找4这个number。返回值是5
console.log(numbers.lastIndexOf(4,4)); // 从索引为4的位置向前查找4这个number。返回值是3
var person = {name:"Tom"};
var people = [{name:"Tom"}];
var morePeople = [person];
console.log(people.indexOf(person)); // -1   [{name:"Tom"}]中找不到person这个项
console.log(morePeople.indexOf(person)); //0

这里也许对于person、people和morePeople会进行比较。这里如果比较的话,如下代码

people[0] == person     //返回的是false
morePeople[0] == person //返回的是true

因为people中的那个对象不是person,它一个新对象,虽然它们的内容相同。比较对象其实就是比较对象的索引

  • 迭代方法

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

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

var numbers = [1,2,3,4,5,4,3,2,1];
var everyResult = numbers.every(function(item,index,arrty){
    return (item > 2);
});
console.log(everyResult); //false
var someResult = numbers.some(function(item, index, array){
    return (item > 2);
})
console.log(someResult);  //true
var filterResult = numbers.filter(function(item, index, array){
    return (item > 2);
});
console.log(filterResult);  //[3, 4, 5, 4, 3]
var mapResult = numbers.map(function(item, index, array){
    return item * 2;
});
console.log(mapResult); //[2, 4, 6, 8, 10, 8, 6, 4, 2]
  • 迭代方法

ECMAScript 5 还新增了两个归并数组的方法:reduce()和reduceRight()。这两个方法都会迭代数组的所有项,然后构建一个最终返回的值。其中,reduce()方法从数组的第一项开始,逐个遍历到最后。而reduceRight()则从数组的最后一项开始,向前遍历到第一项。

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

三、Date类型

要创建一个日期对象,使用new 操作符和Date 构造函数即可:

var now = new Date();
console.log(now)  //Wed Dec 20 2017 10:30:14 GMT+0800 (中国标准时间)

在调用Date 构造函数而不传递参数的情况下,新创建的对象自动获得当前日期和时间。如果想根据特定的日期和时间创建日期对象,必须传入表示该日期的毫秒数(即从UTC 时间1970 年1 月1 日午夜起至该日期止经过的毫秒数)。为了简化这一计算过程,ECMAScript 提供了两个方法:Date.parse()和Date.UTC()。

Date.parse()

var someDate = new Date(Date.parse("May 25, 2004"));
console.log(someDate);  //Tue May 25 2004 00:00:00 GMT+0800 (中国标准时间)

实际上,如果直接将表示日期的字符串传递给Date 构造函数,也会在后台调用Date.parse()。
Date.UTC()
Date.now()返回表示调用这个方法时的日期和时间的毫秒数。

var start = Date.now();
var stop = Date.now();
console.log(stop - start);

Date 类型还有一些专门用于将日期格式化为字符串的方法,这些方法如下。
• toDateString()——以特定于实现的格式显示星期几、月、日和年;
• toTimeString()——以特定于实现的格式显示时、分、秒和时区;
• toLocaleDateString()——以特定于地区的格式显示星期几、月、日和年;
• toLocaleTimeString()——以特定于实现的格式显示时、分、秒;
• toUTCString()——以特定于实现的格式完整的UTC 日期。

四、RegExp类型 (回头单独研究)

ECMAScript 通过RegExp 类型来支持正则表达式。使用下面类似Perl 的语法,就可以创建一个正则表达式。

var expression = / pattern / flags ;

其中的模式(pattern)部分可以是任何简单或复杂的正则表达式,可以包含字符类、限定符、分组、向前查找以及反向引用。每个正则表达式都可带有一或多个标志(flags),用以标明正则表达式的行为。正则表达式的匹配模式支持下列3 个标志。
• g:表示全局(global)模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止;
• i:表示不区分大小写(case-insensitive)模式,即在确定匹配项时忽略模式与字符串的大小写;
• m:表示多行(multiline)模式,即在到达一行文本末尾时还会继续查找下一行中是否存在与模式匹配的项。
因此,一个正则表达式就是一个模式与上述3 个标志的组合体。不同组合产生不同结果

五、Function类型

函数需要理解的两点:
1、函数实际上是对象。每个函数都是Function 类型的实例,而且都与其他引用类型一样具有属性和方法
2、由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。

  • 函数的定义方式:

1、函数声明语法定义

function sum (num1, num2){
  return num1 + num2;
}

2、函数表达式定义:

var sum = function(num1, num2){
  return num1 + num2;
};

3、使用Function构造函数定义

Function 构造函数可以接收任意数量的参数,但最后一个参数始终都被看成是函数体,而前面的参数则枚举出了新函数的参数。

var sum = new Function("num1", "num2", "return num1 + num2"); // 不推荐

由于函数名仅仅是指向函数的指针,因此函数名与包含对象指针的其他变量没有什么不同。换句话说,一个函数可能会有多个名字,如下面的例子所示。

function sum (num1, num2){
    return num1 + num2;
}
console.log(sum(10,10));    //20
var anotherSum = sum;
console.log(anotherSum(10,10)); //20
sum = null;
console.log(anotherSum(10,10)); //20
  • 没有重载

将函数名想象为指针,也有助于理解为什么ECMAScript 中没有函数重载的概念。

function addSomeNumber(num){
    return num + 100;
}
function addSomeNumber(num) {
    return num + 200;
}
var result = addSomeNumber(100); //300

以上代码声明了两个相同的函数,但是后声明的会覆盖前面的。再看下面的代码:

var addSomeNumber = function (num){
   return num + 100;
};
addSomeNumber = function (num) {
   return num + 200;
};
var result = addSomeNumber(100); //300

怎么样,很清晰了吧,在创建第二个函数时,实际上覆盖了引用第一个函数的变量addSomeNumber。

  • 函数声明和函数表达式

两者的区别:解析器在向执行环境中加载数据时,对函数声明和函数表达式并非一视同仁。解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。

console.log(sum(10,10));
function sum(num1,num2){
    return num1 + num2;
}

以上代码如果按照顺序执行的话,sum函数就没有定义。而解析器会先把函数声明的部分先读取,即函数声明提升(function declaration hoisting)

console.log(sum(10,10));
var sum = function(num1,num2){
    return num1 + num2;
};

以上代码就会报错。“sum is not a function”。
其实这就是我们说的js的预解析机制,包括变量提升、函数声明提升。面试中也经常会考的。

  • 作为值的函数

因为ECMAScript 中的函数名本身就是变量,所以函数也可以作为值来使用。也就是说,不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。

function callSomeFunction(someFunction, someArgument){
    return someFunction(someArgument);
}
function add10(num){
    return num + 10;
}
var result1 = callSomeFunction(add10, 10);
console.log(result1); //20

function getGreeting(name){
    return "hello," + name;  
}
var result2 = callSomeFunction(getGreeting,"Tom");
console.log(result2);   //"hello,Tom"
function createComparisonFunction(propertyName){
    return function(object1,object2){
        var value1 = object1[propertyName];
        var value2 = object2[propertyName];
        if(value1 < value2){
            return -1;
        }else if(value1 > value2){
            return 1;
        }else{
            return 0;
        }
    }
}
var data = [{name:"Tom", age:28},{name:"Jerry",age:29}];
data.sort(createComparisonFunction("name"));
console.log(data[0].name);  //jerry
data.sort(createComparisonFunction("age"));
console.log(data[0].name);  //Tom
  • 函数内部的属性

arguments 它是一个类数组对象,包含着传入函数中的所有参数
callee arguments 的主要用途是保存函数参数,但这个对象还有一个名叫callee 的属性,该属性是一个指针,指向拥有这个arguments 对象的函数。
阶乘函数的例子:

function factorial(num) {
    if(num <= 1){
        return 1;
    }else{
        return num * factorial(num - 1);
    }
}
console.log(factorial(5));  //120    这是递归算法

在函数有名字,而且名字以后也不会变的情况下,这样定义没有问题。但问题是这个函数的执行与函数名factorial 紧紧耦合在了一起。为了消除这种紧密耦合的现象,可以像下面这样使用arguments.callee。

function factorial(num) {
    if(num <= 1){
        return 1;
    }else{
        return num * arguments.callee(num - 1);
    }
}
console.log(factorial(6));  //720 
function factorial(num) {
    if(num <= 1){
        return 1;
    }else{
        return num * arguments.callee(num - 1);
    }
}
var trueFactorial = factorial;
factorial = function(){
    return 0;
}
console.log(trueFactorial(5)); //120
console.log(factorial(5)); //0

this 函数内部的一个特殊对象,this引用的是函数据以执行的环境对象

window.color = "red";
var o = {color:"blue"};
function sayColor(){
    console.log(this.color);
}
sayColor();  // "red"
o .sayColor = sayColor;
o.sayColor();  //"blue"

函数的名字仅仅是一个包含指针的变量而已。因此,即使是在不同的环境中执行,全局的sayColor()函数与o.sayColor()指向的仍然是同一个函数
caller这个属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为null

  • 函数的属性和方法

属性:
length表示函数希望接收的命名参数的个数
prototype:

在ECMAScript 核心所定义的全部属性中,最耐人寻味的就要数prototype 属性了。对于ECMAScript 中的引用类型而言,prototype 是保存它们所有实例方法的真正所在。换句话说,诸如toString()和valueOf()等方法实际上都保存在prototype 名下,只不过是通过各自对象的实例访问罢了。在创建自定义引用类型以及实现继承时,prototype 属性的作用是极为重要的

方法:
这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this 对象的值。首先,apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是Array 的实例,也可以是arguments 对象
apply()
call()
call()方法与apply()方法的作用相同,它们的区别仅在于接收参数的方式不同。对于call()方法而言,第一个参数是this 值没有变化,变化的是其余参数都直接传递给函数。换句话说,在使用call()方法时,传递给函数的参数必须逐个列举出来
事实上,传递参数并非apply()和call()真正的用武之地;它们真正强大的地方是能够扩充函数赖以运行的作用域。下面来看一个例子。

window.color = "red";
var o = {color:"blue"};
function sayColor() {
    console.log(this.color);
}
sayColor(); //"red"
sayColor.call(this); //"red"
sayColor.call(window); //"red"
sayColor.call(o); //blue

分析:直接使用sayColor()实在全局作用域(window)调用函数的。所以this.color == window.color。而sayColor.call(this)和sayColor.call(window)则是显式的在全局作用域中调用,也会显示“red”。而sayColor.call(o)则是在o这个对象的作用域中调用,所以 this.color = o.color。
bind()这个方法会创建一个函数的实例,其this 值会被绑定到传给bind()函数的值

window.color = "red";
var o = { color: "blue" };
function sayColor(){
    alert(this.color);
}
var objectSayColor = sayColor.bind(o);
objectSayColor(); //blue
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,701评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,649评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,037评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,994评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,018评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,796评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,481评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,370评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,868评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,014评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,153评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,832评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,494评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,039评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,437评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,131评论 2 356

推荐阅读更多精彩内容