引用类型 [重温JavaScript基础(四)]
这篇比较水,主要重温把之前一些疑惑的问题做了补充总结
引用类型综述
引用类型的值(即对象)是引用类型的一个实例。在js中,引用类型是一种数据结构,用于将数据和功能组织在一起。这种形式也常被成为类,但这种说法并不妥当。尽管js从技术上讲是一门面向对象的语言,但它不具备传统的面向对象的语言所支持的类和接口等基本结构(es6开始有支持)。引用类型有时候也被称为对象定义,因为它们描述的是一类对象所具有的属性和方法
如前所述,对象是某个特定引用类型的实例。新对象是使用new 操作符后跟一个构造函数来创建的。构造函数本身就是一个函数,只不过该函数是出于创建新对象的目的而定义的。例如:
var person = new Object()
这行代码创建了object引用类型的一个实例,然后把该实例保存在了变量person中。使用的构造函数是Object,它只为新对象定义了默认的属性和方法。js中提供了很多原生引用类型(例如Object),以便开发人员用于实现常见的计算任务。
object类型
object是我们使用最多的一个类型,虽然object类型不具备多少功能,但是对于在应用程序中储存和传输数据而言,这是非常理想的选择。
创建object实例的方式
new Object()
var person = new Object();
person.name = "Nicholas";
person.age = 29;
对象字面量 {}
var person = {
name : "Nicholas",
age : 29
};
在通过对象字面量定义对象时,实际上不会调用 构造函数()
通过对象字面量与通过构造函数创建对象的差别:
Object.create()
Object.create()
方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
可以理解为继承一个对象,es6提供了这个api在语义化上很有帮助,避免了去直接操作__proto__或者prototype。
const person = {
isHuman: false,
printIntroduction: function () {
console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
}
};
const me = Object.create(person);
me.name = "Matthew"; // "name" is a property set on "me", but not on "person"
me.isHuman = true; // inherited properties can be overwritten
me.printIntroduction();
// expected output: "My name is Matthew. Am I human? true"
Polyfill中的实现:
if (typeof Object.create !== "function") {
Object.create = function (proto, propertiesObject) {
if (typeof proto !== 'object' && typeof proto !== 'function') {
throw new TypeError('Object prototype may only be an Object: ' + proto);
} else if (proto === null) {
throw new Error("This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument.");
}
if (typeof propertiesObject != 'undefined') throw new Error("This browser's implementation of Object.create is a shim and doesn't support a second argument.");
function F() {}
F.prototype = proto;
return new F();
};
}
主要看这三句:
function F() {}
F.prototype = proto;//proto是Object.create的第一个参数
return new F();
上面三句其实等同于
const _o = {}
_o.__proto__ = proto
return _o
验证:
var a={aa:"1"}
var b= Object.create(a)
console.log(b.__proto__==a) //true
但是因为ie10等老浏览器没有__proto__所以Polyfill使用返回函数实例的方式来写
可见Object.create的作用主要是返回一个对象,这个对象的原型对象是Object.create的第一个参数
Array类型
js中的数组与其他语言最大的差别是数组的每一项可以保存任何类型的数据
这里主要介绍一下Array.prototype的几个方法
栈方法
LIFO(Last-In-First-Out,后进先出)
Array.prototype.push()
push()方法可以接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组的长度。
Array.prototype.pop()
pop()方法则从数组末尾移除最后一项,减少数组的 length 值,然后返回移除的项。
队列方法
FIFO(First-In-First-Out,先进先出)
Array.prototype.push()
push()方法可以接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组的长度。
Array.prototype.shift()
移除数组中的第一个项并返回该项,同时将数组长度减 1。
Array.prototype.unshift()
我管它叫插队方法hhhhhhh
能在数组前端添加任意个项并返回新数组的长度。
操作方法
Array.prototype.concat()
这个方法会先创建当前数组一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。
位置方法
Array.prototype.concat()
这个方法会先创建当前数组一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。
Array.prototype.splice()
这个方法会先创建当前数组一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。
- 删除:可以删除任意数量的项,只需指定 2 个参数:要删除的第一项的位置和要删除的项数。例如,splice(0,2)会删除数组中的前两项。
- 插入:可以向指定位置插入任意数量的项,只需提供 3 个参数:起始位置、0(要删除的项数)和要插入的项。如果要插入多个项,可以再传入第四、第五,以至任意多个项。例如,splice(2,0,"red","green")会从当前数组的位置 2 开始插入字符串"red"和"green"。
- 替换:可以向指定位置插入任意数量的项,且同时删除任意数量的项,只需指定 3 个参数:起始位置、要删除的项数和要插入的任意数量的项。插入的项数不必与删除的项数相等。例如,
splice (2,1,"red","green")会删除当前数组位置 2 的项,然后再从位置 2 开始插入字符串
"red"和"green"。
var colors = ["red", "green", "blue"];
var removed = colors.splice(0,1); // 删除第一项
alert(colors); // green,blue
alert(removed); // red,返回的数组中只包含一项
removed = colors.splice(1, 0, "yellow", "orange"); // 从位置 1 开始插入两项
alert(colors); // green,yellow,orange,blue
alert(removed); // 返回的是一个空数组
removed = colors.splice(1, 1, "red", "purple"); // 插入两项,删除一项
alert(colors); // green,red,purple,orange,blue
alert(removed); // yellow,返回的数组中只包含一项
迭代方法
这几个迭代方法可能相比直接用for循环并没有效率上的提升,但是对于代码语义性有很大帮助,也能少写几行代码
Array.prototype.every()
every():对数组中的每一项运行给定函数,如果该函数对每一项都返回 true,则返回 true。
Array.prototype.filter()
filter():对数组中的每一项运行给定函数,返回该函数会返回 true 的项组成的数组。
Array.prototype.forEach()
forEach():对数组中的每一项运行给定函数。这个方法没有返回值。
Array.prototype.map()
map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
Array.prototype.some()
some():对数组中的每一项运行给定函数,如果该函数对任一项返回 true,则返回 true。
归并方法
Array.prototype.reduce()
都接收两个参数:一个在每一项上调用的函数和(可选的)作为归并基础的初始值。函数接收 4 个参数:前一个值、当前值、项的索引和数组对象。
//求数组中所有值之和
var values = [1,2,3,4,5];
var sum = values.reduce(function(prev, cur, index, array){
return prev + cur;
});
alert(sum); //15
Date类型
日后整理
RegExp类型
日后整理
Function类型
函数实际上是对象。每个函数都是 Function 类型的实例,而且都与其他引用类型一样具有属性和方法。由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。
没有重载
将函数名想象为指针,也有助于理解为什么 js 中没有函数重载的概念。
函数声明和函数表达式
解析器在向执行环境中加载数据时,对函数声明和函数表达式并非一视同仁。解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行
//这是可用的 因为函数声明有提升
alert(sum(10,10));
function sum(num1, num2){
return num1 + num2;
}
//这是错误的,只有变量sum提升了,但是在执行alert的时候,sum的值是undefined
alert(sum(10,10));
var sum = function(num1, num2){
return num1 + num2;
};
函数内部属性
在函数内部,有两个特殊的对象:arguments 和 this。
arguments
arguments 是一个类数组对象,包含着传入函数中的所有参数。虽然 arguments 的主要用途是保存函数参数,但这个对象还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 arguments 对象的函数。
function factorial(num){
if (num <=1) {
return 1;
} else {
return num * factorial(num-1)
}
}
定义阶乘函数一般都要用到递归算法;如上面的代码所示,在函数有名字,而且名字以后也不会变的情况下,这样定义没有问题。但问题是这个函数的执行与函数名 factorial 紧紧耦合在了一起。为了消除这种紧密耦合的现象,可以像下面这样使用 arguments.callee。
function factorial(num){
if (num <=1) {
return 1;
} else {
return num * arguments.callee(num-1) //callee指向拥有这个arguments 对象的函数
}
}
this
函数内部的另一个特殊对象是 this,其行为与 Java 和 C#中的 this 大致类似。换句话说,this引用的是函数据以执行的环境对象
关于this 可以看this 的值到底是什么?一次说清楚
函数属性和方法
前面曾经提到过,ECMAScript 中的函数是对象,因此函数也有属性和方法。每个函数都包含两个属性:length 和 prototype。
length
其中,length 属性表示函数希望接收的命名参数的个数,如下面的例子所示。
function sayName(name){
alert(name);
}
function sum(num1, num2){
return num1 + num2;
}
function sayHi(){
alert("hi");
}
alert(sayName.length); //1
alert(sum.length); //2
alert(sayHi.length); //0
prototype
ECMAScript 核心所定义的全部属性中,最耐人寻味的就要数prototype 属性了。对于ECMAScript 中的引用类型而言,prototype 是保存它们所有实例方法的真正所在。换句话说,诸如toString()和 valueOf()等方法实际上都保存在 prototype 名下,只不过是通过各自对象的实例访问罢了。在创建自定义引用类型以及实现继承时,prototype 属性的作用是极为重要的
call apply
基本包装类型
不做记录
单体内置对象
不做记录