前端进阶-深入了解对象

  • 如何创建、访问和修改对象?
  • 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 运算符来删除,它会直接改变对象。

‘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 对象上的一个​​属性!

全局变量和 varletconst

JavaScript 中使用关键字 varletconst 来声明变量。只有使用 var 关键字来声明变量才会将其添加到 window 对象中。如果你用 letconst 在函数外部声明一个变量,它将不会被作为属性添加到 window 对象中

全局函数是 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() 呢?请查看浏览器兼容性表!

Object.keys
Object.values

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值