js基础

234 阅读12分钟

数据类型

js基本的数据类型:

  • Number: Any number, including numbers with decimals: 48151623.42.

  • BigInt: Any number, greater than 253-1 or less than -(253-1), with n appended to the number: 1234567890123456n.

  • String: Any grouping of characters on your keyboard (letters, numbers, spaces, symbols, etc.) surrounded by single quotes: ' ... ' or double quotes " ... ", though we prefer single quotes. Some people like to think of string as a fancy word for text.

  • Boolean: This data type only has two possible values— either true or false (without quotes). It’s helpful to think of booleans as on and off switches or as the answers to a “yes” or “no” question.

  • null:This data type represents the intentional absence of value

  • undefined:This data type is denoted by the keyword undefined.It also represents the absence of a value though it has a different use than nullundefined means that a given value does not exist.

null 和 undefined的区别 当一个变量被声明了,但没有赋值时,它的默认值就是undefined undefined还可以作为函数没有返回值时的默认返回值 null表示“无”,表示该变量没有值 简而言之,undefined 是缺少初始化的状态,null 是一种空值的状态。

  • symbol:A newer feature to the language, symbols are unique identifiers, useful in more complex coding. No need to worry about these for now.
  • symbol是从es6开始引入的一种新特性,用于创建独一无二的标识符
  • Object: Collections of related data.

运算符

  1. Add: +
  2. Subtract: -
  3. Multiply: *
  4. Divide: /
  5. Remainder: %
  6. 自增自减运算符

字符串拼接

Operators aren’t just for numbers! When a + operator is used on two strings, it appends the right string to the left string:

console.log('hi' + 'ya'); // Prints 'hiya'
console.log('wo' + 'ah'); // Prints 'woah'
console.log('I love to ' + 'code.')
// Prints 'I love to code.'

属性

当你在js中声明新的数据时,浏览器将会存下数据类型的实例(instance) 所有的数据类型都有特定属性,可以通过实例访问 比如说,每个String都有个length实例,存储了String中字符的个数(即长度)

console.log('Hello'.length); // Prints 5

方法

We call, or use, these methods by appending an instance with:

  • a period (the dot operator)
  • the name of the method
  • opening and closing parentheses

e.g.'example string'.methodName().

console.log('hello'.toUpperCase()); // Prints 'HELLO'
console.log('Hey'.startsWith('H')); // Prints true
console.log('    Remove whitespace   '.trim()); //prints Remove whitespace

变量

  • es6之后使用let创建变量,之前用var
  • If we don’t assign a value to a variable declared using the let keyword, it automatically has a value of undefined.
  • We can reassign the value of the variable.
  • 如果我们用let声明变量时不赋予任何值,它默认会是undefined
let changeMe = true;
changeMe = false;
console.log(changeMe)

常量

  • es6之后用const声明常量
  • Constant variables must be assigned a value when declared. If you try to declare a const variable without a value, you’ll get a SyntaxError.
  • 如果创建const时不赋予任何值,它会报错

字符串插值

const myPet = 'armadillo';
console.log(`I own a pet ${myPet}.`);
// Output: I own a pet armadillo.

