- 如何创建、访问和修改对象?
- JavaScript 函数为什么是一级函数?
- JavaScript 对传统类和继承的抽象?
前端入门回顾
数组
数组是 JavaScript 中最有用的数据结构之一。在本质上,数组就是一个由方括号(即 [ ]
)括起来的有序
元素集合。数组可以存储许多不同类型的数据,而不仅仅是字符串!
const mixedArray = [0, "hello", true, ['apple', 'banana', 'orange', 'grape', 'lychee']];
对象
对象是 JavaScript 中最重要的数据结构之一。从根本上来说,对象就是一个有关联的键/值对
的集合。我们使用大括号(即 { }
)来创建对象。数组中的元素被数字索引所引用,而对象中的键则必须直接命名。
const car = { // 赋给该对象的变量被命名为 car
color: 'red', // 每个键均与一个值相关联。这些键值对通过冒号连接起来
year: 1992, // 每个独特的键值对(称为该对象的属性)均通过逗号与其他属性分隔开来
isPreOwned: true
}; // 使用大括号来定义 car 对象
与数组不同,对象是无序集合。
对象属性语法
另外要注意的是,键
(即对象属性的名称)是字符串
,但是围绕这些字符串的引号是可选的
,前提是该字符串也是一个有效的 JavaScript 标识符(即可以将其用作变量名称或函数名称)。因此,以下三个对象是等价的:
const course = { courseId: 711 }; // ← 没有引号
const course = { 'courseId': 711 }; // ← 单引号
const course = { "courseId": 711 }; // ← 双引号
对比Python字典
Python 字典与 JavaScript 中的对象也有一些类似功能,并有一些显著差异。首先,Python 字典中的键必须是可哈希的
(例如字符串、数字、浮点数等)。以下是 JavaScript 中的一个有效对象:
const javascriptObject = { name: 'George Orwell', year: 1984 }
然而,它作为一个 Python 字典则是无效的:
python_dictionary = {name: 'George Orwell', year: 1984}
# Traceback (most recent call last):
# NameError: name 'name' is not defined
一个快速对策是将 Python 字典的键转换为字符串
:
my_dictionary = {'name': 'George Orwell', 'year': 1984}
访问对象属性
有两种方法:点表示法
和方括号表示法
。
const bicycle = {
color: 'blue',
type: 'mountain bike',
wheels: {
diameter: 18,
width: 8
}
};
bicycle.color; // 'blue'
bicycle['color']; // 'blue'
bicycle.wheels.width; // 8
bicycle['wheels']['width']; // 8
点表示法的局限性
请注意,尽管点表示法可能更易于读写,但它并不适用于所有情况。
例如,假设上面的 bicycle 对象中有一个键是数字
。那么,像 bicycle.1
; 这样的表达式将会导致错误;而 bicycle[1]
,则可以返回预期的值:
bicycle.1; // Uncaught SyntaxError: Unexpected number
bicycle[1]; // (returns the value of the `1` property)
另一个问题在于将变量赋给属性名称。假设我们声明了 myVariable
,并将其赋给字符串 'color'
:
const myVariable = 'color';
bicycle[myVariable]; // 'blue'
bicycle.myVariable; // undefined
要记得,JavaScript 对象中的所有属性键都是字符串,即使省略了引号
也是如此。当使用点表示法
时,JavaScript 解释器将在 bicycle 中查找一个值为 'myVariable' 的键
。由于该对象中并未定义这样一个键,因此这个表达式将会返回 undefined
。
创建和修改属性
创建对象
const myObject = {}; // 使用字面量表示法(推荐):
const myObject = new Object(); // 使用 Object() 构造函数:
虽然这两个方法最终都会返回一个没有自己属性的对象,但是 Object()
构造函数相对较慢,而且较为冗长。因此,在 JavaScript 中创建新对象的推荐方法是使用字面量表示法
。
修改属性
const cat = {
age: 2,
name: 'Bailey',
meow: function () {
console.log('Meow!');
},
greet: function (name) {
console.log(`Hello ${name}`);
}
};
cat.age += 1;
cat.age; // 3
cat.name = 'Bambi';
cat.name; // 'Bambi'
// 现在的 cat 对象
{
age: 3,
name: 'Bambi',
meow: function () {
console.log('Meow!');
},
greet: function (name) {
console.log(`Hello ${name}`);
}
};
添加属性
const printer = {};
// 点表示法来添加属性
printer.on = true;
printer.mode = 'black and white';
// 方括号表示法
printer['remainingSheets'] = 168;
// 添加一个方法
printer.print = function () {
console.log('The printer is printing!');
};
// 最终 printer 对象
{
on: true,
mode: 'black and white',
remainingSheets: 168,
print: function () {
console.log('The printer is printing!');
}
};
移除属性
由于对象是可变的
,我们不仅可以修改现有属性(或添加新属性),还可以从对象中删除属性。
delete printer.mode; // true
delete
会直接改变当前的对象。如果调用一个已被删除的方法,JavaScript 解释器将无法再找到 mode
属性,因为 mode 键(及其值 true)已被删除:
printer.mode; // undefined
传递参数
传递一个原始类型
,在 JavaScript 中,原始类型(例如字符串、数字、布尔值
等)是不可变
的。换句话说,对函数中的参数所作的任何更改都会有效地为该函数创建一个局部副本,而不会影响该函数外部的原始类型
。
function changeToEight(n) {
n = 8; // 无论 n 是什么,它此刻都是 8... 但仅仅是在这个函数中!
}
let n = 7;
changeToEight(n);
console.log(n); // 7
传递一个对象
,JavaScript 中的对象是可变的
。如果你向函数传递一个对象,Javascript 会传递一个引用给该对象。如果我们向函数传递一个对象,然后修改一个属性。
let originalObject = {
favoriteColor: 'red'
};
function setToBlue(object) {
object.favoriteColor = 'blue';
}
setToBlue(originalObject);
originalObject.favoriteColor; // blue
这是怎么发生的?答案是,由于JavaScript 中的对象是通过引用传递的
,因此如果我们修改那个引用,我们其实是在直接修改原始对象本身!
更重要的是:同样的规则适用于将一个对象重新赋给新的变量,然后改变那个副本
。同样,由于对象是通过引用传递的,因此原始对象也被改变了。
const iceCreamOriginal = {
Andrew: 3,
Richard: 15
};
const iceCreamCopy = iceCreamOriginal;
iceCreamCopy.Richard; // 15
iceCreamCopy.Richard = 99;
iceCreamCopy.Richard; // 99
iceCreamOriginal.Richard; // 99
对象比较
const parrot = {
group: 'bird',
feathers: true,
chirp: function () {
console.log('Chirp chirp!');
}
};
const pigeon = {
group: 'bird',
feathers: true,
chirp: function () {
console.log('Chirp chirp!');
}
};
parrot === pigeon; // false
const myBird = parrot;
myBird === parrot; // true
事实证明,只有在将对同一个对象的两个引用进行比较时
,这个表达式才会返回 true
。
原始类型(例如数字、字符串、布尔值等)是不可变的值。
let string = 'orange';
function changeToApple(string) {
string = 'apple';
}
changeToApple(string);
console.log(string); // orange
默认情况下,对象是可变的(除了少数例外),因此其中的数据可以被改变。既可以添加新属性,也可以通过指定属性名称并赋值(或重新赋值)来轻松修改现有属性。此外,对象的属性和方法还可以使用 delete
运算符来删除,它会直接改变对象。
函数与方法
const developer = {
name: 'Andrew',
sayHello: function () {
console.log('Hi there!');
}
};
调用方法
developer.sayHello(); // 'Hi there!'
developer['sayHello'](); // 'Hi there!'
对象引用自身的属性
const triangle = {
type: 'scalene',
identify: function () {
console.log(`This is a ${this.type} triangle.`);
}
};
triangle.identify(); // 'This is a scalene triangle.'
this
的值
由于对象既包括数据,又包括对这些数据进行操作的手段,因此方法可以使用特别的 this
关键字来访问被调用的对象。当方法被调用时,this 的值将被确定,它的值就是调用该方法的对象
。由于 this
是 JavaScript 中的一个保留字,因此它的值不能用作标识符。
this
详解
const chameleon = {
eyes: 2,
lookAround: function () {
console.log(`I see you with my ${this.eyes} eyes!`);
}
};
chameleon.lookAround();
// 'I see you with my 2 eyes!'
该函数体内部是代码 this.eyes
。由于 lookAround()
方法在 chameleon
对象上作为 chameleon.lookAround()
被调用,因此 this
的值就是 chameleon
对象本身!相应地,this.eyes
就是数字 2,因为它指向 chameleon
对象的 eyes
属性。
函数/方法中的 this
const chameleon = {
eyes: 2,
lookAround: function () {
console.log(`I see you with my ${this.eyes} eyes!`);
}
};
function whoThis () {
this.trickyish = true
}
chameleon.lookAround();
whoThis();
在这两种情况下,this 的使用基本上是相同的。chameleon
代码中使用 this
来检索一个属性
,而在 whoThis
代码中使用 this
来设置一个属性
。
函数如何调用决定了函数内的 this
的值。
whoThis()
函数中的 this
的值是什么呢?这是 JavaScript 语言一个有趣的特点。当一个常规函数被调用时,this
的值就是全局 window
对象。
const chameleon = {
eyes: 2,
lookAround: function () {
debugger
console.log(`I see you with my ${this.eyes} eyes!`);
}
};
chameleon.lookAround();
function whoThis () {
this.trickyish = true
}
whoThis();
注意全局变量
window
对象
如果你还没有用过 window
对象,该对象是由浏览器环境提供的
,并可使用标识符 window
在 JavaScript 代码中进行全局访问
。该对象不是 JavaScript 规范(即 ECMAScript)的一部分,而是由 W3C 开发的。
这个 window
对象可以访问大量有关页面本身的信息,包括:
- 页面的 URL (
window.location;
) - 页面的垂直滚动位置 (
window.scrollY
) - 滚动到新位置(
window.scroll(0, window.scrollY + 200);
,从当前位置向下滚动 200 个像素) - 打开一个新的网页 (
window.open("https://www.udacity.com/");
)
示例:
const car = {
numberOfDoors: 4,
drive: function () {
console.log(`Get in one of the ${this.numberOfDoors} doors, and let's go!`);
}
};
const letsRoll = car.drive;
letsRoll();
this
指向 window
对象。虽然 car.drive
是一个方法,但我们还是将该函数存储在一个变量 letsRoll
中。由于 letsRoll()
是作为一个常规函数调用
的,因此 this
将指向它内部的 window
对象。
全局变量是 window
上的属性
每个在全局级别(在函数外部)进行的变量声明都会自动成为 window
对象上的一个属性!
全局变量和 var
、let
及 const
JavaScript 中使用关键字 var
、let
和 const
来声明变量。只有使用 var
关键字来声明变量才会将其添加到 window
对象中。如果你用 let
或 const
在函数外部声明一个变量,它将不会被作为属性添加到 window 对象中
。
全局函数是 window
上的方法
与全局变量可以作为 window 对象上的属性进行访问类似,任何全局函数声明都可以作为 window 对象上的方法进行访问。
避免全局变量
全局变量和函数并不理想。这是由于很多原因,不过我们要看的两种是:
- 紧密耦合
- 名称冲突
紧密耦合
紧密耦合是开发者用来表示代码过于依赖彼此细节的一个短语。“耦合”一词是指“将两样东西配对”。在紧密耦合中,代码片断以一种紧密的方式连接在一起,使得更改一段代码会无意中改变其他代码的功能。
名称冲突
一个主要问题是,两个函数都会尝试更新变量和/或设置变量,但是这些更改将被相互覆盖。
提取属性和值
在本质上,对象只是一个键/值对的集合。 如果我们只想从对象中提取键呢?假设我们有以下对象,表示一个字典:
const dictionary = {
car: 'automobile',
apple: 'healthy snack',
cat: 'cute furry animal',
dog: 'best friend'
};
当 Object.keys()
被赋予一个对象时,它只会提取该对象的键
,并将它们返回为一个数组
:
Object.keys(dictionary);
// ['car', 'apple', 'cat', 'dog']
如果我们需要一个对象的值列表
,我们可以使用 Object.values()
:
Object.values(dictionary);
// ['automobile', 'healthy snack', 'cute furry animal', 'best friend']
Object.keys()
结果数组的元素是字符串,数组元素的顺序与 for...in
循环的顺序相同。Object.values()
结果数组元素的顺序与 for...in
循环的顺序相同。
支持现状
Object.keys()
已经存在很长时间了,因此每个浏览器都完全支持它。
与此相反,Object.values()
则是新近才出现的。它在 2017 年被正式添加到了语言规范中。但是,仅仅因为它已经被添加到规范中,并不意味着你的浏览器就一定支持它!
如何确定你的浏览器_确实_支持 Object.values()
呢?请查看浏览器兼容性表!