有两种创建对象的方式
let user = new Object(); // “构造函数” 的语法
let user = {}; // “字面量” 的语法
文本和属性
可以在创建对象的时候,立即将一些属性以键值对的形式放到 {...}
中。
let user = { // 一个对象
name: "John", // 键 "name",值 "John"
age: 30 // 键 "age",值 30
};
访问属性值:
alert( user.name ); // John
alert( user.age ); // 30
属性的值可以使任意类型:
user.age = true;
移除属性:
delete user.age;
我们也可以用多字词语来作为属性名,但必须给它们加上引号:
let user = {
name: "John",
age: 30,
"likes birds": true // 多词属性名必须加引号
};
注意:
使用 const 声明的对象是可以被修改的
const user = {
name: "John"
};
user.name = "Pete"; // (*)
alert(user.name); // Pete
const
声明仅固定了 user
的值,而不是值(该对象)里面的内容。
仅当我们尝试将 user=...
作为一个整体进行赋值时,const
会抛出错误。
方括号
// 这将提示有语法错误
user.likes birds = true
点符号要求 key
是有效的变量标识符。这意味着:不包含空格,不以数字开头,也不包含特殊字符(允许使用 $
和 _
)。
此时需要使用方括号来处理对象的属性
let user = {};
// 设置
user["likes birds"] = true;
// 读取
alert(user["likes birds"]); // true
// 删除
delete user["likes birds"];
另外:还可以这样使用
let user = {
name: "John",
age: 30
};
let key = prompt("What do you want to know about the user?", "name");
// 访问变量
alert( user[key] ); // John(如果输入 "name")
但是点符号不能这样使用
计算属性
当创建一个对象时,我们可以在对象字面量中使用方括号。这叫做 计算属性。
let fruit = prompt("Which fruit to buy?", "apple");
let bag = {
[fruit]: 5, // 属性名是从 fruit 变量中得到的
};
alert( bag.apple ); // 5 如果 fruit="apple"
计算属性的含义很简单:[fruit]
含义是属性名应该从 fruit
变量中获取
let fruit = 'apple';
let bag = {
[fruit + 'Computers']: 5 // bag.appleComputers = 5
};
属性名称限制
任何关键字都可以当做属性名称,因为最后都会被转换为字符串
属性存在性检测
相比于其他语言,JavaScript 的对象有一个需要注意的特性:能够被访问任何属性。即使属性不存在也不会报错!
读取不存在的属性只会得到 undefined
。所以我们可以很容易地判断一个属性是否存在:
let user = {};
alert( user.noSuchProperty === undefined ); // true 意思是没有这个属性
可以使用in来检测属性是否存在
let user = { name: "John", age: 30 };
alert( "age" in user ); // true,user.age 存在
alert( "blabla" in user ); // false,user.blabla 不存在。
in
的左边必须是 属性名。通常是一个带引号的字符串。
如果我们省略引号,就意味着左边是一个变量,它应该包含要判断的实际属性名。例如:
let user = { age: 30 };
let key = "age";
alert( key in user ); // true,属性 "age" 存在
正常来说,想要判断属性是否存在只需要将属性 === undefined即可,但是在一种情况下会判断失败:属性定义了,但是属性值是undefined,此时需要用in来判断。
let obj = {
test: undefined
};
alert( obj.test ); // 显示 undefined,所以属性不存在?
alert( "test" in obj ); // true,属性存在!
检测对象类型
使用instaceof来检测对象的类型
result = variable instanceof constructor;
console.log(person instanceof Object);
console.log(colors instanceof Array);
遍历对象的所有属性
我们可以使用for…in来循环遍历一个对象的所有属性
let user = {
name: "John",
age: 30,
isAdmin: true
};
for (let key in user) {
// keys
alert( key ); // name, age, isAdmin
// 属性键的值
alert( user[key] ); // John, 30, true
}
属性排列问题
整数属性的排列为从小到大,而其余属性会根据创建的顺序依次显示
这里的“整数属性”指的是一个可以在不做任何更改的情况下与一个整数进行相互转换的字符串。
所以,“49” 是一个整数属性名,因为我们把它转换成整数,再转换回来,它还是一样的。但是 “+49” 和 “1.2” 就不行了
如果不想让整数属性进行排列,而是想要正常的按照创建顺序排列的话,可以再整数属性的前面加上+。
对象与原始类型其中一个基本的区别是:对象“通过引用的形式”被存储和拷贝。
变量存储的不是对象自身,而是该对象的“内存地址”,换句话说就是一个对该对象的“引用”。
换句话说:创建对象时会放在内存地址中,变量存储的就是对这个地址的引用。
当一个对象变量被拷贝 —— 引用则被拷贝,而该对象并没有被复制。
let user = { name: "John" };
let admin = user; // 拷贝引用
admin存储的也是对对象user的引用,即两个变量存储的都是相同对象的引用,
一个变量(对象的引用)改变对象的属性值,另一个引用也能看到这个改变,两个引用都可以改变对象的属性。
let user = { name: 'John' };
let admin = user;
admin.name = 'Pete'; // 通过 "admin" 引用来修改
alert(user.name); // 'Pete',修改能通过 "user" 引用看到
比较
对于对象来说,==
和===
都是一样的,都是判断是否为同一个对象,
let a = {};
let b = a; // 拷贝引用
alert( a == b ); // true,都引用同一对象
alert( a === b ); // true
而两个空对象并不是同一个对象,因为内存地址并不是一个地方,
let a = {};
let b = {}; // 两个独立的对象
alert( a == b ); // false
浅拷贝(克隆与合并)
如果想要完全的克隆一个对象,而不是引用,那么可以:
let user = {
name: "John",
age: 30
};
let clone = {}; // 新的空对象
// 将 user 中所有的属性拷贝到其中
for (let key in user) {
clone[key] = user[key];
}
// 现在 clone 是带有相同内容的完全独立的对象
clone.name = "Pete"; // 改变了其中的数据
alert( user.name ); // 原来的对象中的 name 属性依然是 John
Object.assign(dest, [src1, src2, src3...])
1.第一个参数 dest
是指目标对象。
2.更后面的参数 src1, ..., srcN
(可按需传递多个参数)是源对象。
3.该方法将所有源对象的属性拷贝到目标对象 dest
中。换句话说,从第二个开始的所有参数的属性都被拷贝到第一个参数的对象中。
4.调用结果返回 dest
。
let user = { name: "John" };
let permissions1 = { canView: true };
let permissions2 = { canEdit: true };
// 将 permissions1 和 permissions2 中的所有属性都拷贝到 user 中
Object.assign(user, permissions1, permissions2);
// 现在 user = { name: "John", canView: true, canEdit: true }
如果拷贝时遇到相同属性,则会覆盖原来的属性
简单克隆:
let user = {
name: "John",
age: 30
};
let clone = Object.assign({}, user); //将所有属性拷贝到了一个空对象中,并返回这个新对象
深拷贝(深层克隆)
当属性是对其他对象的引用时,需要用到深拷贝
let user = {
name: "John",
sizes: {
height: 182,
width: 50
}
};
alert( user.sizes.height ); // 182
此时不能通过简单地将属性sizes拷贝来实现
结构化拷贝算法:我们应该使用会检查每个 user[key]
的值的克隆循环,如果值是一个对象,那么也要复制它的结构。这就叫“深拷贝”。
或者使用javascript库中的lodash中的_.cloneDeep(obj)来实现,避免重复造轮子
举例:
let user = {
name: "John",
age: 30
};
user.sayHi = function() {
alert("Hello!");
};
user.sayHi(); // Hello!
作为对象属性的函数被称为 方法。
得到了 user
对象的 sayHi
方法。
简写一下
user = {
sayHi: function() {
alert("Hello");
}
};
// 方法简写看起来更好,对吧?
let user = {
sayHi() { // 与 "sayHi: function()" 一样
alert("Hello");
}
};
方法中的this
通常,对象方法需要访问对象中存储的信息才能完成其工作。
例如,user.sayHi()
中的代码可能需要用到 user
的 name 属性。
为了访问该对象,方法中可以使用 this
关键字。
this
的值就是在点之前的这个对象,即调用该方法的对象。
let user = {
name: "John",
age: 30,
sayHi() {
// "this" 指的是“当前的对象”
alert(this.name);
}
};
user.sayHi(); // John
JavaScript 中的 this
可以用于任何函数。
this
的值是在代码运行时计算出来的,它取决于代码上下文。
在没有对象的情况下调用this
在没有对象的情况下调用:this == undefined
我们甚至可以在没有对象的情况下调用函数:
function sayHi() {
alert(this);
}
sayHi(); // undefined
在这种情况下,严格模式下的 this
值为 undefined
。如果我们尝试访问 this.name
,将会报错。
在非严格模式的情况下,this
将会是 全局对象
箭头函数没有自己的this
箭头函数有些特别:它们没有自己的 this
。如果我们在这样的函数中引用 this
,this
值取决于外部“正常的”函数。
let user = {
firstName: "Ilya",
sayHi() {
let arrow = () => alert(this.firstName);
arrow();
}
};
user.sayHi(); // Ilya
构造函数和new用于创造多个类似的对象
构造函数
构造函数有两个约定-
"new"
操作符来执行。function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("Jack");
alert(user.name); // Jack
alert(user.isAdmin); // false
当一个函数被使用 new
操作符执行时,它按照以下步骤:
this
。this
,为其添加新的属性。this
的值。function User(name) {
// this = {};(隐式创建)
// 添加属性到 this
this.name = name;
this.isAdmin = false;
// return this;(隐式返回)
}
new function() { … }
如果我们有许多行用于创建单个复杂对象的代码,我们可以将它们封装在构造函数中,像这样
let user = new function() {
this.name = "John";
this.isAdmin = false;
// ……用于用户创建的其他代码
// 也许是复杂的逻辑和语句
// 局部变量等
};
构造器不能被再次调用,因为它不保存在任何地方,只是被创建和调用。因此,这个技巧旨在封装构建单个对象的代码,而无需将来重用。
new.target
在一个函数内部,我们可以使用 new.target
属性来检查它是否被使用 new
进行调用了。
对于常规调用,它为空,对于使用 new
的调用,则等于该函数:
function User() {
alert(new.target);
}
// 不带 "new":
User(); // undefined
// 带 "new":
new User(); // function User { ... }
构造器的return
通常,构造器没有 return
语句。它们的任务是将所有必要的东西写入 this
,并自动转换为结果。
return
返回的是一个对象,则返回这个对象,而不是 this
。return
返回的是一个原始类型,则忽略。换句话说,带有对象的 return
返回该对象,在所有其他情况下返回 this
。
例如,这里 return
通过返回一个对象覆盖 this
:
function BigUser() {
this.name = "John";
return { name: "Godzilla" }; // <-- 返回这个对象
}
alert( new BigUser().name ); // Godzilla,得到了那个对象
function SmallUser() {
this.name = "John";
return; // <-- 返回 this
}
alert( new SmallUser().name ); // John
构造器中的方法
构造函数中还可以定义函数
function User(name) {
this.name = name;
this.sayHi = function() {
alert( "My name is: " + this.name );
};
}
let john = new User("John");
john.sayHi(); // My name is: John
1.使用Array构造函数
let colors = new Array();
//如果知道数组的大小,可以传入一个数值,然后length属性就会被自动创建并设置成这个值
let colors1 = new Array(2);
//也可以传入几个要保存的数据
let colors2 = new Array("red","blue",3);
//也可以省略new关键字
let name = Array();
2.数组字面量创建
let colors = ["red","blue","green"];
//注意:在使用数组字面量表示法创建数组不会调用Array构造函数
3.from()方法创建
from()方法是es6新增的静态方法,用于将类数组结构转换为数组实例
Array().from()的第一个参数是一个类数组对象(任何可迭代的结构),或者有一个length属性和课索引元素的结构
console.log(Array.from("hello")) //["h","e","l","l","o"]
//也可以将集合和映射转换为一个新数组
Array.from()对现有数组进行浅复制
const a1 = [1,2,3,4];
const a1 = Array.from(a1);
console.log(a1); //[1,2,3,4]
alert(a1 === a2);
在symbol.iterator也可以使用
arguments对象也可以被转换为数组
function getArgsArray(){
return Array.from(arguments);
}
console.log(getArgsArray(1,2,3,4)) //[1,2,3,4]
from()也能转换带有必要属性的自定义对象
const arrayLikeObject = {
0:1,
1:2,
2:3,
3:4
length:4
}
console.log(Array.from(arrayLikeObject)); //[1,2,3,4]
from()方法的第二个参数是可选的映射函数参数,这个函数可以直接增强新数组的值,
第三个可选的参数用于指定映射函数中this的值,但是在箭头函数中不适用
const a1 = [1,2,3];
cosnt a2 = Array.from(a1,x =>x**2);
const a3 = Array.from(a1,function(x) { return x**this.exponent },{exponent:2});
console.log(a2); //[1,4,9]
console.log(a3); //[1,4,9]
4,Array.of()方法创建
Array.of()可以把一组参数转换为数组,此方法用于替代之前的Array.prototype.slice.call(arguments)
console.log(Array.of(1,2,3,4)); //[1,2,3,4]
console.log(Array.of(undefined)); //[undefined]
const options = [,,,,,]; //创建包含五个元素的数组
console.log(options.length) //5
es6中将这个空位当成存在的元素,值为undefined
要获取数组某一项的值,需要使用中括号
let colors = [1,2,3,4];
alert(colors[1]); //显示第二项
colors[1] = 1; //修改第二项
colors[4] = 5; //添加第五项
如果索引小于数组包含的元素数,则返回相应位置的元素,如果索引大于数组包含的元素数,则数组长度会自动扩充到改索引值+1的大小,且新增的元素值为undefined
数组的length并不是只读的,可以改变length的值,可以从数组末尾删除或添加元素,
let colors = ["red","blue","green"];
colors.length = 2;
alert(colors[2]); //undefined
//添加元素
let colors = ["red","blue","green"];
colors.length = 4;
alert(colors[3]); //undefined
//向数组末尾添加元素
let colors = ["red","blue","green"];
colors[colors.length] = "black";
colors[colors.length] = "brown";
//此时length会自动添加1
如果在单网页中检测数组可以使用instanceof
if(value instanceof Array){
//操作数组
}
但是使用instanceof的前提是只有一个全局执行上下文
Array.isArray()方法可以用来检测是否为数组
if(Array.isArray(value)){
//操作数组
}
es6中,array原型暴露了检索数组的方法:
利用.from()方法可以很轻松的构建数组实例
const a = ["foo","bar","baz","qux"];
const aKeys = Array.from(a.keys()); //[0,1,2,3]
const aValues = Array.from(a.values()); //["foo","bar","baz","qux"]
const aEntries = Array.from(a.entries()); //[[0,"foo"],[1,"bar"],[2,"baz"],[3,"quz"]]
拆分键值对:
const a = ["foo","bar","baz","qux"];
for(const [idx,element] of a.entries()){
alert(idx);
alert(element);
}
es6新增:
copyWidth()方法会按照指定范围浅复制数组中的部分内容,然后将他们插入到指定索引开始的位置,开始索引和结束索引与fill相同(参数:复制元素的索引,开始索引,结束索引)
const ins = [0,1,2,3,4,5,6,7,8,9];
ins.copyWidthin(5);
console.log(ins); //[0,1,2,3,4,0,1,2,3,4]
ins.copyWithin(4,0,3);
console.log(ins); //[0,1,2,3,0,1,2,7,8,9]
fill()方法可以向一个已经存在的数组中插入全部或部分相同的值,开始索引用于指定开始填充的位置,他是可选的,如果不提供结束索引,则一直填充到数组末尾,复制索引从数组末尾开始计算,也可以将福索引想象成数组长度加上它得到的一个正索引
const zeros = [0,0,0,0,0];
//用5填充整个数组
zeros.fill(5);
console.log(zeros); //[5,5,5,5,5]
zeros.fill(0); //重置
zeros.fill(7,1,3); //用7填充索引>=1,且<3的元素
两种方法索引规则:默认忽略超出数组边界,零长度和方向相反的索引范围,(什么都不操作),如果部分索引可用,则操作可用索引部分
栈是一种后进先出的结构,栈的插入和删除都在栈顶发生,es提供了push()和pop()方法
push()方法接受任意数量的参数,并将它们添加到数组末尾,返回数组的新长度
pop()方法则用于删除数组的最后一项,同时减少长度值,返回删除的项
let colors = new Array();
colors.push("red","blue");
console.log(colors); //[ 'red', 'blue' ]
let result = colors.pop();
console.log(result); //blue
console.log(colors.length); //1
队列是先进先出的结构,在列表末尾添加数据,在头部获取数据
一般使用push(),shift()模拟队列,还可以使用unshift()和pop()在相反方向上模拟队列
let colors = new Array();
colors.push("red","blue","white");
let first = colors.shift();
console.log(first); //red
let addLength = colors.unshift("1","2");
console.log(addLength); //4
console.log(colors); //[ '1', '2', 'blue', 'white' ]
解决方法是:在sort内传入一个函数参数,例如:(如果想要降序排序,可以将1和-1调换)
let a = [0,1,5,10,15];
a.sort((a,b)=>{ a<b? 1:a>b? -1:0 });
console.log(a);
//或者传入下列函数
function compare(val1,val2){
return val2-val1;
}
reverse()和sort()函数都返回调用他们数组的引用(即数组本身)
let nums = [1,2,3,4];
let nums2 = nums.concat(5,[6,7]);
console.log(nums2);
在方法内部,有一个符号位Symbol.isConcatSpreadable,这个符号能够阻止concat打平参数数组,把这个值设置为true可以强制打平类数组对象,值为true代表打平数组,值为false代表不打平按照原来的结构合并到原数组
let colors = ["red","green","blue"];
let newColors = ["black","white"];
let moreColors = {
length:2,
0:"pink",
1:"cyan"
}
// newColors[Symbol.isConcatSpreadable] = false;
let colors2 = colors.concat("yellow",newColors);
let colors3 = colors.concat(moreColors);
console.log(colors); //[ 'red', 'green', 'blue' ]
console.log(colors2); //
console.log(colors3);
两种搜索方法:按严格相等搜索和按断言函数搜索
严格搜索:都接受两个桉树:要查找的元素和一个可选的起始搜索位置
断言搜索:允许按照定义的断言函数搜索数组,每个索引都会调用这个函数,断言函数的返回值表示是否匹配
断言函数三个参数:元素(数组中当前搜索的元素),索引(当前元素的索引),数组本身,
const people = [
{
name:"Matt",
age:27
},
{
name:"Nicholas",
age:29
}
];
console.log(people.find((element,index,array)=>element.age<28));
//{name:"Matt",age:27}
五个迭代方法:每个方法都接受两个参数:以每一项为参数运行的函数,可选的作为函数运行上下文的作用域对象(影响函数中this的值)。传给每个方法的函数接受3个参数:数组元素,元素索引,数组本身
这些方法都不改变调用他们的数组
这两个方法都会迭代数组的所有项,并在此基础上构建一个最终的返回值,
两个参数:对每一项都会运行的归并函数,可选的以之为归并起点的初始值
传给reduce和reduceright的函数接受四个参数:上一个归并值,当前项,当前项的索引,数组本身。
let values = [1,2,3,4,5];
let sum = values.reduce((prev,cur,index,array)=>prev+cur);
console.log(sum); //15
函数返回的任何值都会作为下一次调用相同函数的第一个参数(上一个归并值),如果没有指定可选的第二个参数,默认第一次迭代从数组的第二项开始,因此传给归并函数的第一个参数是数组的第一项,第二个参数是数组的第二项
用得不多,暂时省略
映射:可以实现键值的结构
使用构造函数创建一个空映射:
const m = new Map();
//如果想要在创建的同事初始化实例,可以给map构造函数传入一个可迭代的对象,需要包含键值对数组
const m1 = new Map([
["key1":"val1"],
["key2":"val2"],
["key3":"val3"],
]);
console.log(m1.size); //3
//使用自定义迭代器初始化映射
const m2 = new Map({
[Symbol.iterator]:function*() {
yield ["key1","val1"];
yield ["key2","val2"];
yield ["key3","val3"];
}
});
console.log(m2.size); //3
map可以使用任何数据类型作为键,map内部使用SameValueZero(相当于使用严格对象相等的标准来检查)
Map实例会维护键值对的插入顺序,实例提供一个迭代器(iterator),能以插入顺序生成[key,value]形式的数组,可以通过entries()方法,(或者Symbol.iterator属性,他引用entries)取得这个迭代器
const m = new Map([
["key1","val1"],
["key2","val2"],
["key3","val3"]
])
console.log(m.entries === m[Symbol.iterator]) //true
for(let pair of m.entries()){
console.log(pair);
}
//[key1,val1]
//[key2,val2]
//[key3,val3]
for(let pair of m[Symbol.iterator]()){
console.log(pair);
}
因为entries是默认迭代器,可以直接进行拓展操作,将映射转换为数组
const m = new Map([
["key1","val1"],
["key2","val2"],
["key3","val3"]
]);
console.log([...m]); //[["key1","val1"],["key2","val2"],["key3","val3"]]
还可以使用回调函数进行迭代:forEach(callback,opt_thisArg) opt_thisArg用于重写this
const m = new Map([
["key1","val1"],
["key2","val2"],
["key3","val3"]
]);
m.forEach((val,key)=>alert(`${key}->${val}`));
key()和m.values()和entries一样,都是迭代器,可以迭代键和值
键和值在内部是无法修改的,即使修改了也不会改变遍历的结果
WeakMap是弱映射
const m = new WeakMap();
弱映射中的键只能是Object或者继承自Object的类型,使用其他类型会抛出错误
初始化之后可以使用:
弱映射中的weak意思是这些键不属于正事的引用,所以不会阻止垃圾回收,但是只要键存在,键值对就会存在于映射中,并被当做对值的引用,因此就不会被当做垃圾回收
当作为键的对象没有其他引用时,这个对象键就会被当做垃圾回收,最后映射变成一个空映射
weakmap不能够迭代,因为可能随时被销毁
集合,es6新增,可以包含任何数据类型作为值
//初始化
const s = new Set();
//如果想同时创建实例,可以传入一个可迭代的对象
const s1 = new Set(["val1","val2","val3"]);
用作值的对象和其他“集合”类型在自己的内容被修改时也不会被改变
const s = new Set();
const objval = {};
const arrval = [];
s.add(objval);
s.add(arrval);
objval.bar = "bar";
arrval.push("bar");
console.log(s.has(objval)); //true
console.log(s.has(arrval)); //true
set会维护值插入时的顺序,因此支持按顺序迭代
提供迭代器和values()
const s = new Set(["val1","val2","val3"]);
for(let value of s.values()) {
alert(value);
}
for(let value of s[Symbol.iterator]()) {
alert(value);
}
还可以使用拓展操作
const s = new Set(["val1","val2","val3"]);
console.log([...s]); //["val1","val2","val3"]
set的entries()方法会返回一个迭代器,可以按照插入顺序产生包含两个元素的数组,这两个元素是集合中每个值的重复出现
const s = new Set(["val1","val2","val3"]);
for(let pairs of s.entries()) {
console.log(pair);
}
//["val1","val1"]
//["val2","val2"]
//["val3","val3"]
也可以类似Map一样,使用回调函数迭代
弱集合
const ws = new WeakSet();
弱集合中的值只能是Object或者继承自Object的类型,
和weakmap一样,没有被引用时会被当做垃圾回收
不能迭代
有四种原生集合类型定义了默认迭代器:
意味着上述的类型都支持顺序迭代,都可以传入for-of循环
都可以兼容拓展操作符,拓展操作符在对可迭代对象执行浅复制时很有用(浅复制只会复制对象引用)
也都支持Array.of()和Array.from()