JS/TS算法---二分查找
引用数据类型有object , array ,function
一 、数组
一、创建数组
1、使用数组字面量方法
var arr1 = []; // 创建一个数组
var arr2 = [20]; // 创建一个包含1项数据为20的数组
var arr3 = ['lily','lucy','Tom']; // 创建一个包含3个字符串的数组
123
2、使用Array构造函数
无参构造
var arr1 = new Array(); // 创建一个空数组
1
带参构造
如果只传一个数值参数,则表示创建一个初始长度为指定数组的空数组
var arr2 = new Array(10); // 创建一个包含10项的数组
1
如果传入一个非数值的参数或者参数大于1,则表示创建一个包含指定元素的数组
var arr3 = new Array('lily','lucy','Tom'); // 创建一个包含3个字符串的数组
1
3、Array.of方法创建数组(es6新增)
Array.of()方法会创建一个包含所有传入参数的数组,而不管参数的数量与类型
let arr1 = Array.of(1,2);
console.log(arr1.length); // 2
let arr2 = Array.of(3);
console.log(arr2.length); // 1
console.log(arr2[0]); // 3
123456
4、Array.from方法创建数组(es6新增)
在js中将非数组对象转换为真正的数组是非常麻烦的。在es6中,将可迭代对象或者类数组对象作为第一个参数传入,Array.from()就能返回一个数组
function arga(...args){ // ...args剩余参数数组,由传递给函数的实际参数提供
let arg = Array.from(args);
console.log(arg);
}
arga(arr1,26,from); // [arr1,26,from]
123456
二、数组方法
数组原型方法主要有以下这些:
- join():用指定的分隔符将数组每一项拼接为字符串
- push():向数组的末尾添加新元素
- pop():删除数组的最后一项
- unshift():向数组首位添加新元素
- shift():删除数组的第一项
- slice():按照条件查找出其中的部分元素
- splice():对数组进行增删改
- filter():过滤功能
- concat():用于连接两个或多个数组
- indexOf():检测当前值在数组中第一次出现的位置索引
- lastIndexOf():检测当前值在数组中最后一次出现的位置索引
- every():判断数组中每一项都是否满足条件
- some():判断数组中是否存在满足条件的项
- includes():判断一个数组是否包含一个指定的值
- sort():对数组的元素进行排序
- reverse():对数组进行倒序
- forEach():es5及以下循环遍历数组每一项
- map():es6循环遍历数组每一项
- find():返回匹配的项
- findIndex():返回匹配位置的索引
- reduce():从数组的第一项开始遍历到最后一项,返回一个最终的值
- reduceRight():从数组的最后一项开始遍历到第一项,返回一个最终的值
- toLocaleString()、toString():将数组转换为字符串
- entries()、keys()、values():遍历数组
- fill() 填充数组
- flat();flatMap(); 按照一个可指定的深度递归遍历数组
- isArray()
- 欢迎补充…
各个方法的基本功能详解
1、join()
join()方法用于把数组中的所有元素转换一个字符串,默认使用逗号作为分隔符
var arr1 = [1,2,3];
console.log(arr1.join()); // 1,2,3
console.log(arr.join('-')); // 1-2-3
console.log(arr); // [1,2,3](原数组不变)
1234
2、push()和pop()
push()方法从数组末尾向数组添加元素,可以添加一个或多个元素,并返回新的长度
pop()方法用于删除数组的最后一个元素并返回删除的元素
var arr1 = ['lily','lucy','Tom'];
var count = arr1.push('Jack','Sean');
console.log(count); // 5
console.log(arr1); // ['lily','lucy','Tom','Jack','Sean']
var item = arr1.pop();
console.log(item); // Sean
console.log(arr1); // ['lily','lucy','Tom','Jack']
12345678
3、unshift()和shift()
unshift()方法可向数组的开头添加一个或更多元素,并返回新的长度
shift()方法用于把数组的第一个元素从其中删除,并返回第一个元素的值
var arr1 = ['lily','lucy','Tom'];
var count = arr1.unshift('Jack','Sean');
console.log(count); // 5
console.log(arr1); // ['Jack','Sean','lily','lucy','Tom']
var item = arr1.shift();
console.log(item); // Jack
console.log(arr1); // [''Sean','lily','lucy','Tom']
12345678
4、slice()
返回从原数组中指定开始下标到结束下标之间的项组成的新数组,可以接受一或两个参数,即要返回项的起始和结束位置(不包括结束位置的项)
用法:array.slice(start,end)
解释:该方法是对数组进行部分截取,并返回一个数组副本;参数start是截取的开始数组索引,end参数等于你要取的最后一个字符的位置值加上1(可选)
12
var arr1 = [1,3,5,7,9,11];
var arrCopy = arr1.slice(1);
var arrCopy2 = arr1.slice(1,4);
var arrCopy3 = arr1.slice(1,-2); // 相当于arr1.slice(1,4);
var arrCopy4 = arr1.slice(-4,-1); // 相当于arr1.slice(2,5);
console.log(arr1); // [1,3,5,7,9,11](原数组没变)
console.log(arrCopy); // [3,5,7,9,11]
console.log(arrCopy2); // [3,5,7]
console.log(arrCopy3); // [3,5,7]
console.log(arrCopy4); // [5,7,9]
//如果不传入参数二,那么将从参数一的索引位置开始截取,一直到数组尾
var a=[1,2,3,4,5,6];
var b=a.slice(0,3); //[1,2,3]
var c=a.slice(3); //[4,5,6]
//如果两个参数中的任何一个是负数,array.length会和它们相加,试图让它们成为非负数,举例说明:
//当只传入一个参数,且是负数时,length会与参数相加,然后再截取
var a=[1,2,3,4,5,6];
var b=a.slice(-1); //[6]
//当只传入一个参数,是负数时,并且参数的绝对值大于数组length时,会截取整个数组
var a=[1,2,3,4,5,6];
var b=a.slice(-6); //[1,2,3,4,5,6]
var c=a.slice(-8); //[1,2,3,4,5,6]
//当传入两个参数一正一负时,length也会先于负数相加后,再截取
var a=[1,2,3,4,5,6];
var b=a.slice(2,-3); //[3]
//当传入一个参数,大于length时,将返回一个空数组
var a=[1,2,3,4,5,6];
var b=a.slice(6); //[]
123456789101112131415161718192021222324252627282930313233
5、splice()
可以实现删除、插入和替换
用法:array.splice(start,deleteCount,item...)
解释:splice方法从array中移除一个或多个数组,并用新的item替换它们。参数start是从数组array中移除元素的开始位置。参数deleteCount是要移除的元素的个数。
如果有额外的参数,那么item会插入到被移除元素的位置上。它返回一个包含被移除元素的数组。
1234
//替换
var a=['a','b','c'];
var b=a.splice(1,1,'e','f'); //a=['a','e','f','c'],b=['b']
//删除
var arr1 = [1,3,5,7,9,11];
var arrRemoved = arr1.splice(0,2);
console.log(arr1); // [5,7,9,11]
console.log(arrRemoved); // [1,3]
// 添加元素 deleteCount=0
var arr1 = [22,3,31,12];
arr1.splice(1,0,12,35);
console.log(arr1); // [22,12,35,3,31,12]
1234567891011121314
6、filter()
filter用于对数组进行过滤。
它创建一个新数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let res = nums.filter((num) => {
return num > 5;
});
console.log(res); // [6, 7, 8, 9, 10]
7、concat()
用于连接两个或多个数组,该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本
var arr1 = [1,3,5,7];
var arrCopy = arr1.concat(9,[11,13]);
console.log(arrCopy); // [1,3,5,7,9,11,13]
console.log(arr1); // [1,3,5,7](原数组未被修改)
1234
8、indexOf()
indexof方法可以在字符串和数组上使用。
indexOf() 方法可返回某个指定的字符串值在字符串、数组中首次出现的位置。
12
arr = ['mfg', '2017', '2016'];
console.log(arr.indexOf('mfg')); // 0
console.log(arr.indexOf('m')); // -1
console.log(arr.indexOf('2017'));// 1
console.log(arr.indexOf(2017)); //
9.lastIndexOf()
lastIndexOf() 方法可返回一个指定的字符串值最后出现的位置,如果指定第二个参数 start,则在一个字符串中的指定位置从后向前搜索。
var str="I am from runoob,welcome to runoob site.";
var n=str.lastIndexOf("runoob"); //28
10、every()
判断数组中每一项都是否满足条件,只有所有项都满足条件,才会返回true
var arr1 = [1,2,3,4,5];
var arr2 = arr1.every.every(x => {
return x < 10;
});
console.log(arr2); // true
var arr3 = arr1.every(x => {
return x < 3;
});
console.log(arr3); // false
12345678910
11、some()
判断数组中是否存在满足条件的项,只要有一项满足条件,就会返回true
var arr1 = [1,2,3,4,5];
var arr2 = arr1.some(x => {
return x < 3;
});
console.log(arr2); // true
var arr3 = arr1.some(x => {
return x < 1;
});
console.log(arr3); // false
12345678910
12、includes()
es7新增,用来判断一个数组、字符串是否包含一个指定的值,使用===运算符来进行值比较,如果是返回true,否则false,参数有两个,第一个是(必填)需要查找的元素值,第二个是(可选)开始查找元素的位置
var arr1 = [22,3,31,12,58];
var includes = arr1.includes(31);
console.log(includes); // true
var includes2 = arr1.includes(31,3); // 从索引3开始查找31是否存在
console.log(includes2); // false
123456
13、sort()
用于对数组的元素进行排序。排序顺序可以是字母或数字,并按升序或降序,默认排序顺序为按字母升序
var arr1 = ['a','d','c','b'];
console.log(arr1.sort()); // ['a','b','c','d']
function compare(value1,value2){
if(value1 < value2){
return -1;
}else if(value1 > value2){
return 1;
}else{
return 0;
}
}
var arr2 = [13,24,51,3];
console.log(arr2.sort(compare)); // [3,13,24,51]
// 如果需要通过比较函数产生降序排序的结果,只要交后比较函数返回的值即可
1234567891011121314151617
14、reverse()
用于颠倒数组中元素的顺序,原数组改变
var arr1 = [13,24,51,3];
console.log(arr1.reverse()); // [3,51,24,13]
console.log(arr1); // [3,51,24,13](原数组改变)
123
15、forEach()
forEach方法中的function回调有三个参数:
第一个参数是遍历的数组内容,
第二个参数是对应的数组索引,
第三个参数是数组本身
1234
var arr = [1,2,3,4];
var sum =0;
arr.forEach(function(value,index,array){
array[index] == value; //结果为true
sum+=value;
});
console.log(sum); //结果为 10
1234567891011
16、map()
返回一个新数组,会按照原始数组元素顺序依次处理元素
let array = [1, 2, 3, 4, 5];
let newArray = array.map((item) => {
return item * item;
})
console.log(newArray) // [1, 4, 9, 16, 25]
1234567
17、find()和findIndex()
都接受两个参数:一个回调函数,一个可选值用于指定回调函数内部的this
该回调函数可接受3个参数:数组的某个元素、该元素对应的索引位置、数组本身,在回调函数第一次返回true时停止查找。
二者的区别是:find()方法返回匹配的值,而findIndex()方法返回匹配位置的索引
let arr = [1,2,3,4,5];
let num = arr.find(item => item > 1);
console.log(num) // 2
let arr = [1,2,3,4,5];
let num = arr.findIndex(item => item > 1);
console.log(num) // 1
1234567
18、reduce()和reduceRight()
都会实现迭代数组的所有项(即累加器),然后构建一个最终返回的值
reduce()方法从数组的第一项开始,逐个遍历到最后
reduceRight()方法从数组的最后一项开始。向前遍历到第一项
4个参数:前一个值、当前值、项的索引和数组对象
12345
var arr1 = [1,2,3,4,5];
var sum = arr1.reduce((prev,cur,index,array) => {
return prev + cur;
},10); // 数组一开始加了一个初始值10,可以不设默认0
console.log(sum); // 25
12345
19、toLocaleString()和toString()
都是将数组转换为字符串
var arr1 = [22,3,31,12];
let str = arr1.toLocaleString();
var str2 = arr1.toString();
console.log(str); // 22,3,31,12
console.log(str2); // 22,3,31,12
123456
20、entries()、keys()和values()
es6新增
entries()、keys()和values()--用于遍历数组。它们都返回一个遍历器对象,可以用for...of循环进行遍历
区别是keys()是对键名的遍历、values()是对键值的遍历、entries()是对键值对的遍历
1234
for(let index of [a,b].keys()){
console.log(index);
}
// 0
// 1
for(let elem of [a,b].values()){
console.log(elem);
}
// a
// b
for(let [index,elem] of [a,b].entries()){
console.log(index,elem);
}
// 0 'a'
// 1 'b'
1234567891011121314151617
如果不使用for…of循环,可以手动调用遍历器对象的next方法,进行遍历
let arr1 = [a,b,c];
let entries = arrr1.entries();
console.log(entries.next().value); // [0,a]
console.log(entries.next().value); // [1,b]
console.log(entries.next().value); // [2,c]
12345
21.flat()与flatMap
var newArray = arr.flat([depth])
// depth 可选:指定要提取嵌套数组的结构深度,默认值为 1。
flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
const list = [:grinning:, :tired_face:, [:grinning:, :tired_face:, ]];
list.flat(Infinity); // [:grinning:, :tired_face:, :grinning:, :tired_face:, ]
// Code
const list = [1, 2, [3, 4, [5, 6]]];
list.flat(Infinity); // [1, 2, 3, 4, 5, 6]
flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与 map 连着深度值为1的 flat 几乎相同,但 flatMap 通常在合并成一种方法的效率稍微高一些。
const list = [:grinning:, :tired_face:, [:grinning:, :tired_face:, ]];
list.flatMap((:white_circle:️) => [:white_circle:️, :white_circle:️ + :white_circle:️ ]); // [:grinning:, :grinning::grinning:, :tired_face:, :tired_face::tired_face:, :grinning:, :grinning::grinning:, :tired_face:, :tired_face::tired_face:, , ]
// Code
const list = [1, 2, 3];
list.flatMap((el) => [el, el * el]); // [1, 1, 2, 4, 3, 9]
22.isArray() 判断是否是数组
实现效果
实现原生 JavaScript 中的Array.isArray()
方法,使用如下:
console.log(Array.isArray([])); // true
console.log(Array.isArray({})); // false
console.log(Array.isArray(123)); // false
console.log(Array.isArray('123')); // false
实现过程
通过Object.prototype.toString.call()
,获取数据类型:
console.log(Object.prototype.toString.call('123')); // [object String]
console.log(Object.prototype.toString.call([])); // [object Array]
console.log(Object.prototype.toString.call({})); // [object Object]
console.log(Object.prototype.toString.call(/123/)); // [object RegExp]
console.log(Object.prototype.toString.call(123)); // [object Number]
console.log(Object.prototype.toString.call(new Date())); // [object Date]
所以,可以通过Object.prototype.toString.call()
得到数据类型,判断是否为[object Array]
即可。
在Array
对象中添加自己的方法myIsArray()
:
Array.myIsArray = function(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
}
验证
console.log(Array.myIsArray([])); // true
console.log(Array.myIsArray({})); // false
console.log(Array.myIsArray(123)); // false
console.log(Array.myIsArray('123')); // false
二、字符串
字符串其实就是数组的一种
charAt() | 返回在指定位置的字符。 |
---|---|
charCodeAt() | 返回在指定的位置的字符的 Unicode 编码。 |
concat() | 连接两个或更多字符串,并返回新的字符串。 |
endsWith() | 判断当前字符串是否是以指定的子字符串结尾的(区分大小写)。 |
fromCharCode() | 将 Unicode 编码转为字符。 |
indexOf() | 返回某个指定的字符串值在字符串中首次出现的位置。 |
includes() | 查找字符串中是否包含指定的子字符串。 |
lastIndexOf() | 从后向前搜索字符串,并从起始位置(0)开始计算返回字符串最后出现的位置。 |
match() | 查找找到一个或多个正则表达式的匹配。 |
repeat() | 复制字符串指定次数,并将它们连接在一起返回。 |
replace() | 在字符串中查找匹配的子串,并替换与正则表达式匹配的子串。 |
replaceAll() | 在字符串中查找匹配的子串,并替换与正则表达式匹配的所有子串。 |
search() | 查找与正则表达式相匹配的值。 |
slice() | 提取字符串的片断,并在新的字符串中返回被提取的部分。 |
split() | 把字符串分割为字符串数组。 |
startsWith() | 查看字符串是否以指定的子字符串开头。 |
substr() | 从起始索引号提取字符串中指定数目的字符。 |
substring() | 提取字符串中两个指定的索引号之间的字符。 |
toLowerCase() | 把字符串转换为小写。 |
toUpperCase() | 把字符串转换为大写。 |
trim() | 去除字符串两边的空白。 |
toLocaleLowerCase() | 根据本地主机的语言环境把字符串转换为小写。 |
toLocaleUpperCase() | 根据本地主机的语言环境把字符串转换为大写。 |
valueOf() | 返回某个字符串对象的原始值。 |
toString() | 返回一个字符串。 |
JS中常用字符串方法
1、查找字符串中的字符串 indexOf() lastIndexOf()
- indexOf() 方法返回字符串中指定文本首次出现的索引(位置);
- lastIndexOf() 方法返回指定文本在字符串中最后一次出现的索引;
- 如果未找到文本, indexOf() 和 lastIndexOf() 均返回 -1;两种方法都接受作为检索起始位置的第二个参数。
2、检索字符串中的字符串 search()
search() 方法搜索特定值的字符串,并返回匹配的位置;
indexOf()、search()这两种方法是不相等的。区别在于:
- search() 方法无法设置第二个开始位置参数。
- indexOf() 方法无法设置更强大的搜索值(正则表达式)。
3、提取部分字符串
有三种提取部分字符串的方法:
- slice(start, end)
- substring(start, end)
- substr(start, length)
slice() 方法
- slice() 提取字符串的某个部分并在新字符串中返回被提取的部分。
- 该方法设置两个参数:起始索引(开始位置),终止索引(结束位置)。
substring() 方法
- substring() 类似于 slice()。
- 不同之处在于 substring() 无法接受负的索引。
substr() 方法
- substr() 类似于 slice()。
- 不同之处在于第二个参数规定被提取部分的长度。
4、替换字符串内容 replace()
- replace() 方法用另一个值替换在字符串中指定的值;
- replace() 方法不会改变调用它的字符串。它返回的是新字符串;
- 默认地,replace() 只替换首个匹配。默认地,replace() 对大小写敏感。
5、转换为大写和小写 toUpperCase() toLowerCase()
- 通过 toUpperCase() 把字符串转换为大写;
- 通过 toLowerCase() 把字符串转换为小写;
6、concat() 方法
- concat() 连接两个或多个字符串:
7、String.trim()
- trim() 方法删除字符串两端的空白符:
8、提取字符串字符
这是两个提取字符串字符的安全方法:
- charAt(position)
- charCodeAt(position)
charAt() 方法
-
charAt() 方法返回字符串中指定下标(位置)的字符串:
-
var str = "HELLO WORLD"; str.charAt(0); // 返回 H
charCodeAt() 方法
-
charCodeAt() 方法返回字符串中指定索引的字符 unicode 编码:
-
var str = "HELLO WORLD"; str.charCodeAt(0); // 返回 72
9.分割字符串
三、对象
从本质上看,Object
是一个构造函数,用于创建对象。
-
1、用键值对(key:value 俗称属性名和属性值)来描述一个对象的特征(每一个对象都是综合体,存在零到多组键值对);
-
2、{ key : value , ...} 每组键值对是key : value 的格式,多组键值对用逗号分隔;
-
3、key 不能是引用数据类型,value 可以是任何的数据类型
let obj = { name: '张三', age: 25, sex: '男', score: [100, 98, 89], fn: function () {} }; console.log(obj);
既然我们的对象主要是由键值对组成,那我们就来说一下键值对
1、键值对的增删改
-
组成:
- 属性名:属性值
-
操作方式:
- 1、对象.属性名 = 属性值
- 2、对象[属性名] = 属性值
‘点’和‘[]’在这里可以理解为‘的’
1、获取
-
获取值:
- 1、对象.属性名
- 基于这种方法操作,属性名就是
.
后面的 - 这种方式,属性名不能是数字
- 基于这种方法操作,属性名就是
- 2、对象[属性名]
- 1、基于这种方式操作,需要保证属性名是一个值(字符串/数字/布尔都可以)
- 2、如果不是值而是一个变量,它会把变量储存的值作为对象的属性名进行操作
- 如果属性名是数字则只能用此方法
如果指定的属性不存在,获取到的属性值是undefined(不会报错)
let n = 100; let obj = { 1: 100 }; console.log(obj[1]); console.log(obj.1); //=>Uncaught SyntaxError: missing ) after argument list 基于点的方式操作有自己的局限性,属性名不能是数字的,不能 对象.数字属性,此时只能用 对象[数字属性] console.log(obj[1]); console.log(obj['1']); //=>其它非字符串格式作为属性名和字符串格式没啥区别 obj.n = 200; //=> {n:200} n是属性名(数据格式是字符串) obj['n'] = 200; //=> {n:200} 和上面的情况一样 obj[n] = 200; //=> {100:200} => obj[100]=200 n本身是一个变量(n和'n'的区别:前者是一个变量,后者是一个字符串的值),它代表的是所存储的值100(是一个数字格式) obj[m] = 200; //=>Uncaught ReferenceError: m is not defined m这个变量没有被定义 obj[true] = 300; //=>{true:300} obj[undefined] = 400; //=>{undefined:400} console.log(obj); 复制代码
- 1、对象.属性名
-
获取所有属性名 - Object.keys(对象):返回结果是包含所有属性名的数组
let obj = { sex: 0 }; //============================ 1)获取指定属性名的属性值 console.log(obj.sex); //=>0 console.log(obj['sex']); //=>0 2)如果指定的属性不存在,获取到的属性值是undefined(不会报错) console.log(obj['age']); //=>undefined 3)获取当前对象中所有的属性名:返回结果是包含所有属性名的数组 console.log(Object.keys(obj)); //=>["sex"] 复制代码
2、增加 | 修改
-
原理:对象的属性名(键)是不允许重复的
- 之前没有这个属性则为新增
- 之前有这个属性名,则是修改对应的属性值
let obj = { sex: 0 }; //============================ obj.name = '张三';//=> 新增 obj['name'] = "李四";//=> 修改 因为此时obj中已经有name:‘张三’存在了,所以此次操作为修改 复制代码
3、删除
- 真删除:彻底把属性从对象中移除
- delete obj.name
- 假删除:当前属性还存在,只不过属性值为空
- obj.name = null
2、Object
构造函数的属性
Object.length
—— 值为1Object.prototype
—— 指向Object
函数的原型对象
对象里的属性分为两种:数据属性 和 访问器属性。首先我们看看数据属性。
1.数据属性
数据属性包含一个保存数据值的位置。值会从这个位置读取,也会写入到这个位置。数据属性有 4 个特性描述它们的行为。
- [[Configurable]]:翻译:adj. 可配置的;结构的 表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特 性,以及是否可以把它改为访问器属性。默认情况下,所有直接定义在对象上的属性的这个特 性都是 true,如前面的例子所示。
- [[Enumerable]]:翻译:adj. 可列举的;可点数的 表示属性是否可以通过 for-in 循环返回。默认情况下,所有直接定义在对 象上的属性的这个特性都是 true,如前面的例子所示。
- [[Writable]]:翻译:adj. 可写的,能写成文的 表示属性的值是否可以被修改。默认情况下,所有直接定义在对象上的属性的 这个特性都是 true,如前面的例子所示。
- [[Value]]:翻译:值 包含属性实际的值。这就是前面提到的那个读取和写入属性值的位置。这个特性 的默认值为 undefined
将属性添加到对象之后 Configurable Enumerable Writable 都会被默认设置为true。而value会被设置为指定值。要修改属性的默认特性,我们必须使用
Object.defineProperty()
翻译:定义属性
js let person = {}; Object.defineProperty(person,"name",{ writable: false, // 设置为false后无法被修改 value: "Nicholas" }); console.log(person.name); person.name = "Greg"; console.log(person.name);
这个例子创建了一个名为 name的属性,并把它赋值 Nicholas,并且它的writeable属性被设置为 false 。那么这个值就不能修改了,在非严格模式下重新给name赋值会被忽略,而在严格模式下 则会抛出错误。
类似的规则,也使用与其它数据属性,下面我们来试试 configurable这个属性,这个属性设置为false后,那么相应的属性就不能删除了。
js let person = {}; Object.defineProperty(person,"name",{ configurable: false, // 设置false后无法被删除了 value: "Nicholas" }); console.log(person.name); delete person.name; console.log(person.name);
还是老样子,非严格模式不报错,严格模式下抛出错误。
此外:此外,一个属性被定义为不可配置之后,就 不能再变回可配置的了。
也就是说这玩意是单程票,改了就改不回去了。这些数据属性,可能我们在开发中用的不多,单通过它们可以更好的理解JS中的对象。
2.访问器属性
访问器属性不包括数据值。它们包含一个获取 getter函数 和一个设置 setter函数,不过这两个函数不是必须的。在设置访问属性时,会调用获取还能输返回一个有效值。在写入访问器属性时,会调用设置函数并传入新值,访问器属性有四个特性描述它们的行为
- [[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特 性,以及是否可以把它改为数据属性。默认情况下,所有直接定义在对象上的属性的这个特性 都是 true。
- [[Enumerable]]:表示属性是否可以通过 for-in 循环返回。默认情况下,所有直接定义在对 象上的属性的这个特性都是 true。
- [[Get]]:获取函数,在读取属性时调用。默认值为 undefined。
- [[Set]]:设置函数,在写入属性时调用。默认值为 undefined。
访问器属性不能直接定义,必须使用Object.defineProperty()
eg:
// 访问器属性 // 定义一个对象,包含私有成员year_和公共成员edition let book = { year_: 2017, edition: 1 }; Object.defineProperty(book,"year",{ get(){ return this.year_; }, set(newValue){ if (newValue > 2017){ this.year_ = newValue; this.edition+=newValue-2017 } } }); book.year = 2018; console.log(book.edition); // 2
这个例子里面 book 对象 有两个默认属性 year_ 和 edition 。这是访问器属性的典型使用场景,即设置一个值会导致其它值发送一些变化。 获取函数和设置函数不一定都需要定义,只定义获取函数意味着属性时只读的,只有设置函数的属性意味着不能读取,非严格模式会返回 undefined 严格模式下会抛出错误。
3.定义多个属性
在一个对象上同时定义多个属性 我们可以采用 Object.defineProperties()方法。它接收两个参数
eg:
let book = {}; Object.defineProperties(book,{ year_:{ value:2017 }, edition:{ value:1 }, year:{ get(){ return this.year_; }, set(newValue){ if(newValue>2017){ this.year_=newValue; this.edition+=newValue-2017 } } } });
定义一个属性和多个属性的唯一区别就是,所有属性是同时定义的,并且数据属性的configurable enumerable writable 特性值都是false。这也意味着。。无法删除,无法遍历,无法修改。。。黑人问号.jpg??????好吧我也不知道为啥这样搞
4.读取属性的特性
使用 Object.getOwnPropertyDescriptor()方法可以取得指定属性的描述接收两个参数 属性所在的对象和属性名,返回一个对象。
eg:
let book = {}; Object.defineProperties(book, { year_: { value: 2017 }, edition: { value: 1 }, year: { get() { return this.year_; }, set(newValue) { if (newValue > 2017) { this.year_ = newValue; this.edition += newValue - 2017 } } } }); let descriptor = Object.getOwnPropertyDescriptor(book, "year_"); console.log(descriptor); descriptor = Object.getOwnPropertyDescriptor(book, "year"); console.log(descriptor);
输出:
{ value: 2017, writable: false, enumerable: false, configurable: false } { get: [Function: get], set: [Function: set], enumerable: false, configurable: false }
ECMAScript 2017 新增了 Object.getOwnPropertyDescriptors()(其实也就是多了个s)静态方法。这个方法实际上 会在每个自有属性上调Object.getOwnPropertyDescriptor()并在一个新对象中返回它们。
用这个方法我们就可以用这个方法来直接输出了
let book = {}; Object.defineProperties(book, { year_: { value: 2017 }, edition: { value: 1 }, year: { get() { return this.year_; }, set(newValue) { if (newValue > 2017) { this.year_ = newValue; this.edition += newValue - 2017 } } } }); console.log(Object.getOwnPropertyDescriptors(book));
输出:
{ year_: { value: 2017, writable: false, enumerable: false, configurable: false }, edition: { value: 1, writable: false, enumerable: false, configurable: false }, year: { get: [Function: get], set: [Function: set], enumerable: false, configurable: false } }
5.合并对象
- 合并两个对象,有时候也称为“混入”,因为目标对象通过混入元对象的属性得到了增强。ECMAScript6专门为合并对象提供了Object.assign()方法这个方法接收一个目标对象和一个或多个源对象作为参数,然后将每个源对象中可枚举属性复制到目标对象。
eg:
js // 合并对象 let dest,src,result; /** * 简单复制 */ dest = {}; src = {id:'src'}; result = Object.assign(dest,src); // Object.assign修改目标对象 // 也会返回修改后的目标对象 console.log(dest === result); console.log(dest !== src); console.log(result); console.log(dest); true true { id: 'src' } { id: 'src' }
多个源对象
eg:
js /** * 多个源对象 */ let dest,src,result; dest = {}; result = Object.assign(dest,{a:'foo'},{b:'bar'}); console.log(result);
获取函数与设置函数
eg:
/** * 获取函数与设置函数 */ let dest,src,result; dest = { set a(val){ console.log(`You input ${val}`); } }; src = { get a(){ console.log("get it"); return 'foo'; } }; Object.assign(dest,src); // 调用 src 的获取方法 // 调用 dest 的设置方法并传入参数"foo" // 因为这里的设置函数不执行赋值操作 // 所以实际上并没有把值转移过来 console.log(dest);
Object.assign()实际上对每个源对象执行的是浅复制。如果多个源对象都有相同的属性,则使 用最后一个复制的值。此外,从源对象访问器属性取得的值,比如获取函数,会作为一个静态值赋给目 标对象。换句话说,不能在两个对象间转移获取函数和设置函数。
覆盖属性:
/** * 覆盖属性 */ let dest,src,result; dest = {id:'dest'}; result = Object.assign(dest,{id:'src1',a:'foo'},{id:'src2',b:'bar'}); console.log(result);
对象引用
let dest,src,result; dest = {}; src ={a:{}}; Object.assign(dest,src); // 浅复制意味着只会复制对象的引用 console.log(dest); console.log(dest.a === src.a); //{ a: {} } //true
3、静态方法
静态方法就是直接定义在 Object
函数上的方法,注意与实例方法区分!!!调用的方式也不同,直接通过 Object.xxx()
的方式调用。
Object.assign(target,...assign)
作用:将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
简单来说,该方法可以接收多个参数,其中第一个参数作为目标对象,剩下的都是源对象。该方法将所有源对象的可枚举属性复制(浅复制)到目标对象中,结果返回目标对象。该方法会直接改变目标对象。
const target = {name:"jonas",age:18};
const source = {address:"Guangzhou",gender:"male"}
Object.assign(target,source);
console.log(target);//{name: "jonas", age: 18, address: "Guangzhou", gender: "male"}
1234
如果使用 ES6
的语法来实现这种需求就很简洁了:
let target = {name:"jonas",age:18};
let source = {address:"Guangzhou",gender:"male"}
target = {...target,...source}
123
三点运算符也是通过浅复制来实现拷贝的。
注意:如果目标对象中的属性具有相同的键,则属性将被源对象的属性覆盖,后面的源对象的属性将类似地覆盖前面的源对象属性。
Object.create(proto,propertiesObject)
该方法用于创建新对象。第一个参数用于指定新建对象的原型对象;第二个参数是对象的属性描述对象。方法返回新建的对象。
在默认的情况下,我们通过对象字面量的方式 {}
创建的对象的原型对象就是 Object
,然而,通过该方法就可以指定一个新建对象的原型对象,从而改变原型链的结构。
function Person() {}
Person.prototype.hello = function (){
console.log("hello")
}
let person = Object.create(Person.prototype,{
name:{
value:"jonas",
writable:true,
configurable:true,
},
age:{
value:18,
writable:true,
configurable:true,
}
})
console.log(person)//Person {name: "jonas", age: 18}
person.hello()//hello
123456789101112131415161718
Object.defineProperty(obj,prop,desc)
在对象 obj
上定义新的属性,或者修改对象 obj
中的属性,结果返回对象 obj
。
该方法有三个参数,第一个参数 obj
是目标对象,第二个参数 prop
是属性键名,第三个参数是这个属性的描述符。
let person = {}
Object.defineProperty(person,"name",{
value : "jonas",
writable : true,
enumerable : true,
configurable : true
})
console.log(person)//{name: "jonas"}
12345678
害,也许你会感到疑惑,给对象定义属性或修改属性不是直接通过对象字面量的方法更加简洁吗?
是的,如果只是简单的添加或者修改属性,那么必然是对象字面量的方式更为直观。但是,这种方式并不是万能的,在某些场景下就需要使用到这个方法。
比如,在 Vue
的底层实现数据绑定中就使用到了这个方法。
扩展:属性描述符有两种:
- 数据描述符。具有值的属性。
- 存取描述符。由
getter
和setter
函数对属性的描述。
一个属性只能是其中的一种描述符。
描述符通用属性:
configurable
—— 布尔值,默认值为false
。若值为true
,则表示这个属性描述符可以被改变,同时该属性也能从对象上删除。enumerable
—— 布尔值,默认值为false
。表示是否能枚举
数据描述符特有的属性:
value
—— 该属性的值,默认值为undefined
writable
—— 布尔值,默认值为false
,表示是否能重写。
存取描述符特有的属性:
get:function
—— 默认值为undefined
,当访问该属性时,该方法会被执行。set:function
—— 默认值为undefined
,当属性修改时,触发执行改方法,该方法接收一个参数,就是该属性新的值。
Object.entries(obj)
该方法返回对象 obj
自身的可枚举属性的键值对数组。结果是一个二维数组,数组中的元素是一个由两个元素 key
,value
组成的数组。
let person = {name:"jonas",age:18}
let arr = Object.entries(person)
console.log(arr)//[["name", "jonas"],["age", 18]]
123
该方法的使用场景是:将普通的对象转换为 Map
:
let person = {name:"jonas",age:18}
let map = new Map(Object.entries(person))
console.log(map)//Map(2) {"name" => "jonas", "age" => 18}
123
相似的,还有两个方法可以取出对象的键名或键值:
Object.keys(obj)
—— 返回一个对象中的可枚举属性组成的数组Object.values(obj)
—— 返回一个对象中的可枚举属性值组成的数组。
Object.freeze(obj)
该方法用于冻结对象,一个被冻结的对象不能被修改,不能添加新的属性,不能修改属性的描述符,该对象的原型对象也不能修改。返回值为被冻结的对象。
let person = {name:"jonas",age:18}
Object.freeze(person)
person.address = "Guangzhou"
person.age = 20
console.log(person)//{name: "jonas", age: 18}
12345
Object.getOwnPropertyDescriptor(obj,prop)
该方法用于返回指定对象上自有属性对应的属性描述符。
let obj = {}
Object.defineProperty(obj,"name",{
configurable:false,
enumerable:true,
writable:true,
value:"Jonas"
})
let descriptor = Object.getOwnPropertyDescriptor(obj,"name")
console.log(descriptor)//{value: "Jonas", writable: true, enumerable: true, configurable: false}
123456789
Object.getOwnPropertyNames(obj)
该方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括 Symbol
作为键名的属性)组成的数组。
该方法包含的范围比 Object.keys()
广。
let obj = {}
Object.defineProperty(obj,"name",{
configurable:false,
enumerable:false,
writable:true,
value:"Jonas"
})
Object.defineProperty(obj,Symbol(),{
configurable:false,
enumerable:false,
writable:true,
value: 18
})
let arr = Object.getOwnPropertyNames(obj)
console.log(arr)//["name"]
123456789101112131415
Object.getOwnPropertySymbols(obj)
该方法返回一个指定对象自身所有的 Symbol
键名的属性的数组。
let obj = {}
Object.defineProperty(obj,"name",{
configurable:false,
enumerable:false,
writable:true,
value:"Jonas"
})
Object.defineProperty(obj,Symbol(),{
configurable:false,
enumerable:false,
writable:true,
value: 18
})
let arr = Object.getOwnPropertySymbols(obj)
console.log(arr)//[Symbol()]
123456789101112131415
Object.getPrototypeOf(obj)
该方法返回指定对象的原型对象。
function Person() {}
Person.prototype.hello = function () {
console.log("hello")
}
let person = new Person()
let proto = Object.getPrototypeOf(person)
proto.hello()//hello
1234567
Object.is(obj1,obj2)
该方法用于比较两个对象是否相同,返回布尔值。
比较规则如下:
- 如果两个值都是
undefined
,则返回true
- 如果两个值都是
null
,则返回true
- 如果两个值都是
true
或false
,则返回true
- 如果两个值都是由相同个数的字符按照相同的顺序组成的字符串,则返回
true
- 如果两个值指向同一个对象,则返回
true
- 如果两个值都是
+0
,-0
,NaN
,则返回true
注意:该方法不会做隐式类型转换。
Object.isExtensible(obj)
该方法用于判断一个对象是否可以扩展(是否可以添加属性),返回布尔值。
在默认的情况下,对象是允许扩展的(无论是通过对象构造函数还是对象字面量方式创建的对象)。封闭对象,冻结对象是不可扩展的!!!
let obj = {}
Object.freeze(obj)
console.log(Object.isExtensible(obj))//false
123
如果要将一个对象设置为禁止扩展的对象,那么可以使用 Object.preventExtensions(obj)
。
Object.isFrozen(obj)
判断对象是否被冻结。返回布尔值。
Object.seal(obj)
封闭对象,阻止添加新属性并将所有的属性标记为不可配置!
4、实例方法
在 JS
中,所有的对象都是来自 Object
,所有对象从 Object.prototype
中集成方法和属性,尽管它们可能被覆盖。Object
的原型对象中也定义着一些方法,但是有一部分已经遗弃了,下面展示几个还在使用的:
Object.prototype.hasOwnProperty(prop)
—— 检测指定对象的自身中是否具有指定的属性,返回布尔值。Object.prototype.toString()
—— 返回对象的字符串形式。Object.prototype.valueOf()
—— 返回对象本身。Object.prototype.isPrototypeOf(obj)
—— 检测对象是否在另一个对象的原型链上,返回布尔值。
四、Set
简介: ES6引入了两种新的数据结构:Set和Map。Set是一组值的集合,其中值不能重复;Map(也叫字典)是一组键值对的集合,其中键不能重复。Set和Map都由哈希表(Hash Table)实现,并可按添加时候的顺序枚举。
Set类似于Array(数组),但需要通过SameValueZero算法保持值的唯一性。Object.is()依据的比较算法是SameValue,SameValueZero算法与之类似,唯一的区别就是在该算法中,+0和-0是相等的。
1)创建
要使用Set,需要先将其实例化,如下代码所示,其中构造函数Set()能接收一个可选的参数:可迭代对象,例如字符串、数组等。
new Set(); //Set(0) {}
new Set("abc"); //Set(3) {"a", "b", "c"}
new Set([+0, -0, NaN, NaN]); //Set(2) {0, NaN}
2)写入
通过add()方法可以在Set的末尾添加一个任意类型的值,并且由于方法的返回值是当前的Set,因此可以采用链式的写法。再利用size属性就可得到成员数量,从而获悉是否添加成功,如下所示。
var set = new Set();
set.add(1).add("a"); //Set(2) {1, "a"}
set.size; //2
有一点需要注意,虽然Set有写入方法,但并没有对应的读取方法。
3)移除
总共有两个移除的方法,分别是delete()和clear()。delete()可指定移除的值,而clear()能清空集合,即移除所有成员。下面承接写入中的示例,分别调用这两个移除方法,并在其之后会根据has()方法判断是否移除成功。
set.delete(1); //Set(1) {"a"}
set.has(1); //false
set.has("a"); //true
set.clear(); //Set(0) {}
set.has(1); //false
set.has("a"); //false
4)遍历
Set与数组一样,也有三个ES6新增的迭代器方法:keys()、values()和entries(),功能也相同。但由于Set没有键,只有值,因此keys()和values()返回的结果是相同的,如下所示。
var digits = new Set();
digits.add(3).add(2).add(1);
[...digits.keys()]; //[3, 2, 1]
[...digits.values()]; //[3, 2, 1]
[...digits.entries()]; //[[3, 3], [2, 2], [1, 1]]
除此之外,Set也有一个迭代方法:forEach(),参数也与数组的类似,第一个是回调函数,第二个是执行回调函数时使用的this对象。其中回调函数也包含3个参数,但参数含义略有不同,第一个和第二个都是当前成员的值,第三个是原始Set。从下面代码的注释中可知,Set的枚举顺序只与添加顺序有关,没有按照ES6所规定的枚举顺序(可参考第11篇)。
/*
3 3 Set(3) {3, 2, 1}
2 2 Set(3) {3, 2, 1}
1 1 Set(3) {3, 2, 1}
*/
digits.forEach(function(value1, value2, set) {
console.log(value1, value2, set);
});
5)转换
如果要将Set转换成数组,那么可以用Array.from()方法或扩展运算符实现,具体如下代码所示。注意,数组中的重复元素在传给Set后,就被过滤掉了。
var duplicate = new Set([1, 1, {}, undefined]);
Array.from(duplicate); //[1, {}, undefined]
[...duplicate]; //[1, {}, undefined]
简介: Map类似于Object(对象),可用来存储键值对,但需要通过SameValueZero算法保持键的唯一性。与Set一样,在使用之前也得要实例化,如下代码所示,构造函数Map()中的参数也是一个可选的可迭代对象,但此对象得是键值对的集合或两列的二维数组。
五、Map
Map类似于Object(对象),可用来存储键值对,但需要通过SameValueZero算法保持键的唯一性。与Set一样,在使用之前也得要实例化,如下代码所示,构造函数Map()中的参数也是一个可选的可迭代对象,但此对象得是键值对的集合或两列的二维数组。
实例方法
- new Map() —— 创建 map。
- map.set(key, value) —— 根据键存储值。
- map.get(key) —— 根据键来返回值,如果 map 中不存在对应的 key,则返回 undefined。
- map.has(key) —— 如果 key 存在则返回 true,否则返回 false。
- map.delete(key) —— 删除指定键的值。
- map.clear() —— 清空 map。
- map.size —— 返回当前元素个数。
迭代方法
- map.keys() —— 遍历并返回所有的键(returns an iterable for keys),
- map.values() —— 遍历并返回所有的值(returns an iterable for values),
- map.entries() —— 遍历并返回所有的实体(returns an iterable for entries)[key, value],for…of 在默认情况下使用的就是这个
- map.forEach( (value, key, map) => {
alert(${key}: ${value}
);
});
创建Map
- let map = new Map()
- let map = new Map([
[‘1’, ‘str1’],
[1, ‘num1’],
[true, ‘bool1’]
]); - Object.entities(obj):
let obj = {
name: "John",
age: 30
};
let map = new Map(Object.entries(obj));
12345
- Object.fromEntries:从 Map 创建对象
let prices = Object.fromEntries([
['banana', 1],
['orange', 2],
['meat', 4]
]);
// 现在 prices = { banana: 1, orange: 2, meat: 4 }
alert(prices.orange); // 2
123456789
Map 和 Object
- Map 的key可以是任意类型,Object 的key是字符串,自动转换
- Map 有size等属性
- delete obj.id,使用for in 访问obj时候,还是会访问到id,值为undefined
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)