模板字面量由反引号 ` 包裹

typeof 操作符

If you need to check the data type of a variable’s value, you can use the typeof operator.

The typeof operator checks the value to its right and returns, or passes back, a string of the data type.

const unknown1 = 'foo';
console.log(typeof unknown1); // Output: string

const unknown2 = 10;
console.log(typeof unknown2); // Output: number

const unknown3 = true; 
console.log(typeof unknown3); // Output: boolean

条件语句

if

if (true) {
  console.log('This message will print!'); 
}
// Prints: This message will print!

if……else

if (false) {
  console.log('The code in this block will not run.');
} else {
  console.log('But the code in this block will!');
}

// Prints: But the code in this block will!

条件运算符

  • Less than: <
  • Greater than: >
  • Less than or equal to: <=
  • Greater than or equal to: >=
  • Is equal to: ===
  • Is not equal to: !==
10 < 12 // Evaluates to true

逻辑运算符

  • the and operator (&&)
  • the or operator (||)
  • the not operator, otherwise known as the bang operator (!)
if (stopLight === 'green' && pedestrians === 0) {
  console.log('Go!');
} else {
  console.log('Stop');
}

非Boolean值的真假

当你想查看一个变量是否存在,不必非要用它去和null或undefined作比较:

let myVariable = 'I Exist!';

if (myVariable) {
   console.log(myVariable)
} else {
   console.log('The variable does not exist.')
}

以上代码输出将会是myVariable,因为myVariable是一个真值,尽管它不为'true',但只要不为falsy数就是真值

falsy数即为:

  • 0
  • Empty strings like "" or ''
  • null which represent when there is no value at all
  • undefined which represent when a declared variable lacks a value
  • NaN, or Not a Number
let numberOfApples = 0;

if (numberOfApples){
   console.log('Let us eat apples!');
} else {
   console.log('No apples left!');
}

// Prints 'No apples left!'

常见应用

验证用户名是否为空:

let username = '';
let defaultName;

if (username) {
  defaultName = username;
} else {
  defaultName = 'Stranger';
}

console.log(defaultName); // Prints: Stranger

当你的赋值里有或表达式时,js会选取真值进行赋值

let username = '';
let defaultName = username || 'Stranger';

console.log(defaultName); // Prints: Stranger

当都为true时,会选择第一个

三目运算符

一些简单的if……else语句:

let isNightTime = true;

if (isNightTime) {
  console.log('Turn on the lights!');
} else {
  console.log('Turn off the lights!');
}

可以用三目运算符代替:

isNightTime ? console.log('Turn on the lights!') : console.log('Turn off the lights!');

  • The condition, isNightTime, is provided before the ?.
  • Two expressions follow the ? and are separated by a colon :.
  • If the condition evaluates to true, the first expression executes.
  • If the condition evaluates to false, the second expression executes.

函数

创建函数

image.png

  • We should also be aware of the hoisting feature in JavaScript which allows access to function declarations before they’re defined.
  • js具有hoisting(提升性),可以在函数声明之前访问函数

js函数构成:

  • function 关键字
  • 函数名
  • 代码块
greetWorld(); // Output: Hello, World!

function greetWorld() {
  console.log('Hello, World!');
}

调用函数

image.png

形参和实参

image.png

image.png

image.png

默认参数

es6的新特性是默认参数,默认参数允许参数有默认值,当调用函数时没有给参数或参数为undefined时会选择默认参数值

function greeting (name = 'stranger') {
  console.log(`Hello, ${name}!`)
}

greeting('Nick') // Output: Hello, Nick!
greeting() // Output: Hello, stranger!

返回值

function rectangleArea(width, height) {
  let area = width * height;
}
console.log(rectangleArea(5, 7)) // Prints undefined

如果一个函数没有返回值,那它的返回值默认为undefined

image.png

function rectangleArea(width, height) {
  if (width < 0 || height < 0) {
    return 'You need positive integers to calculate area!';
  }
  return width * height;
}

函数表达式

另一种定义函数的方法是通过函数表达式

image.png 在函数表达式中,通常不需要函数名。没有函数名的函数称为匿名函数。

A function expression is often stored in a variable in order to refer to it. 函数表达式将函数存储在变量中,可以通过变量引用它

声明函数表达式:

  • 声明一个变量,从es6开始,通常使用const声明
  • 赋予变量一个匿名函数

函数表达式没有hoisting特性

const plantNeedsWater  = function(day){
  if(day === 'Wednesday'){
    return true;
  }else{
    return false;
  }
};
plantNeedsWater('Tuesday');
console.log(plantNeedsWater('Tuesday'));

为什么需要函数表达式

  1. 函数表达式非常适合作为回调函数
setTimeout(function() {
    console.log("1秒后执行");
}, 1000);

如果用函数声明,就必须先在外部定义函数:

function delayedMessage() {
    console.log("1秒后执行");
}
setTimeout(delayedMessage, 1000);

回调函数:函数表达式更适合在需要“立即传递函数”的场景,如 setTimeoutmap()filter()

  1. 作为匿名函数
const greet = function(name) {
    return "Hello, " + name;
};
console.log(greet("Alice")); // 输出: Hello, Alice

优点:

  • 简洁,适用于临时函数
  • 可以作为参数传递,不污染全局作用域
  1. 立即执行函数

函数表达式可以用来 创建一个立即执行的函数(IIFE,Immediately Invoked Function Expression)

javascript
复制编辑
(function() {
    console.log("立即执行!");
})();

  1. 作为对象的方法,函数表达式可以直接用于对象的方法:
const person = {
    name: "Alice",
    sayHi: function() {
        console.log("Hi, I'm " + this.name);
    }
};
person.sayHi(); // 输出: Hi, I'm Alice

如果用函数声明,不能直接写在对象里

  1. 用于高阶函数 在 函数式编程 中,函数表达式可以赋值给变量,并传递给其他函数:
const add = function(a, b) {
    return a + b;
};

function operate(fn, x, y) {
    return fn(x, y);
}

console.log(operate(add, 3, 4)); // 输出: 7

这里 add 作为参数传递给 operate,如果是函数声明,就不能这样传递。

箭头函数

es6开始引入箭头函数,一种简单的书写函数的方法

  • 箭头函数去除了关键词function,而是用()代替
  • =>指向函数体
const rectangleArea = (width, height) => {
  let area = width * height;
  return area;
};
  • 只有一个入参的函数,不需要用括号 image.png

  • 只有一行代码的函数体,不需要用括号,在没有大括号的情况下,该行执行结果会被自动返回,不需要显式写return

image.png

如:

const squareNum = (num) => {
  return num * num;
};

等于

const squareNum = num => num * num;

作用域

作用域定义了变量可以在哪里被使用

代码块和作用域

代码块作用于函数定义和if语句:

const logSkyColor = () => {
  let color = 'blue'; 
  console.log(color); // blue 
}
if (dusk) {
  let color = 'pink';
  console.log(color); // pink
}

全局变量

  • 全局变量通常声明在代码块之外
  • 全局变量可以被程序内的任何代码使用,包括代码块内
const color = 'blue';

const returnSkyColor = () => {
  return color; // blue 
};

console.log(returnSkyColor()); // blue

局部变量

  • 局部变量被定义在代码块内,只能在代码块内被使用
const logSkyColor = () => {
  let color = 'blue'; 
  console.log(color); // Prints "blue"
};

logSkyColor(); // Prints "blue"
console.log(color); // throws a ReferenceError

作用域污染

  • 过多的全局变量可能会造成问题
  • 当你声明全局变量时,这些变量会进入global namespace
  • global namespace允许从程序中的任何地方访问全局变量,这些变量会在global namespace中一直存在到程序运行结束
  • 如果我们使用过多的全局变量,global namespace很快就会满
  • 作用域污染是指我们创建太多全局变量,或者我们在不同作用域之间重复使用变量,即在不同的代码块中声明同名变量
let num = 50;  
  
const logNum = () => {  
  num = 100; // Take note of this line of code  
  console.log(num);  
};  
  
logNum(); // Prints 100  
console.log(num); // Prints 100

数组

js的数组可以存储任何数据类型(包括字符串,数字,布尔型)。数组是有序的,意味着每个元素都有一个索引

创建数组

image.png

js可以创建全是同一数据类型的数组,也可以创建全是不同数据类型的数组

let newYearsResolutions = ['Keep a journal', 'Take a falconry class', 'Learn to juggle'];
const hobbies = ['a', 'b', 'c'];
console.log(hobbies);
//[ 'a', 'b', 'c' ]

访问数组元素

  • 数组中的每一个元素都一个数字表示其位置,即索引

  • 我们可以通过索引访问其中的单个元素,js的索引从0开始计算

  • 如果访问数组长度以外的元素,会返回undefined

image.png

const hello = 'Hello World';
console.log(hello[6]);
// Output: W

更新数组元素

let seasons = ['Winter', 'Spring', 'Summer', 'Fall'];

seasons[3] = 'Autumn';
console.log(seasons); 
//Output: ['Winter', 'Spring', 'Summer', 'Autumn']

let/const数组

  • 用const声明的变量是不可以再重新赋值的,但是,用const声明的数组中的元素是可以变化的,我们可以改变const数组的内容,但是不能重新赋值一个新数组或一个不同的值

length属性

  • length会返回数组中元素的个数
const newYearsResolutions = ['Keep a journal', 'Take a falconry class'];

console.log(newYearsResolutions.length);
// Output: 2

push方法

push方法允许我们添加一个元素在数组末尾

const itemTracker = ['item 0', 'item 1', 'item 2'];

itemTracker.push('item 3', 'item 4');

console.log(itemTracker); 
// Output: ['item 0', 'item 1', 'item 2', 'item 3', 'item 4'];

pop方法

pop方法会删除数组中的最后一个元素,且pop方法本身会返回被删除的那个元素

const newItemTracker = ['item 0', 'item 1', 'item 2'];

const removed = newItemTracker.pop();

console.log(newItemTracker); 
// Output: [ 'item 0', 'item 1' ]
console.log(removed);
// Output: item 2

其他Array方法

当调用pop和push时,会改变原数组,如果我们不想改变原数组,也存在不会改变元素组的方法:

shift:移除数组中的第一个元素

unshift(item):将元素插到数组中第一个位置

slice(start index, end index):截取数组中的一部分

indexOf(item):返回item所在的索引

数组和函数

之前我们学习了可变方法和不可变方法,那么如果作为函数参数对数组进行修改,数组会变化吗

const flowers = ['peony', 'daffodil', 'marigold'];

function addFlower(arr) {
  arr.push('lily');
}

addFlower(flowers);

console.log(flowers); // Output: ['peony', 'daffodil', 'marigold', 'lily']

会变的,因为是引用传递

嵌套数组

数组中也可以存放其他数组,当数组中有元素为数组时,这就是一个嵌套数组

const nestedArr = [[1], [2, 3]];

console.log(nestedArr[1]); // Output: [2, 3]

const nestedArr = [[1], [2, 3]];

console.log(nestedArr[1]); // Output: [2, 3]
console.log(nestedArr[1][0]); // Output: 2

循环

for循环

for (let counter = 0; counter < 4; counter++) {
  console.log(counter);
}

遍历数组

const animals = ['Grizzly Bear', 'Sloth', 'Sea Lion'];
for (let i = 0; i < animals.length; i++){
  console.log(animals[i]);
}

嵌套循环

const myArray = [6, 19, 20];
const yourArray = [19, 81, 2];
for (let i = 0; i < myArray.length; i++) {
  for (let j = 0; j < yourArray.length; j++) {
    if (myArray[i] === yourArray[j]) {
      console.log('Both arrays have the number: ' + yourArray[j]);
    }
  }
}

while循环

for循环转while循环

// A for loop that prints 1, 2, and 3
for (let counterOne = 1; counterOne < 4; counterOne++){
  console.log(counterOne);
}

// A while loop that prints 1, 2, and 3
let counterTwo = 1;
while (counterTwo < 4) {
  console.log(counterTwo);
  counterTwo++;
}

do……while循环

let countString = '';
let i = 0;

do {
  countString = countString + i;
  i++;
} while (i < 5);

console.log(countString);

break

for (let i = 0; i < 99; i++) {
  if (i > 2 ) {
     break;
  }
  console.log('Banana.');
}

console.log('Orange you glad I broke out the loop!');

函数

函数为数据

const announceThatIAmDoingImportantWork = () => {
    console.log("I’m doing very important work!");
};


const busy = announceThatIAmDoingImportantWork;

busy(); // This function call barely takes any space!
  • 因为原函数名太长所以换成了busy
  • 原函数名可以通过busy.name得到

函数为参数

  • 在js中,函数可以作为参数传递

  • 作为参数传递进来的函数叫回调函数,回调函数会在高阶函数执行中被调用

const higherOrderFunc = param => {
  param();
  return `I just invoked ${param.name} as a callback function!`
}
 
const anotherFunc = () => {
  return 'I\'m being invoked by the higher-order function!';
}

higherOrderFunc(anotherFunc);

const addTwo = num => {
  return num + 2;
}

const checkConsistentOutput = (func, val) => {
  let checkA = val + 2;
  let checkB = func(val);

  if(checkA === checkB){
    return checkA;
  }else{
    return 'inconsistent results';
  }

}

console.log(checkConsistentOutput(addTwo, 2));

迭代器

迭代器是调用在数组上的方法,用于迭代数组并返回值:

  • .forEach()
  • .map()
  • .filter()
const artists = ['Picasso', 'Kahlo', 'Matisse', 'Utamaro'];

artists.forEach(artist => {
  console.log(artist + ' is one of my favorite artists.');
});

const numbers = [1, 2, 3, 4, 5];

const squareNumbers = numbers.map(number => {
  return number * number;
});

console.log(squareNumbers);

const things = ['desk', 'chair', 5, 'backpack', 3.14, 100];

const onlyNumbers = things.filter(thing => {
  return typeof thing === 'number';
});

console.log(onlyNumbers);

foreach

foreach用于对每个元素执行相同的代码

image.png

  • foreach可以将回调函数作为参数
  • .forEach() 遍历数组,并对每个元素执行回调函数。在每次执行时,当前元素作为参数传递给回调函数。
  • foreach的返回值永远是undefined

通常我们会结合foreach和箭头函数

groceries.forEach(groceryItem => console.log(groceryItem));

我们也可以提前定义一个函数作为回调函数

function printGrocery(element){
  console.log(element);
}

groceries.forEach(printGrocery);

示例:

const fruits = ['mango', 'papaya', 'pineapple', 'apple'];

// Iterate over fruits below
fruits.forEach(fruit => console.log("I want to eat a " + fruit))

map

map也可以将回调函数作为参数,与forEach的不同是它会返回修改过的新数组

const numbers = [1, 2, 3, 4, 5]; 

const bigNumbers = numbers.map(number => {
  return number * 10;
});

console.log(numbers); // Output: [1, 2, 3, 4, 5]
console.log(bigNumbers); // Output: [10, 20, 30, 40, 50]


  • map会将每个元素作为参数调用回调函数,并将结果返回在新的数组

示例:


const animals = ['Hen', 'elephant', 'llama', 'leopard', 'ostrich', 'Whale', 'octopus', 'rabbit', 'lion', 'dog'];

// Create the secretMessage array below
const secretMessage = animals.map(animal => {return animal[0];});

console.log(secretMessage.join(''));

const bigNumbers = [100, 200, 300, 400, 500];

// Create the smallNumbers array below
const smallNumbers = bigNumbers.map(bigNumber => {return bigNumber/100;});

filter

  • filter也可以像map一样返回新数组,但filter返回的新数组是经过筛选的
  • filter会根据回调函数调用元素后返回的真/假值,判断是否过滤掉这个元素
  • 返回结果为真的元素会被加进新数组
const words = ['chair', 'music', 'pillow', 'brick', 'pen', 'door']; 

const shortWords = words.filter(word => {
  return word.length < 6;
});

console.log(words); // Output: ['chair', 'music', 'pillow', 'brick', 'pen', 'door'];  
console.log(shortWords); // Output: ['chair', 'music', 'brick', 'pen', 'door']

findIndex()

有时我们想知道某个元素在数组中的位置,这时就会用到findIndex方法

const jumbledNums = [123, 25, 78, 5, 9]; 

const lessThanTen = jumbledNums.findIndex(num => {
  return num < 10;
});

console.log(lessThanTen); // Output: 3 
console.log(jumbledNums[3]); // Output: 5

  • findIndex会返回数组中第一个符合条件的元素的索引
  • 如果没有任何元素符合条件,findIndex会返回-1

reduce()

  • reduce 可以用来处理数组的各种累积操作,通过将数组元素逐一处理并累加或合并,最终得到一个单一结果
const numbers = [1, 2, 4, 10];

const summedNums = numbers.reduce((accumulator, currentValue) => {
  return accumulator + currentValue
})

console.log(summedNums) // Output: 17

  • reduce会按照固定的顺序将数组元素逐一传递给回调函数,并且回调函数的第一个参数总是作为累计值(上一次回调的结果),第二个参数总是当前正在处理的数组元素

  • reduce也可以通过第二个参数去设置初始值(accumulator的初始值),例如:

const numbers = [1, 2, 4, 10];

const summedNums = numbers.reduce((accumulator, currentValue) => {
  return accumulator + currentValue
}, 100)  // <- Second argument for .reduce()

console.log(summedNums); // Output: 117

some

  • 检查数组中至少有一个元素满足指定条件
const words = ['unique', 'uncanny', 'pique', 'oxymoron', 'guise'];

// Something is missing in the method call below

console.log(words.some((word) => {
  return word.length < 6;
}));

every

用来检查数组中的 每一个元素 是否都符合指定的条件

console.log(interestingWords.every((word) => {return word.length > 5} ));

对象

创建对象

  • 我们用{}来创建对象
let spaceship = {}; // spaceship is an empty object
  • js的对象是无序的
  • 数据以键值对的形式存在
  • 可以用数字符号字符串作为对象的键,但它们最终会被转换成字符串(自动)
  • 不带引号的键可以直接作为对象的属性名,但必须符合命名规范,带引号的键可以任意命名

image.png

// An object literal with two key-value pairs
let spaceship = {
  'Fuel Type': 'diesel',
  color: 'silver'
};

访问对象属性

访问对象属性有两种方式:

1.通过dot

let spaceship = {
  homePlanet: 'Earth',
  color: 'silver'
};
spaceship.homePlanet; // Returns 'Earth',
spaceship.color; // Returns 'silver',

image.png

  • 如果我们访问不存在的属性,就会得到undefined

2.方括号表示法

let spaceship = {
  'Fuel Type': 'Turbo Fuel',
  'Active Duty': true,
  homePlanet: 'Earth',
  numCrew: 5
};
spaceship['Active Duty'];   // Returns true
spaceship['Fuel Type'];   // Returns  'Turbo Fuel'
spaceship['numCrew'];   // Returns 5
spaceship['!!!!!!!!!!!!!!!'];   // Returns undefined

属性的赋值

image.png

  • 如果属性已经存在在对象中,则会代替旧值
  • 如果属性不存在方法中,则会创建一个新的属性
  • 尽管我们无法直接改变const变量所指向的对象,但是我们可以改变这个对象的属性或创建新的属性
const spaceship = {type: 'shuttle'};
spaceship = {type: 'alien'}; // TypeError: Assignment to constant variable.
spaceship.type = 'alien'; // Changes the value of the type property
spaceship.speed = 'Mach 5'; // Creates a new key of 'speed' with a value of 'Mach 5'

也可以通过delete关键字删除属性:

  • 删除对象属性时,点表示法只支持符合命名规范的属性
  • 包括空格,特殊字符,动态生成的属性名,只能用方括号表示法
const spaceship = {
  'Fuel Type': 'Turbo Fuel',
  homePlanet: 'Earth',
  mission: 'Explore the universe' 
};
 
delete spaceship.mission;  // Removes the mission property
delete spaceship['Secret Mission'];

对象中的方法

当函数作为数据存储在对象中时,我们可以称其为方法

变量是对象有什么,方法是对象可以做什么

在对象中创建方法,key就是函数名,value就是匿名函数表达式:

const alienShip = {
  invade: function () { 
    console.log('Hello! We have come to dominate your planet. Instead of Earth, it shall be called New Xaculon.')
  }
};

在es6之后我们可以省略冒号和function关键字:

const alienShip = {
  invade () { 
    console.log('Hello! We have come to dominate your planet. Instead of Earth, it shall be called New Xaculon.')
  }
};

对象函数的调用是通过对象名和方法名:

alienShip.invade(); // Prints 'Hello! We have come to dominate your planet. Instead of Earth, it shall be called New Xaculon.'

同一个对象中的方法与方法要用逗号隔开:

let alienShip = {
  retreat(){
    console.log(retreatMessage);
  },
  takeOff(){
    console.log('Spim... Borp... Glix... Blastoff!');
  }
};

嵌套对象

在实际应用中,对象经常是嵌套的,即对象可以将其他对象作为变量

const spaceship = {
     telescope: {
        yearBuilt: 2018,
        model: '91031-XLT',
        focalLength: 2032 
     },
    crew: {
        captain: { 
            name: 'Sandra', 
            degree: 'Computer Engineering', 
            encourageTeam() { console.log('We got this!') } 
         }
    },
    engine: {
        model: 'Nimbus2000'
     },
     nanoelectronics: {
         computer: {
            terabytes: 100,
            monitors: 'HD'
         },
        'back-up': {
           battery: 'Lithium',
           terabytes: 50
         }
    }
}; 

链式调用:

spaceship.nanoelectronics['back-up'].battery; // Returns 'Lithium'

按引用传递

js中对象是根据引用传递的

  homePlanet : 'Earth',
  color : 'silver'
};
 
let paintIt = obj => {
  obj.color = 'glorious gold'
};
 
paintIt(spaceship);
 
spaceship.color // Returns 'glorious gold'
 

const spaceship = {
  homePlanet : 'Earth',
  color : 'silver'
};
 
let paintIt = obj => {
  obj.color = 'glorious gold'
};
 
paintIt(spaceship);
 
spaceship.color // Returns 'glorious gold'
 

let spaceship = {
  homePlanet : 'Earth',
  color : 'red'
};
let tryReassignment = obj => {
  obj = {
    identified : false, 
    'transport type' : 'flying'
  }
  console.log(obj) // Prints {'identified': false, 'transport type': 'flying'}
 
};
tryReassignment(spaceship) // The attempt at reassignment does not work.
spaceship // Still returns {homePlanet : 'Earth', color : 'red'};
 
spaceship = {
  identified : false, 
  'transport type': 'flying'
}; // Regular reassignment still works.

  • 当我们尝试重新赋值传入对象时,赋值并没有生效
  • 当我们将 spaceship 传递给那个函数时,obj 变成了指向 spaceship 对象内存位置的引用,但并没有指向 spaceship 变量。
  • 这是因为 tryReassignment() 函数的 obj 参数是它自己的一个变量,tryReassignment() 函数的代码体完全不知道 spaceship 变量的存在
  • 所以obj指向了一个新的对象,但这只会影响obj变量,不会印象外部的spaceship变量

通过对象循环

我们之前学习了如何通过数组的数字索引遍历数组,但对象中的键值对是无序的,js提供了一种通过for…in语法遍历对象的解决方案

for…in会执行给出的代码块中每一个变量

let spaceship = {
  crew: {
    captain: { 
      name: 'Lily', 
      degree: 'Computer Engineering', 
      cheerTeam() { console.log('You got this!') } 
    },
    'chief officer': { 
      name: 'Dan', 
      degree: 'Aerospace Engineering', 
      agree() { console.log('I agree, captain!') } 
    },
    medic: { 
      name: 'Clementine', 
      degree: 'Physics', 
      announce() { console.log(`Jets on!`) } },
    translator: {
      name: 'Shauna', 
      degree: 'Conservation Science', 
      powerFuel() { console.log('The tank is full!') } 
    }
  }
}; 

// for...in
for (let crewMember in spaceship.crew) {
  console.log(`${crewMember}: ${spaceship.crew[crewMember].name}`);
}

  • for…in循环会遍历spaceship.crew中的每一个元素,在每次迭代中,crewMember会设置为spaceship.crew中的一个键,从而使我们能够打印出每个船员的名字

对象的进一步学习

this

const goat = {
  dietType: 'herbivore',
  makeSound() {
    console.log('baaa');
  }
};

看起来可以成功运行,但如果我们再加一个diet方法让它输出dietType的值?

const goat = {
  dietType: 'herbivore',
  makeSound() {
    console.log('baaa');
  },
  diet() {
    console.log(dietType);
  }
};
goat.diet(); 
// Output will be "ReferenceError: dietType is not defined"
  • 为什么dietType会成为未定义的变量,这是因为在diet的作用域内,我们不能自动访问goat的其他属性,这时需要用到this关键字
const goat = {
  dietType: 'herbivore',
  makeSound() {
    console.log('baaa');
  },
  diet() {
    console.log(this.dietType);
  }
};

goat.diet(); 
// Output: herbivore

this关键字引用的是调用对象,它提供了访问调用对象属性的能力

箭头函数和this

当我们在方法中使用箭头函数和this时,情况会变得复杂一点,例如:

const goat = {
  dietType: 'herbivore',
  makeSound() {
    console.log('baaa');
  },
  diet: () => {
    console.log(this.dietType);
  }
};

goat.diet(); // 输出 undefined

从注释中可以看出,调用goat.diet()后,打印的结果是undefined

当diet方法是由箭头函数定义时,箭头函数会自动绑定this,并且this的值不会指向调用该方法的对象,而是指向定义时的作用域,在上面的代码段中,this的值是全局对象(浏览器下为windows对象,Node.js中位global对象,而全局对象中并没有dietType视讯跟,因此返回的是undefined)

所以在方法中使用this时,应该尽量避免使用箭头函数

私有

访问和修改变量是项目中最基本的工作,然而有时我们不想让其他代码随意修改和访问变量

在讨论对象的隐私性时,我们将其定义为只有特定的属性应当是可变的或能够改变其值的。

在js中,通常约定私有变量前有下划线:

const bankAccount = {
  _amount: 1000
}
bankAccount._amount = 1000000;

Getters

Getters是第一种拿到和返回对象中内部变量的方法,但是它的作用不止取数

const person = {
  _firstName: 'John',
  _lastName: 'Doe',
  get fullName() {
    if (this._firstName && this._lastName){
      return `${this._firstName} ${this._lastName}`;
    } else {
      return 'Missing a first name or a last name.';
    }
  }
}

// To call the getter method: 
person.fullName; // 'John Doe'

  • 我们通过get关键字声明了一个函数
  • 在函数中加了条件判断
  • getter函数的调用不用加括号,所以看起来会像属性

getter的好处:

  • getter可以在拿变量时同时对数据进行操作
  • getter可以根据条件返回不同的数据
  • getter中可以使用this
  • 使代码更加易读

使用getter/setter方法时需要注意的是,属性不能与getter/setter方法同名,如果这样做,调用时会导致无限调用栈错误

setter

我们同样可以创建setter方法来赋值对象中已存在的变量

const person = {
  _age: 37,
  set age(newAge){
    if (typeof newAge === 'number'){
      this._age = newAge;
    } else {
      console.log('You must assign a number to age');
    }
  }
};

  • 可以注意到上例中,setter可以起到数据检验的作用
  • 当使用setter方法时,只有this._age变量会改变
  • 可以根据变量的值有不同的输出
person.age = 40;
console.log(person._age); // Logs: 40
person.age = '40'; // Logs: You must assign a number to age
  • setter方法不需要加括号调用,像setter方法一样,可以检查输入,执行变量操作,使代码更有易读性
  • 尽管有了setter函数,还是有可能直接修改对象中的变量,例如上面的例子中,我们直接修改变量:
person._age = 'forty-five'
console.log(person._age); // Prints forty-five

例子:

const robot = {
  _model: '1E78V2',
  _energyLevel: 100,
  _numOfSensors: 15,
  get numOfSensors(){
    if(typeof this._numOfSensors === 'number'){
      return this._numOfSensors;
    } else {
      return 'Sensors are currently down.'
    }
  },
  set numOfSensors(num){
    if(typeof num === 'number' && num >= 0){
      this._numOfSensors = num;
    }else{
      console.log('Pass in a number that is greater than or equal to 0');
    }
  }
  
};

robot.numOfSensors = 100;

console.log(robot.numOfSensors);



工厂函数

我们已经掌握了创建单个对象的方法,但是很多时候我们想快速创建多个实例,这就需要引进工厂函数

工厂函数是可以返回对象并可以复用创建多种对象实例,也可以定制返回的对象

const monsterFactory = (name, age, energySource, catchPhrase) => {
  return { 
    name: name,
    age: age, 
    energySource: energySource,
    scare() {
      console.log(catchPhrase);
    } 
  }
};

  • monsterFactory 有四个参数,并且会返回一个对象
const ghost = monsterFactory('Ghouly', 251, 'ectoplasm', 'BOO!');
ghost.scare(); // 'BOO!'

现在我们通过调用monsterFactory方法获取了一个ghost对象

属性值简写

es6新增特性

const monsterFactory = (name, age) => {
  return { 
    name: name,
    age: age
  }
};

可以简写成:

const monsterFactory = (name, age) => {
  return { 
    name,
    age 
  }
};

解构赋值

我们经常想从对象中提取键值对并将它们存储在对象中

const vampire = {
  name: 'Dracula',
  residence: 'Transylvania',
  preferences: {
    day: 'stay inside',
    night: 'satisfy appetite'
  }
};

如果我们想要提取出residence并存储到变量中,通常会这么做:

const residence = vampire.residence; 
console.log(residence); // Prints 'Transylvania' 

然而,我们也可以通过解构赋值提取对象中的变量,例如:

const { residence } = vampire; 
console.log(residence); // Prints 'Transylvania'
  • vampire :对象名
  • residence : 要提取的变量名

我们也可以用解构赋值获取嵌套中的对象变量:

const { day } = vampire.preferences; 
console.log(day); // Prints 'stay inside'

内置对象方法

Object.assign()Object.entries()Object.keys()

Object也有一些内置对象方法,例如Object.assign()Object.entries()Object.keys()

const robot = {
	model: 'SAL-1000',
  mobile: true,
  sentient: false,
  armor: 'Steel-plated',
  energyLevel: 75
};

// What is missing in the following method call?
const robotKeys = Object.keys(robot);
  • Object.Keys():获取对象的所有键,并将它们存储到数组中。
  • Object.entries():返回一个数组,数组中包含多个子数组,每个子数组都包含对象中属性的键和值
  • Object.assign():将一个或多个源对象的属性复制到目标对象中
const newRobot = {};
Object.assign(newRobot,robot,{laserBlaster: true, voiceRecognition: true});
console.log(newRobot);

在这段代码中,assign会将属性复制到target也就是第一个变量上,之后的变量都是源对象,它们的属性会被复制到 target