http://kangax.github.io/compat-table/es6/ 查看兼容性
1、 ES6
1. let 变量
- 变量不能重复声明
let a, b = 123, c = 'haha', d = [];
// 再let a = 1; 会报错 var可以重复声明
-
块级作用域
在如if else while for {}内使用let,let只在该代码块内生效,超出无效
ES5三种作用域: 全局 函数 eval(ES5严格模式下存在)
-
不存在变量提升
如果变量a在未定义之前使用了会报错,let必须先声明后使用
对于var,先使用后声明会显示underfined,即
console.log(a); // underfined
var a = 1;
// 相当于
var a;
console.log(a); // underfined
a = 1;
-
不影响作用域链
尽管let是块级作用域,但在块级作用域内部写个函数调用let的变量,不会报错
{
let a = 1;
function fn() {
console.log(a);
}
fn(); // 显示'1'
}
-
实例
有三个div,class均为item,现需要点击每一个div后给其加背景颜色
// 获取div元素对象
let items = document.getElementsByClassName('item');
// 遍历并绑定事件
for(let i = 0; i < items.length; i++) {
items[i].onclick = function() {
// 修改当前元素的背景颜色
items[i].style.background = 'pink';
}
}
// 如果是var,循环遍历完后 i = 3,此时在点击div时,函数里面没有i,往上级window找到i=3,所以背景颜色不能改变
// let则只在自己的作用块里面有效,相当于
{
let i = 0;
items[i].onclick = function() {
items[i].style.background = 'pink';
}
}
{
let i = 1;
items[i].onclick = function() {
items[i].style.background = 'pink';
}
}
{
let i = 2;
items[i].onclick = function() {
items[i].style.background = 'pink';
}
}
2. const 常量
-
必须要赋初始值
const A;会报错 需const A = 10;
-
一般常量需要大写
-
常量的值不能修改
-
块级作用域
-
对于数组和对象的元素修改,不算做对常量的修改,不会报错
const A = ['sas', 'qqwex', 'ertfv', 'ooiu'];
A.push('hgfd');
// 因为并没有改变常量A所指向的地址,只是数组元素改变了
3. 变量的解构赋值
解构赋值: 按照一定模式从对象和数组中提取值,对变量进行赋值
3.1 数组的解构
const SUBJECT = ['语文', '数学', '英语', '生物'];
let [yu, shu, ying, sheng] = SUBJECT;
console.log(yu); // 语文
console.log(shu); // 数学
console.log(ying); // 英语
console.log(sheng); // 生物
3.2 对象的解构
const M = {
name: 'haha',
age: 12,
gender: 'female'
};
let {name, age, gender} = M;
console.log(name); // haha
console.log(age); // 12
console.log(gender); // female
4. 模板字符串
反引号``
-
声明
let str = `是字符串!!!`;
-
内容中可直接出现换行符
let str = `<ul> <li>a</li> <li>b</li> <li>c</li> </ul>`; // 模板字符串 var str = '<ul>' + '<li>a</li>' + '<li>b</li>' + '<li>c</li>' + '</ul>'; // 原来
-
变量拼接
let a = 'hello';
let b = `${a}=你好`; // 模板字符串
let c = a + '=你好'; // 原来
5. 简化对象写法
在大括号里面直接写入变量和函数
,作为对象的属性和方法
let name = '小明'; // 字符串
let change = function() { // 函数
console.log('请起个昵称!');
}
const school = { // 对象
name,
change, // 简化
// 方法声明的简化
improve: function() { // 之前的方法声明
console.log('加大技能!');
}
improve() { // 现在的方法声明
console.log('加大技能!');
}
}
6. 箭头函数 =>
// 之前声明函数
let fn = function() {...}
// 现在声明函数
let fn = (a, b) => {
return a + b;
}
// 调用函数
let add = fn(1,2);
console.log(add); // 3
箭头函数的声明特性:
-
this是静态的 this始终指向函数声明时所在作用域下的this的值
function getName() { // 普通函数 console.log(this.name); } let getName2 = () => { // 箭头函数 console.log(this.name); } // 设置window对象的name属性 window.name = '小明'; const school = { name: "xiaoming" } // 直接调用 getName(); // 小明 普通函数直接调用this指向window getName2(); // 小明 箭头函数在全局作用域下声明的,所以直接调用this也指向window // call调用 call可以改变函数内部this的值 getNmae.call(school); // xiaoming getName2.call(school); // 小明 this指向window // 箭头函数无论用什么方法调用,this始终指向函数声明时所在作用域下的this的值
-
不能作为构造函数实例化对象
let Person = (name, age) => { this.name = name; this.age = age; } let me = new Person('xiao', 24); console.log(me); // Person is not a constructor
-
不能使用 arguments变量(保存实参)
let fn = () => { console.log(arguments); } fn(1, 2, 3); // arguments is not defined
-
箭头函数的简写
- 形参有且只有一个 可以省略小括号
let add = (n) => { // 改为 let add = n => { return n + n; } console.log(add(9)); // 18
- 代码体只有一条语句 省略花括号,此时return也必须省略,而且语句的执行结果就是函数的返回值
let add = n => n + n; console.log(add(9)); // 18
实例:
- 点击div 2s 后颜色变成粉色
// 获取元素
let ad = document.getElementById('ad');
// 绑定事件 addEventListener() 方法用于向指定元素添加事件句柄
ad.addEventListener("click", function(){
// 保存 this 的值
let _this = this; // 不加的话 在定时器里面this是指向window的
// 定时器
setTimeout(function() {
// 修改背景颜色
_this.style.background = 'pink'; // 如果没有保存 this 的值,此处会报错 'background' of undefined
},2000);
});
变成箭头函数可以用this:
let ad = document.getElementById('ad');
ad.addEventListener("click", function(){
setTimeout(() => {
this.style.background = 'pink'; // 箭头函数的this是静态的,指向声明时所在作用域下的值
// this是在function外层的作用域下生成的,this就指向function()的this,而function()的this指向事件源ad
},2000);
});
- 从数组中返回偶数的元素
const arr = [1,2,3,4,9,10];
const result = arr.filter(function(item) {
if(item % 2 === 0) {
return true;
}else {
return false;
}
});
// 改为箭头函数
const result = arr.filter(item => item % 2 === 0);
console.log(result); // 2,4,10
箭头函数适合与 this 无关的回调,比如 定时器、数组的方法回调
箭头函数不适合与 this 有关的回调, 比如 事件回调、对象的方法
{
name: 'hha',
getName: () => {
this.name; // 此时this指向{}外层作用域的this
}
}
7. call、apply、bind比较
相同点:
都可以改变 this 的指向
第一个参数都是this要指向的对象
都可以利用后续参数传参
区别:
var p1 = { name:"隔壁老王", }
function eat(str,drinkstr) {
console.log(this.name+"在吃"+str+",同时在喝"+drinkstr);
}
//1、bind:
let f1 = eat.bind(p1);//f1是个函数。即,用bind把eat函数和p1对象绑死,产生新的函数叫作f1
f1("油条","豆浆");//调用f1,就相当于 p1.eat();,内部的this就是p1,永远都是p1,不会是window
//2、call:
eat.call(p1,"油泼面","面汤");//不会产生新的函数,只是临时把eat函数和p1对象绑定一下。
//3、apply:(与bind的意思一样,只是第二个参数是数组,是原函数eat的参数)
eat.apply(p1,["油泼面","面汤"]);//不会产生新的函数,只是临时把eat函数和p1对象绑定一下。
区别 | call | apply | bind |
---|---|---|---|
调用 | 立即调用 | 立即调用 | 先返回一个函数稍后()调用 |
传参 | 把apply的第一个参数单列出来 | 第二个参数是数组(函数的所有参数) | 同call |
例子 | eat.call(p1,“油泼面”,“面汤”); | eat.apply(p1,[“油泼面”,“面汤”]); | let f1 = eat.bind(p1); f1(“油条”,“豆浆”); |
call中的细节:
1、 非严格模式
如果不传参数,或者第一个参数是 null 或 undefined,this 都指向window
let fn = function(a,b){
console.log(this,a,b);
}
let obj = {name:"obj"};
fn.call(obj,1,2); // this:obj a:1 b:2
fn.call(1,2); // this:1 a:2 b:undefined
fn.call(); // this:window a:undefined b:undefined
fn.call(null); // this=window a=undefined b=undefined
fn.call(undefined); // this=window a=undefined b=undefined
2、 严格模式
第一个参数是谁, this 就指向谁,包括 null 和 undefined, 如果不传参数 this 就是 undefined
"use strict"
let fn = function(a,b){
console.log(this,a,b);
}
let obj = {name:"obj"};
fn.call(obj,1,2); // this:obj a:1 b:2
fn.call(1,2); // this:1 a:2 b=undefined
fn.call(); // this:undefined a:undefined b:undefined
fn.call(null); // this:null a:undefined b:undefined
fn.call(undefined); // this:undefined a:undefined b:undefined
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
function Student(name, age, sex, tel, grade) {
// var this = {} // 隐式 call传进去的就是这个this
Person.call(this, name, age, sex); // 调用Person的方法就不用重写了
this.tel = tel;
this.grade = grade;
}
var student = new Student('sunny', 123, 'male', 139, 2017);
student // Student {name: "sunny", age: 123, sex: "male", tel: 139, grade: 2017}
function Person(name, age) {
this.name = name;
this.age = age;
}
var person = new Person('deng', 100);
person // Person {name: "deng", age: 100}
var obj = {}
Person.call(obj, 'zhang', 500); // Person里的this改为obj
obj // {name: "zhang", age: 500}
8. 函数参数默认值
-
形参初始值 具有默认值的参数,一般位置要靠后(如果放在前面,传入的值会覆盖掉,后面的参数仍然是undefined,结果是NaN)
function add(a, b, c=4) { return a + b + c; } let result = add(1, 2); console.log(result); // 1+2+4
-
可与解构赋值结合
function connect({host="haha", username, password, port}) { // 如果connect里面有参数没有赋值,这里还可以赋初始值 console.log(host) console.log(username) console.log(password) console.log(port) } connect({ host: '192.168.0.0', username: 'root', password: 'root', port: 8080 })
9. rest参数…
rest: 获取函数的实参,用来代替 arguments
// ES5 获取实参的方式
function test() {
console.log(arguments);
}
test('a', 'b', 'c'); // 显示对象Object abc
// rest 参数
function test(...args) {
console.log(args);
}
test('a', 'b', 'c'); // 显示数组abc,由于是数组所以可以用filter some every map等方法
rest参数必须要放到所有参数的后面
function fn(a, b, ...args) {
console.log(a); // 1
console.log(b); // 2
console.log(args); //数组[3,4,5,6]
}
fn(1, 2, 3, 4, 5, 6);
10. 扩展运算符…
扩展运算符可以将 数组
转换为 逗号分隔的 参数序列
// 声明一个数组
const tfboys = ['易烊千玺', '王源', '王俊凯']; // 转化为 '易烊千玺', '王源', '王俊凯'
// 声明一个函数
function chunwan() {
console.log(arguments);
}
chunwan(tfboys); // 显示的是一个参数 数组 0: ['易烊千玺', '王源', '王俊凯']
chunwan(...tfboys); // 显示三个参数 0: "易烊千玺" 1: "王源" 2: "王俊凯"
// 相当于 chunwan('易烊千玺', '王源', '王俊凯')
// ...tfboys放到了函数调用的实参里面 ...rest 放在形参里面
应用:
-
数组的合并
const kuaizi = ['王太利', '肖央']; const fenghuang = ['曾毅', '玲花']; const zuixuanxiaopingguo = kuaizi.concat(fenghuang); // ES5 const zuixuanxiaopingguo = [...kuaizi, ...fenghuang]; // 扩展运算符 console.log(zuixuanxiaopingguo);// ["王太利", "肖央", "曾毅", "玲花"]
-
数组的克隆
const a = ['A', 'B', 'C']; const b = [...a]; // ['A', 'B', 'C']; 浅拷贝??? 引用类型数据变化 console.log(b); // ["A", "B", "C"]
-
将伪数组转化为真正的数组
const divs = document.querySelectorAll('div'); // 获取到三个div const divArr = [...divs]; console.log(divArr);
11. Symbol
新的原始数据类型symbol,表示独一无二的值。是JavaScript的第七种数据类型,类似字符串
特点:
- symbol值唯一 不会出现命名冲突
- symbol值不能与其他数据进行运算(数学计算、字符串拼接、模板字符串)
- symbol定义的对象属性不能使用 for…in 循环遍历,但可以使用Reflect.ownKeys / Object.getOwnPropertySymbols()来获取对象的所有键名
// 创建Symbol
let s = Symbol();
console.log(s, typeof s); // Symbol() "symbol"
// 给symbol传入一个字符串
let s2 = Symbol('haha'); // symbol是函数
let s3 = Symbol('haha');
console.log(s2 === s3); // false 二者是不一样的,传入的字符串只是一个标志,返回的结果不一样
// Symbol(haha) != Symbol(haha)
// Symbol.for创建 得到的是唯一的Symbol值
let s4 = Symbol.for('haha'); // symbol是一个对象,称之为 函数对象
let s5 = Symbol.for('haha'); // Symbol(haha)
console.log(s4 === s5); // true
// 不能与其他数据进行运算
let result = s + 10; // x Cannot convert a Symbol value to a number
let result = s > 10; // x Cannot convert a Symbol value to a number
let result = s + s; // x Cannot convert a Symbol value to a number
let result = s + '100'; // x Cannot convert a Symbol value to a string 隐式转换
let sym = Symbol(); console.log(`${sym}`); // 模板字符串 Cannot convert a Symbol value to a string
// 显示转换
Symbol().toString()
let sy = Symbol();
let obj = {
name:"zhangsan",
age:21
};
obj[sy] = "symbol";
console.log(obj); // {name: "zhangsan", age: 21, Symbol(): "symbol"}
for(let key in obj) {
console.log(key);
} // 只输出了name,age
Object.getOwnPropertySymbols(obj); // [Symbol()]
Reflect.ownKeys(obj); // ["name", "age", Symbol()]
Object.getOwnPropertyNames(obj); // ["name", "age"]
Object.keys(obj); // ["name", "age"]
// Object.keys()方法返回给定对象自己的可枚举属性名称的数组,并以与普通循环相同的顺序进行迭代。
Object.values(obj); // ["zhangsan", 21]
JSON.stringify(obj); // {"name":"zhangsan","age":21}
JSON.stringify()
JSON.stringify() 方法用于将 JavaScript 值转换为 JSON 字符串。
语法
JSON.stringify(value[, replacer[, space]])
参数说明:
-
value: 必需, 要转换的 JavaScript 值(通常为对象或数组)。
-
replacer: 可选。用于转换结果的函数或数组。
如果 replacer 为函数,则 JSON.stringify 将调用该函数,并传入每个成员的键和值。使用返回值而不是原始值。如果此函数返回 undefined,则排除成员。根对象的键是一个空字符串:""。
如果 replacer 是一个数组,则仅转换该数组中具有键值的成员。成员的转换顺序与键在数组中的顺序一样。
-
space: 可选,文本添加缩进、空格和换行符,如果 space 是一个数字,则返回值文本在每个级别缩进指定数目的空格,如果 space 大于 10,则文本缩进 10 个空格。space 也可以使用非数字,如:\t。
**返回值:**返回包含 JSON 文本的字符串。
实例
var str = {"name":"菜鸟教程", "site":"http://www.runoob.com"}
str_pretty1 = JSON.stringify(str)
document.write( "只有一个参数情况:" );
document.write( "<br>" );
document.write("<pre>" + str_pretty1 + "</pre>" );
document.write( "<br>" );
str_pretty2 = JSON.stringify(str, null, 4) //使用四个空格缩进
document.write( "使用参数情况:" );
document.write( "<br>" );
document.write("<pre>" + str_pretty2 + "</pre>" ); // pre 用于格式化输出
// 显示
只有一个参数情况:
{"name":"菜鸟教程","site":"http://www.runoob.com"}
使用参数情况:
{
"name": "菜鸟教程",
"site": "http://www.runoob.com"
}
Reflect
Reflect是一个内置对象,它提供用于可拦截JavaScript操作的方法。这些方法与代理处理程序的方法相同。Reflect
不是函数对象,因此它是不可构造的。 其所有属性与方法均是静态的 大部分方法同Object
Reflect.ownKeys()
Reflect.ownKeys(target) target:从中获取自由属性名称的对象。返回target
对象自己的属性键的数组
- 如果target不是一个对象会报错
- 此方法的返回数组等同于Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
Reflect.ownKeys({z: 3, y: 2, x: 1}) // [ "z", "y", "x" ]
Reflect.ownKeys([]) // ["length"]
let sym = Symbol.for('comet')
let sym2 = Symbol.for('meteor')
let obj = {[sym]: 0, 'str': 0, '773': 0, '0': 0,
[sym2]: 0, '-1': 0, '8': 0, 'second str': 0}
Reflect.ownKeys(obj) // [ "0", "8", "773", "str", "-1", "second str", Symbol(comet), Symbol(meteor) ]
// Indexes in numeric order, 数字顺序
// strings in insertion order, 插入顺序
// symbols in insertion order 插入顺序
Object.getOwnPropertySymbols()
obj 要返回其符号属性的对象
返回值: 直接在给定对象上找到的所有符号属性的数组。
与Object.getOwnPropertyNames()
相似,您可以将给定对象的所有符号属性获取为符号数组。请注意Object.getOwnPropertyNames()
它本身不包含对象的符号属性,而仅包含字符串属性。
由于所有对象最初都没有自己的符号属性,因此Object.getOwnPropertySymbols()
除非您在对象上设置了符号属性,否则返回一个空数组。
var obj = {};
var a = Symbol('a');
var b = Symbol.for('b'); // Symbol.for创建 得到的是唯一的Symbol值
obj[a] = 'localSymbol';
obj[b] = 'globalSymbol';
var objectSymbols = Object.getOwnPropertySymbols(obj);
console.log(objectSymbols.length); // 2
console.log(objectSymbols); // [Symbol(a), Symbol(b)]
console.log(objectSymbols[0]); // Symbol(a)
枚举
枚举是指对象中的属性是否可以遍历出来,再简单点说就是属性是否可以被列举出来。
-
怎么判断枚举
-
在JavaScript中,对象的属性分为可枚举和不可枚举之分,它们是由属性的enumerable值决定的。
-
可枚举性决定了这个属性能否被for…in查找遍历到。
-
JS中的基本包装类型的原型属性是不可枚举的:boolean、number、string
-
枚举属性的作用
- ES5中
- for…in 只遍历对象自身的和继承的可枚举的属性
- Object.keys() 返回对象自身的所有可枚举的属性的键名
- JSON.stringify JSON.stringify() 方法用于将 JavaScript 值转换为 JSON 字符串。
- ES6中
- Object.assign() 会忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。
- ES5中
-
设置可枚举属性
Object.defineProperty(person,'age',{ enumerable:true //可以被枚举 });
- Object.defineProperty(obj, prop, descriptor)方法有三个参数
第一个:目标对象
第二个:目标属性,字符串
第三个:对目标属性的行为,放在对象里 - enumerable为true时表示可枚举,enumerable为false表示不可枚举;
- 开发者自己定义的对象person中的所有属性默认都是可枚举的;
- Object.defineProperty(obj, prop, descriptor)方法有三个参数
-
判断是否可枚举propertyIsEnumerable
用法: obj.propertyIsEnumerable(“属性名”);
-
这个属性必须属于实例的,并且不属于原型。
-
这个属性必须是可枚举的。
-
如果对象没有指定的属性,该方法返回false
function Person(){ this.name = "我是实例属性" this.age = 19; } var p = new Person(); console.log(p.propertyIsEnumerable("name")); //true Person.prototype.prop = "我是原型属性" //添加一个原型属性 console.log(p.propertyIsEnumerable("prop")); //false prop是继承自原型上的属性,所以返回的是false for(var k in p){ console.log(k+","+p[k]); //name,我是实例属性 age,19 prop,我是原型属性 }
-
用户自定义对象和引擎内置对象的区别
Math.propertyIsEnumerable('random'); // 返回 false Object.propertyIsEnumerable('constructor'); // 返回 false var num = new Number(); for(var pro in num) { console.log("num." + pro + " = " + num[pro]); // 输出空 }
-
-
引入enumerable的最初目的,就是让某些属性可以规避掉for…in操作。比如,对象原型的toString方法,以及数组的length属性
Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable // false
Object.getOwnPropertyDescriptor([], 'length').enumerable // false
ES6 规定,所有 Class 的原型的方法都是不可枚举的。
Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable // false
总的来说,操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性。所以,尽量不要用for…in循环,而用Object.keys()代替。
Object.getOwnPropertyNames()
-
Object.getOwnPropertyNames()返回直接在给定对象中找到的所有属性(不包括那些使用Symbol的属性)的数组。
-
该数组的元素是对应于直接在给定对象中发现的
可枚举和不可枚举
属性的字符串obj。数组中可枚举属性的顺序与for…in循环(或通过Object.keys())在对象属性上显示的顺序一致。 数组中不可枚举属性的顺序未定义。
-
不会获取原型链上的属性
-
根据ES6,对象的整数键(可枚举和不可枚举)首先按升序添加到数组,然后按插入顺序添加字符串键。
var obj = {name: 'zyp', age: 18};
Object.defineProperty(obj, 'like', {
enumerable: false, // 不可枚举
value: 'reading'
})
Object.defineProperty(obj, Symbol(), {
value: 'symbol'
})
var properNameArr = Object.getOwnPropertyNames(obj); // properNameArr结果为["name", "age", "like"]
var arr = [1,2,3,4,5,6];
console.log(Object.getOwnPropertyNames(arr));//[0,1,2,3,4,5,length]
// 输出为数组对象的索引和数组长度的属性值。
如果只需要可枚举的属性,请查看Object.keys()
或使用for...in
循环(请注意,除非对象使用进行了过滤,否则还会返回沿对象原型链找到的可枚举的属性hasOwnProperty()
。
hasOwnProperty()
返回一个布尔值,该布尔值指示对象是否具有指定的属性作为其自身的属性(此方法不检查对象原型链中的属性)。
hasOwnProperty
即使该属性的值为null
orundefined
,也返回true 。
o = new Object();
o.propOne = null;
o.hasOwnProperty('propOne'); // returns true
o.propTwo = undefined;
o.hasOwnProperty('propTwo'); // returns true
下面的示例区分直接属性和通过原型链继承的属性:
o = new Object();
o.prop = 'exists';
o.hasOwnProperty('prop'); // returns true
o.hasOwnProperty('toString'); // returns false
o.hasOwnProperty('hasOwnProperty'); // returns false
JavaScript不保护属性名称hasOwnProperty
;因此,如果存在一个对象可能具有使用该名称的属性的可能性,则必须使用外部对象 hasOwnProperty
来获得正确的结果:
var foo = {
hasOwnProperty: function() {
return false;
},
bar: 'Here be dragons'
};
foo.hasOwnProperty('bar'); // always returns false
// Use another Object's hasOwnProperty
// and call it with 'this' set to foo
({}).hasOwnProperty.call(foo, 'bar'); // true
// It's also possible to use the hasOwnProperty property
// from the Object prototype for this purpose
Object.prototype.hasOwnProperty.call(foo, 'bar'); // true
在最后一种情况下,没有新创建的对象
Object.keys()
Object.keys(obj)
参数:要返回其枚举自身属性的对象
返回值:一个表示给定对象的所有可枚举
属性的字符串数组,不包括属性名为Symbol值的属性 enumerable: false不可枚举
- 如果属性名的类型是
Number
,那么Object.keys
返回值是按照key
从小到大排序 - 如果属性名的类型是
String
,那么Object.keys
返回值是按照属性被创建的时间升序排序。 - 如果属性名的类型是
Symbol
,那么逻辑同String
相同 - 负数是作为字符串
String
处理的,也是按照定义时候的顺序
// 处理对象,返回可枚举的属性数组
let person = {name:"张三",age:25,address:"深圳",getName:function(){}}
Object.keys(person); // ["name", "age", "address","getName"]
// 处理数组,返回索引值数组
let arr = [1,2,3,4,5,6];
Object.keys(arr); // ["0", "1", "2", "3", "4", "5"]
// 处理字符串,返回索引值数组,因为String对象有可提取的属性
let str = "saasd字符串";
Object.keys(str); // ["0", "1", "2", "3", "4", "5", "6", "7"]
// 常用技巧
let person = {name:"张三",age:25,address:"深圳",getName:function(){}}
Object.keys(person).map((key)=>{
person[key] // 获取到属性对应的值,做一些处理
})
// 返回的对象没有任何可提取的属性,所以返回空数组
Object.keys(123); // []
var obj = {name: 'zyp', age: 18};
Object.defineProperty(obj, 'like', {
enumerable: false, // 不可枚举
value: 'reading'
})
Object.defineProperty(obj, Symbol(), {
value: 'symbol'
})
var properNameArr = Object.keys(obj); // properNameArr结果为["name", "age"]
当Object.keys
被调用时背后发生了什么
-
调用
ToObject(O)
将结果赋值给变量obj
-
调用
EnumerableOwnPropertyNames(obj, "key")
将结果赋值给变量nameList
-
调用
CreateArrayFromList(nameList)
得到最终的结果
1、将参数转换成Object(ToObject(O)
)
- Undefined | 抛出TypeError
- Null | 抛出TypeError
- Boolean | 返回一个新的 Boolean 对象
- Number | 返回一个新的 Number 对象
- String | 返回一个新的 String 对象
- Symbol | 返回一个新的 Symbol 对象
- Object | 直接将Object返回
2、获得属性列表(EnumerableOwnPropertyNames(obj, "key")
)
针对Object.keys
这个API来说,获取属性列表中最重要的是调用了内部方法OwnPropertyKeys
得到ownKeys
。其实也正是内部方法OwnPropertyKeys
决定了属性的顺序。
关于OwnPropertyKeys
方法ECMA-262中是这样描述的:
当O
的内部方法OwnPropertyKeys
被调用时,执行以下步骤(其实就一步):
Return ! OrdinaryOwnPropertyKeys(O).
而OrdinaryOwnPropertyKeys
是这样规定的:
- 声明变量
keys
值为一个空列表(List类型) - 把每个Number类型的属性,按数值大小升序排序,并依次添加到
keys
中 - 把每个String类型的属性,按创建时间升序排序,并依次添加到
keys
中 - 把每个Symbol类型的属性,按创建时间升序排序,并依次添加到
keys
中 - 将
keys
返回(return keys
)
上面这个规则不光规定了不同类型的返回顺序,还规定了如果对象的属性类型是数字,字符与Symbol混合的,那么返回顺序永远是数字在前,然后是字符串,最后是Symbol。
Object.keys({
5: '5',
a: 'a',
1: '1',
c: 'c',
3: '3',
b: 'b'
}) // ["1", "3", "5", "a", "c", "b"]
// 属性的顺序规则中虽然规定了Symbol的顺序,但其实Object.keys最终会将Symbol类型的属性过滤出去。(原因是顺序规则不只是给Object.keys一个API使用,它是一个通用的规则)
3、将List类型转换为Array得到最终结果(CreateArrayFromList( elements )
)
将List类型的属性列表转换成Array类型非常简单:
- 先声明一个变量
array
,值是一个空数组 - 循环属性列表,将每个元素添加到
array
中 - 将
array
返回
上面介绍的排序规则同样适用于下列API:
Object.entries
Object.values
for...in
循环Object.getOwnPropertyNames
Reflect.ownKeys
for…in
获取对象自身及其原型链上的可枚举属性,不包括属性名为Symbol值的属性
var obj = {name: 'zyp', age: 18};
Object.defineProperty(obj, 'like', {
enumerable: false, // 不可枚举
value: 'reading'
})
Object.defineProperty(obj, Symbol(), {
value: 'symbol'
})
Object.prototype.haha = 'haha' // 原型链
var properNameArr = [];
for(let i in obj) {
properNameArr.push(i)
}
console.log(properNameArr) // properNameArr结果为["name", "age", "haha"]
以上三种方法遍历对象的可枚举属性时,返回的结果顺序是不确定的,因为这种无序性,因此不建议在使用for…in循环进行遍历时对对象的属性进行删除/添加/修改等操作,除非是针对当前正在被访问的属性。也因为这种无序性,在对数组这种有顺序的数据结构进行遍历时,最好不要用for…in(改用for循环/forEach/for…of)。
var obj = {
name: 'xiaozhang',
age: 23,
sex: "male",
height: 170,
wight: 67,
__proto__: {
lastName: 'deng',
__proto__: Object.prototype // 不会打印
}
}
Object.prototype.abc = '123'; // 不会打印
for(var prop in obj) {
console.log(obj.prop); // 5个undefined 因为相当于执行了Obj['prop'],把prop当成了属性而不是变量
console.log(obj[prop]); // xiaozhang 23 male 170 67 deng 原型链上的也会打印出来,但是不会打印Object.prototype
// 不想打印原型链上的 可以用hasOwnProperty属性 是自身属性返回true 否则false
if(obj.hasOwnProperty(prop)) {
console.log(obj[prop]); // // xiaozhang 23 male 170 67
}
}
'height' in obj // true
'lastName' in obj // true 原型链上的也可访问到,in只是判断对象上能不能访问到该属性,不是判断该属性属不属于该对象
// A instanceof B A对象是不是B构造函数构造出来的
// 看A对象的原型链上 有没有 B的原型
[] instanceof Array // true
[] instanceof Object // true
function Person() {}
var person = new Person();
person instanceof Person // true
person instanceof Object // true
// 区分数组和对象
var a = [];
var b = {};
// 1 有父子域的问题
a.constructor // ƒ Array() { [native code] }
b.constructor // ƒ Object() { [native code] }
// 2 有父子域的问题 子元素 instanceof 父元素 为false 实际应该是true
a instanceof Array // true
b instanceof Array // false
// 3 不会有父子域的问题
// Object.prototype.toString = function () {
// 识别this
// 返回相应的结果
// }
Object.prototype.toString.call(a); // "[object Array]"
Object.prototype.toString.call(b); // "[object Object]"
Object.values()和Object.keys()是相反的操作,把一个对象的值转换为数组
Object.values()
Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历( enumerable )属性的键值。
var obj = { 100: 'a', 2: 'b', 7: 'c' };
Object.values(obj); // ["b", "c", "a"]
// 上面代码中,属性名为数值的属性,是按照数值大小,从小到大遍历的,因此返回的顺序是b、c、a。
// Object.values只返回对象自身的可遍历属性。
var obj = Object.create({}, {p: {value: 42}});
Object.values(obj) // []
//上面代码中,Object.create方法的第二个参数添加的对象属性(属性p),如果不显式声明,默认是不可遍历的。Object.values不会返回这个属性。
// Object.values会过滤属性名为 Symbol 值的属性。
Object.values({ [Symbol()]: 123, foo: 'abc' }); // ['abc']
// 如果Object.values方法的参数是一个字符串,会返回各个字符组成的一个数组。
Object.values('foo'); // ['f', 'o', 'o']
// 上面代码中,字符串会先转成一个类似数组的对象。字符串的每个字符,就是该对象的一个属性。因此,Object.values返回每个属性的键值,就是各个字符组成的一个数组
// 如果参数不是对象,Object.values会先将其转为对象。由于数值和布尔值的包装对象,都不会为实例添加非继承的属性。所以,Object.values会返回空数组。
Object.values(42); // []
Object.values(true); // []
Object.entries()
Object.entries方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历( enumerable )属性的键值对数组。
var obj = { foo: 'bar', baz: 42 };
Object.entries(obj); // [ ["foo", "bar"], ["baz", 42] ]
// 除了返回值不一样,该方法的行为与Object.values基本一致。
// 如果原对象的属性名是一个 Symbol 值,该属性会被省略。将来可能会有Reflect.ownEntries()方法,返回对象自身的所有属性。
Object.entries({ [Symbol()]: 123, foo: 'abc' }); // [ [ 'foo', 'abc' ] ]
Object.entries的基本用途是遍历对象的属性。
for (let [k, v] of Object.entries(obj)) {
console.log(`${JSON.stringify(k)}: ${JSON.stringify(v)}`);
} // "one": 1 // "two": 2
Object.entries方法的一个用处是,将对象转为真正的Map结构。
var obj = { foo: 'bar', baz: 42 };
var map = new Map(Object.entries(obj));
map // Map { foo: "bar", baz: 42 }
自己实现Object.entries方法,非常简单。
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
// 非 Generator 函数的版本
function entries(obj) {
let arr = [];
for (let key of Object.keys(obj)) {
arr.push([key, obj[key]]);
}
return arr;
}
给对象添加Symbol类型的属性
// 声明一个对象
let methods = {
up: Symbol(),
down: Symbol()
};
game[methods.up] = function() {
console.log("haha");
}
game[methods.down] = function() {
console.log("xixia");
} // 这样打印出来的game里面就会有两个新建的Symbol类型
// 另一种方法
let youxi = {
name: "狼人杀",
[Symbol('say')]: function() { // 加[]是为了接收变量,因为Symbol里面只能接收定量值
console.log('haha');
},
[Symbol('sing')]: function() {
console.log('xixi');
},
}
Symbol方法
1. Symbol.for()
作用:用于将描述相同的Symbol变量指向同一个Symbol值
,方便我们通过描述(标识)区分开
不同的Symbol
Symbol.for("foo"); // 创建一个 symbol 并放入 symbol 注册表中,键为 "foo"
Symbol.for("foo"); // 从 symbol 注册表中读取键为"foo"的 symbol
Symbol.for("bar") === Symbol.for("bar"); // true,证明了上面说的
Symbol("bar") === Symbol("bar"); // false,Symbol() 函数每次都会返回新的一个 symbol
var sym = Symbol.for("mario");
sym.toString(); // "Symbol(mario)",mario 既是该 symbol 在 symbol 注册表中的键名,又是该 symbol 自身的描述字符串
Symbol()和Symbol.for()的相同点:
- 它们定义的值类型都为"symbol"
Symbol()和Symbol.for()的不同点:
Symbol()定义的值不放入全局 symbol 注册表中,每次都是新建
,即使描述相同 值也不相等;用 Symbol.for() 方法创建的 symbol 会被放入一个全局 symbol 注册表中。
Symbol.for() 并不是每次都会创建一个新的 symbol,它会首先检查给定的 key 是否已经在注册表中了。假如是,则会直接返回上次存储的那个。否则,它会再新建一个。
2. Symbol.keyFor()
作用: 获取 symbol 注册表中与某个 symbol 关联的键。
如果全局注册表中查找到该symbol,则返回该symbol的key值,形式为string。如果symbol未在注册表中,返回undefined
// 创建一个 symbol 并放入 Symbol 注册表,key 为 "foo"
var globalSym = Symbol.for("foo");
Symbol.keyFor(globalSym); // "foo"
// 创建一个 symbol,但不放入 symbol 注册表中
var localSym = Symbol();
Symbol.keyFor(localSym); // undefined,所以是找不到 key 的
Symbol的属性
Symbol.prototype.description
description 是一个只读属性,它会返回 Symbol 对象的可选描述的字符串。
// Symbol()定义的数据
let a = Symbol("acc");
a.description // "acc"
Symbol.keyFor(a); // undefined
// Symbol.for()定义的数据
let a1 = Symbol.for("acc");
a1.description // "acc"
Symbol.keyFor(a1); // acc
// 未指定描述的数据
let a2 = Symbol();
a2.description // undefined
Symbol('desc').toString(); // "Symbol(desc)"
Symbol('desc').description; // "desc"
Symbol('').description; // ""
Symbol().description; // undefined
description属性和Symbol.keyFor()方法的区别是:
- description能返回所有Symbol类型数据的描述,而Symbol.keyFor() 只能返回Symbol.for() 在全局注册过的描述
11个Symbol内置值
内置Symbol的值 | 调用时机 |
---|---|
Symbol.hasInstance | 当其他对象使用 instanceof 运算符,判断是否为该对象的实例时,会调用这个方法 |
Symbol.isConcatSpreadable | 对象的 Symbol.isConcatSpreadable 属性等于的是一个布尔值,表示该对象用于 Array.prototype.concat() 时,是否可以展开。 |
Symbol.species | 创建衍生对象时,会使用该属性 |
Symbol.match | 当执行 str.match(myObject) 时,如果该属性存在,会调用它,返回该方法的返回值。 |
Symbol.replace | 当该对象被 str.replace(myObject)方法调用时,会返回该方法的返回值。 |
Symbol.search | 当该对象被 str. search (myObject)方法调用时,会返回该方法的返回值。 |
Symbol.split | 当该对象被 str. split (myObject)方法调用时,会返回该方法的返回值。 |
Symbol.iterator | 对象进行 for…of 循环时,会调用 Symbol.iterator 方法,返回该对象的默认遍历器 |
Symbol.toPrimitive | 该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。 |
Symbol. toStringTag | 在该对象上面调用 toString 方法时,返回该方法的返回值 |
Symbol. unscopables | 该对象指定了使用 with 关键字时,哪些属性会被 with环境排除。 |
Symbol内置值的使用,都是作为某个对象类型的属性去使用
Symbol.hasInstance
class Person {
static [Symbol.hasInstance](param) { // 自动执行,如果有参数还会传参
console.log(param); // {}
console.log("检测类型"); // 检测类型
return true; // 可以把 o 传递给方法,由方法的返回值确定o instanceof Person的值
}
}
let o = {};
console.log(o instanceof Person); // false
class Person {}
let p1 = new Person;
console.log(p1 instanceof Person); //true
// instanceof 和 [Symbol.hasInstance] 是等价的
console.log(Person[Symbol.hasInstance](p1)); //true
console.log(Object[Symbol.hasInstance]({})); //true
Symbol.isConcatSpreadable
值为布尔值,表示该对象用于Array.prototype.concat() 时,是否可以展开
数组的默认行为是可以展开,Symbol.isConcatSpreadable
默认等于undefined
。该属性等于true
时,也有展开的效果。
类似数组的对象正好相反,默认不展开。它的Symbol.isConcatSpreadable
属性设为true
,才可以展开。
const arr = [1, 2, 3];
const arr2 = [4, 5, 6];
arr2[Symbol.isConcatSpreadable] = false; // 不可展开
console.log(arr.concat(arr2)); // [1, 2, 3, Array(3)]
Symbol.isConcatSpreadable
属性也可以定义在类里面
class A1 extends Array {
constructor(args) {
super(args);
this[Symbol.isConcatSpreadable] = true;
}
}
class A2 extends Array {
constructor(args) {
super(args);
}
get [Symbol.isConcatSpreadable] () {
return false;
}
}
let a1 = new A1();
a1[0] = 3;
a1[1] = 4;
let a2 = new A2();
a2[0] = 5;
a2[1] = 6;
[1, 2].concat(a1).concat(a2) // [1, 2, 3, 4, [5, 6]]
Symbol.species
Symbol.species
属性就是为了解决这个问题而提供的。现在,我们可以为MyArray
设置Symbol.species
属性。
class MyArray extends Array {
static get [Symbol.species]() { return Array; }
}
上面代码中,由于定义了 Symbol.species
属性,创建衍生对象时就会使用这个属性返回的函数,作为构造函数。这个例子也说明,定义 Symbol.species
属性要采用 get
取值器。默认的 Symbol.species
属性等同于下面的写法。
static get [Symbol.species]() {
return this;
}
现在,再来看前面的例子
class MyArray extends Array {
static get [Symbol.species]() { return Array; }
}
const a = new MyArray();
const b = a.map(x => x);
b instanceof MyArray // false
b instanceof Array // true
上面代码中,a.map(x => x)
生成的衍生对象,就不是MyArray
的实例,而直接就是Array
的实例。
再看一个例子
上面代码中,T2
定义了Symbol.species
属性,T1
没有。结果就导致了创建衍生对象时(then
方法),T1
调用的是自身的构造方法,而T2
调用的是Promise
的构造方法。
总之,Symbol.species
的作用在于,实例对象在运行过程中,需要再次调用自身的构造函数时,会调用该属性指定的构造函数。它主要的用途是,有些类库是在基类的基础上修改的,那么子类使用继承的方法时,作者可能希望返回基类的实例,而不是子类的实例。
Symbol.match()
对象的Symbol.match属性指向一个函数,当执行str.match(myObject)
时,如果该属性存在,会调用它,返回该方法的返回值。
String.prototype.match(regexp)
// 等同于
regexp[Symbol.match](this)
class MyMatcher {
[Symbol.match](string) {
return 'hello world'.indexOf(string);
}
}
'e'.match(new MyMatcher()) // 1
Symbol.replace
对象的Symbol.replace
属性,指向一个方法,当该对象被String.prototype.replace
方法调用时,会返回该方法的返回值。
String.prototype.replace(searchValue, replaceValue)
// 等同于
searchValue[Symbol.replace](this, replaceValue)
下面是一个例子
const x = {};
x[Symbol.replace] = (...s) => console.log(s);
'Hello'.replace(x, 'World') // ["Hello", "World"]
Symbol.replace方法会收到两个参数,第一个参数是replace方法正在作用的对象,上面例子是Hello,第二个参数是替换后的值,上面例子是World。
Symbol.search
对象的Symbol.search属性,指向一个方法。当该对象被String.prototype.search
方法调用时,会返回该方法的返回值。
String.prototype.search(regexp)
// 等同于
regexp[Symbol.search](this)
class MySearch {
constructor(value) {
this.value = value;
}
[Symbol.search](string) {
return string.indexOf(this.value);
}
}
'foobar'.search(new MySearch('foo')) // 0
Symbol.split
对象的Symbol.split
属性,指向一个方法,当该对象被String.prototype.split
方法调用时,会返回该方法的返回值。
String.prototype.split(separator, limit)
// 等同于
separator[Symbol.split](this, limit)
下面是一个例子:
class MySplitter {
constructor(value) {
this.value = value;
}
[Symbol.split](string) {
let index = string.indexOf(this.value);
if (index === -1) {
return string;
}
return [
string.substr(0, index),
string.substr(index + this.value.length)
];
}
}
'foobar'.split(new MySplitter('foo'))
// ['', 'bar']
'foobar'.split(new MySplitter('bar'))
// ['foo', '']
'foobar'.split(new MySplitter('baz'))
// 'foobar'
上面方法使用 Symbol.split
方法,重新定义了字符串对象的 split
方法的行为,
Symbol.iterator
ES6 创造了一种新的遍历命令 for…of 循环,Iterator 接口主要供 for…of 消费,这个内置值是比较常见的,也是一个对象可以被for
of
迭代的原因,我们可以查看对象是否存在这个Symbol.iterator
值,判断是否可被for of
迭代,拥有此属性的对象被誉为可被
迭代的对象
,可以使用for…of循环
打印{}对象
可以发现,不存在Symbol.iterator
,所以{}对象
是无法被for of
迭代的,而[]数组
是可以,因为数组上面有
Symbol.iterator
属性
小提示:
原生具备 iterator 接口的数据(可用 for of 遍历)
- Array
- Arguments
- Set
- Map
- String
- TypedArray
- NodeList
手动给{}对象
加上Symbol.iterator属性,使其可以被for of
遍历出来
// 让对象变为可迭代的值,手动加上数组的可迭代方法
let obj = {
0: 'zhangsan',
1: 21,
length: 2,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for(let item of obj) {
console.log(item);
}
这里有个缺陷: 因为我们使用的是数组
原型上的Symbol.iterator
,所以对象必须是个伪数组才能遍历,自定义一个对象上的Symbol.iterator
属性,使其更加通用
let arr = [1, 52]
let iterator = arr[Symbol.iterator]()
console.log(iterator.next()) //{value: 1, done: false}
console.log(iterator.next()) //{value: 52, done: false}
console.log(iterator.next()) //{value: undefined, done: true}
//自定义[Symbol.iterator],使得对象可以通过for of 遍历
let obj = {
name: "Ges",
age: 21,
hobbies: ["ESgsg", "Sfgse", "Egs", "SEGSg"],
[Symbol.iterator]() {
console.log(this)
let index = 0
let Keyarr = Object.keys(this)
let len = Keyarr.length
return {
next: () => {
if(index >= len) return {
value: undefined,
done: true
}
let result = {
value: this[Keyarr[index]],
done: false
}
index++
return result
}
}
}
}
for(let item of obj) {
console.log(item)
}
使用generator和yield简化
//简洁版
let obj = {
name: "Ges",
age: 21,
hobbies: ["ESgsg", "Sfgse", "Egs", "SEGSg"],
*[Symbol.iterator]() {
for(let arg of Object.values(this)) {
yield arg;
}
}
}
for(let item of obj) {
console.log(item)
}
这样实现后,{}对象
就变得可以使用for of
遍历了,当然如果挂载到Obejct.prototype上所以对象都可以使用for of 遍历了
注: 需要自定义遍历数据的时候,要想到迭代器。
Symbol.toPrimitive
该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值
/*
* 对象数据类型进行转换:
* 1. 调用obj[Symbol.toPrimitive](hint),前提是存在
* 2. 否则,如果 hint 是 "string" —— 尝试 obj.toString() 和 obj.valueOf()
* 3. 否则,如果 hint 是 "number" 或 "default" —— 尝试 obj.valueOf() 和 obj.toString()
*/
let a = {
value: 0,
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'number': //此时需要转换成数值 例如:数学运算
return ++this.value;
case 'string': // 此时需要转换成字符串 例如:字符串拼接
return String(this.value);
case 'default': //此时可以转换成数值或字符串 例如:==比较
return ++this.value;
}
}
};
if (a == 1 && a == 2 && a == 3) {
console.log('OK');
}
当然自定义一个valueOf/toString
都是可以的,数据类型进行转换时,调用优先级最高的还是Symbol.toPrimitive
//存在[Symbol.toPrimitive] 属性,优先调用
let a = {
value: 0,
[Symbol.toPrimitive](hint) {
console.log(hint)
switch(hint) {
case 'number': //此时需要转换成数值 例如:数学运算时触发
return ++this.value;
case 'string': // 此时需要转换成字符串 例如:字符串拼接时触发
return String(this.value);
case 'default': //此时可以转换成数值或字符串 例如:==比较时触发
return ++this.value;
}
},
valueOf: function() {
console.log("valueOf")
return a.i++;
},
toString: function() {
console.log("toString")
return a.i++;
}
};
Symbol.toStringTag
在该对象上面调用Object.prototype.toString
方法时,如果这个属性存在,它的返回值会出现在toString
方法返回的字符串之中,表示对象的类型
class Person {
get [Symbol.toStringTag]() {
return 'Person';
}
}
let p1 = new Person;
console.log(Object.prototype.toString.call(p1)); // "[object Person]"
Symbol.unscopables
对象的Symbol.unscopables
属性,指向一个对象。该对象指定了使用with
关键字时,哪些属性会被with
环境排除。
总结:
总的来说Symbol用的最多的地方,还是它作为一个唯一值去使用
,但我们需要知道,它不仅仅只是代表一个唯一值,Symbol难的地方在于它的内置值
,都是底层
12. js 的数据类型
USONB : you are so nibility
-
U undefined(应该有值但未定义值 ,如: var a;)
-
S string symbol
-
O object
-
N null number
-
B boolean
1、 值类型(基本类型):
未定义(Underfined)、字符串(String)、Symbol、对空(Null)、数字(number)、布尔(Boolean)
- 原始值不可修改
- 原始值的比较就是值的比较
2、 引用数据类型:
对象(Object) 特殊对象: 数组(Array)、函数(Function)
- 对象是可变的
- 对象的比较不是值的比较,只有在引用相同时,两个值才会相等
3、 详细
声明新变量 可以使用关键词 “new” 来声明其类型
数组
var cars = new Array();
cars[0] = "BMW";
cars[1] = "Volvo";
cars[2] = "Audi";
var cars = new Array("BMW", "Volvo", "Audi");
var cars = ["BMW", "Volvo", "Audi"];
检测数组类型的方法:
- instanceof 操作符
let arr = ['1', '2', '3'];
console.log(arr instanceof Array); // true
- 对象的 constructor 属性
let arr = ['1', '2', '3'];
console.log(arr.constructor === Array); // true
- Array.isArray( ) 检验值是否为数组
let arr = ['1', '2', '3'];
console.log(Array.isArray(arr)); // true
对象:{name: value}
var person={firstname:"John", lastname:"Doe", id:5566}; // 声明可横跨多行
// 两种寻址方式
name=person.lastname;
name=person["lastname"];
- constructor属性:构造函数属性,可确定当前对象的构造函数。
var o = new Object();
console.log(o.constructor == Object); //true
var arr = new Array();
console.log(arr.constructor == Array); //true
-
hasOwnProperty(propertyName): 判断属性是否存在于当前对象实例中(而不是原型对象中)。作为参数的属性名(propertyName) 必须以字符串形式指定(例如:o.hasOwnProperty(“name”))。
-
isPrototypeOf(object): 用于检查传入的对象是否是传入对象原型。
-
propertyIsEnumerable(propertyName): 用于检查给定属性是否能够用for-in语句。与hasOwnProperty()方法一样,作为参数的属性名必须以字符串形式指定。
-
toLocaleString( ): 返回对象的字符串表示,该字符串与执行环境的地区对应。
-
toString( ): 返回对象的字符串表示。
-
valueOf( ): 返回对象的字符串、数值或者布尔值表示。通常与toString( )方法的返回值相同。
ECMAJS中object是所有对象的基础,因些所有对象都具有这些基本的属性和方法。
对象是可以比较,遍历比较key 和 value就行, Object.is(value1, value2)。
function test(person) {
person.age = 26
person = {
name: 'yyy',
age: 30
}
return person
}
const p1 = {
name: 'yck',
age: 25
}
const p2 = test(p1)
console.log(p1) // {name: "yck", age: 26} ???为什么p1的name没有改变
console.log(p2) // {name: "yyy", age: 30}
Boolean
undefined == true // false
undefined == false // false
undefined == null // true
undefined === null // false
null == false // false
null == true // false
"" == false // true
"" == true // false
0 == false // true
0 == true // false
-0 == false // true
-0 == true // false
NaN == false // false
NaN == true // false
NaN == NaN // false NaN与任何值都不相等,包括它自身
字符串String
字符串可以有单引号、双引号表示。字符串是不可变的,一旦创建,值就不能改变
要改变某个变量保存的字符串,首先要销毁原来的字符串,然后用另一个包含的字符串填充该变量
字符串转换:转型函数String(),适用于任何数据类型(null,undefined 转换后为null和undefined);
toString() 方法可以输出二进制、八进制、十进制,十六进制
(null,undefined没有toString()方法, 用 String 函数不返回这两个值的字面量)
Null
空对象指针
如果你定义了一个变量,但是想在以后把这个变量当做一个对象来用,那么最好将该对象初始化为null值。
不存在的原因是:
1、方法不存在
2、对象不存在
3、字符串变量不存在
4、接口类型对象没初始化
解决方法:
做判断处理的时候,放在设定值的最前面
BigInt
Javascript 中的任意精度整数,可以安全存储和操作大整数。即始超出 Number 能够表示的安全整数范围。是 chrome 67中的新功能。
4、 == 和 ===,即相等和全等
类型 | 例子 | |
---|---|---|
相等(“==”) | false == 0 // true | 使用相等(“==”)符号时,会自动转换符号两边的数据类型再进行比较,容易出错。 |
全等(“===”) | false === 0 //false | 使用全等符号时,不会自动转换数据类型,所以该符号也对数据类型进行了比较。 |
== 表示相同。
比较的是物理地址,相当于比较两个对象的 hashCode ,肯定不相等的。
类型不同,值也可能相等。
=== 表示严格相同。
例:同为 null/undefined ,相等。
简单理解就是 == 就是先比较数据类型是否一样。=== 类型不同直接就是 false。
5、 Infinity和NaN
类型 | 相同点 | 例子 | 说明 |
---|---|---|---|
Infinity | 都是Number类型 | 2/0 | 表示无限大,超过JS的number所能够表示的最大值。 |
NaN | 0/0 | 表示not a number,无法计算的结果。 |
//NaN是一个特殊的number,与其他所有值都不相等,包括它自身
NaN === NaN //false
//唯一识别NaN的方法
isNaN(NaN) //true
isNaN()函数,传入一个参数,函数会先将参数转换为数值。
如果参数类型为对象类型,会先调用对象的valueOf()方法, 再确定该方法返回的值是否可以转换为数值类型。如果不能,再调用对象的toString()方法,再确定返回值。
类型转换(=== !==
不类型转换)
(1) 显示类型转换
Number(mix)转换为数字 parseInt(string,radix)转换成整数 parseFloat(string)转换成浮点数
toString(radix) String(mix)转换成字符串 Boolean()转换成布尔值
(1.1) number 与 Number区别
number 基本数据类型
Number 对象是原始数值的包装对象。 具有对象的属性和方法
创建对象:
var myNum = new Number(value);
var myNum = Number(value);
当 Number() 和运算符 new 一起作为构造函数使用时,它返回一个新创建的 Number 对象。如果不用 new 运算符,把 Number() 作为一个函数来调用,它将把自己的参数转换成一个原始的数值,并且返回这个值(如果转换失败,则返回 NaN)。
(1.1) Number()转换规则
- 如果是布尔值,
true
和false
会被转换为1
和0
。
Number(true) // 1
Number(false) // 0
- 如果是数字,只是简单的传入和返回。
Number(1) // 1
Number(100) // 100
- 如果是
null
值,返回0
。
Number(null) // 0
- 如果是
undefined
,返回NaN
。
Number(undefined) // NaN
- 如果是字符串:
- 字符串中只包含数字(包括前面带正/负号的情况),则将其转换为十进制数值,数字前面有
0
的会被忽略(不管前面有几个0
,全部忽略),例如"010"
会转换成10
。 - 字符串中包含有效的浮点格式,如"1.1",则将其转换为对应的浮点数值。
- 字符串中包含有效的十六进制格式(一般用数字
0
到9
和字母A
到F
(或a~f
)表示,其中:A~F
表示10~15
,这些称作十六进制数字),如"0xf",
将其转换成相同大小的十进制整数值。 - 字符串为空,转换成
0
。 - 字符串中包含除了以上格式之外的字符,则转换为NaN,如字符串中既有数字又有字母的情况。
- 字符串中只包含数字(包括前面带正/负号的情况),则将其转换为十进制数值,数字前面有
// 规则一
Number("1") // 1
Number("123") // 123
Number("010") // 10 (前导的0会被忽略)
Number("-10") // -10
// 规则2
Number("1.1") // 1.1
Number("0.1") // 0.1
Number("0000.1") // 0.1 (前导的0会被忽略)
// 规则3
Number(0xf) // 15
// 规则4
Number("") // 0
Number(" ") // 0
Number('') // 0
// 规则5
Number("Hello Wolrd") // NaN
Number("0ff6600") // NaN
Number("ff6600") // NaN
Number("0.1a") // NaN
Number("10a") // NaN
Number("a10.1") // NaN
var goodsList = {
'pop':
}
var arr = [1,2,3,4]
Number(arr) // NaN
- 如果是对象,调用对象的valueOf(),空数组返回0,空对象返回NaN
为什么Number
和parseInt/parseFloat
计算结果不一样呢?
Number
转换规则是浏览器底层渲染规则,是浏览器的一个非常重要的方法,parseInt/parseFloat
是一个单独方法的规则,就是用来处理字符串的。Number
走的是最底层的机制,遇到其他类型,底层机制会告诉我们哪个类型跟哪个类型应该怎么转换,这是底层机制已经规定好的,比如遇到布尔值,true
转为1
,false
转为0
parseInt
和parseFloat
是额外提供的方法,就是浏览器提供的方法,它们的源码处理机制很简单,会先把传入的参数转换为字符串,然后在从左到右查找数字有效字符。
(1.2) toString ( radix)
作用:其他类型转成 string 的方法 radix 10进制要转换成的目标机制
支持:number、boolean、string、object
不支持:null 、undefined 【undefined null没有包装类,也没有原型,就是个原始值,没有toString方法】
null.toString() Uncaught TypeError: Cannot read property ‘toString’ of null
'123'.toString() "123"
true.toString() "true"
false.toString() "false"
[].toString() ""
// number object 不能直接.toString 会报错 Uncaught SyntaxError: Invalid or unexpected token
var demo = 123;
var num = demo.toString();
console.log(typeof(num) + ":" + num) // string:123
var demo = {};
var num = demo.toString();
console.log(typeof(num) + ":" + num) // string:[object Object]
var obj = {};
obj.toString(); // "[object Object]" // Object.prototype里面的toString()
var num = 123;
num.toString(); // ---> new Number(num).toString();
// 先找原型Number.prototype的toString,自己有 就不再找Number.prototype的原型(Number.prototype.__proto__ = Object.prototype)的toString了
// 方法的重写 : 原型上有这个方法,又写了个同一名字不同功能的方法
// 所以其他能使用toString()方法的都是调用的自己的toString()方法,而不是Object.prototype的,比如:
// Number.prototype.toString() Array.prototype.toString() Boolean.prototype.toString()
// String.prototype.toString()
// Object.prototype调用toString
Object.prototype.toString.call(123); // "[object Number]"
// 让123去调用call前面的方法
document.write();
实际调用的是toString之后的结果
var obj = Object.create(null);
obj.toString = function () {
return '122';
}
document.write(obj); // 执行出来的是 122
// 如果不写obj.toString方法,会报错Uncaught TypeError: Cannot convert object to primitive value 因为找不到该方法
// 无法将对象转换为原始值
(1.3) parseInt(string,radix)
parseInt(string,radix),将值转换为整型,用的比较多;
传入的第一个参数为字符串,第二个参数为整数,表示按照xx
进制转换,如传入参数10
表示按十进制规则转换。如果字符串中以0x
开头且后跟数字字符,就会将其当做一个十六进制整数。
如果字符串以"0"
开头且后跟数字字符,则会被当做一个八进制数来解析。如果不传第二个参数,会按照八进制解析。
除了数字其他全返回NaN,radix基底(2-36,不能是其他值),表示几进制
parseInt(true) NaN
parseInt(false) NaN
parseInt(null) NaN
parseInt(123.01) 123
parseInt('4a') 4 从数字位开始一直到非数字位
parseInt('4.3a') 4
parseInt('4a6') 4
parseInt(10,16) 16
parseInt(3,2) NaN 二进制里面没有3
(1.4) parseFloat(string)
parseFloat(string);将值转换为浮点型。 默认解析十进制值
- 从左到右依次解析字符,一直解析到字符串末尾,遇到非数字字符会或遇到第二个浮点数(在这之前的字符串都会被解析)会使解析停止,如
"3.14"
解析成3.14
,"3.14.1234"
解析成3.14
- 忽略参数首尾空白符
parseFloat(100.2) 100.2
parseFloat('100.2d') 100.2 从数字位开始一直到除了第一个点以外的非数字位
parseFloat('d') NaN
parseFloat('100.299.99') 100.299
parseFloat(100.299.99) Uncaught SyntaxError: missing ) after argument list
(1.5) String(mix)
String(123.9) "123.9"
String(null) "null"
String(undefined) "undefined"
String({}) "[object Object]"
String([]) ""
String(true) "true"
String(false) "false"
(1.6) Boolean()
数字0,空字符串’’,null,undefined,false转换为false
Boolean(true) true
Boolean(false) false
Boolean('aa') true
Boolean('123') true
Boolean('0') true
Boolean(0) false
Boolean(78) true
Boolean({}) true
Boolean([]) true
Boolean(null) false
Boolean(undefined) false
Boolean('') false
Boolean(' ') true
(2) 隐示类型转换
isNaN()内部隐式调用了Number()
如果参数类型为对象类型,会先调用对象的valueOf()方法, 再确定该方法返回的值是否可以转换为数值类型。如果不能,再调用对象的toString()方法,再确定返回值。
++ – + - (一元正负) 调用Number()
+加 调用String()
-减 * / % 调用Number()
&& || ! 调用Boolean()
< > <= >= 调用Number() 字符串与字符串比较ASCII码,字符串与数字调用Number() 最终结果类型是boolean
== != 调用Boolean()
undefined == null true
(3)变量未定义时
console.log(a) // a is not defined
// 只有typeof未定义变量时不报错,而且会显示变量的值
console.log(typeof(a)) // undefined
作业:
typeof(a) // "undefined" var a;typeof(a) ==> "undefined"
typeof(undefined) // "undefined"
typeof(NaN) // "number"
typeof(null) // "object"
var a = "123abc";
typeof(+a) // "number" 值为NaN
typeof(!!a) // "boolean" 值为true
typeof(a + "") // "number"xx "string" 值为113abc
1 == "1" // true
NaN == NaN // false
NaN == undefined // false
"11" + 11 // "1111"
1 === "1" // false
parseInt("123abc") // 123
var num = 123123.345789;
num.toFixed(3) //123123.345xx 123123.346 科学计数法保留3位小数(四舍五入)
typeof(typeof(a)) // "string"
alert(typeof(a))
alert(typeof(undefined))
alert(typeof(NaN))
alert(typeof(null))
var a = "123abc";
alert(typeof(+a))
alert(typeof(!!a))
alert(typeof(a + ""))
alert(1 == "1")
alert(NaN == NaN)
alert(NaN == undefined)
alert("11" + 11)
alert(1 === "1")
alert(parseInt("123abc"))
var num = 123123.345789;
alert(num.toFixed(3))
alert(typeof(typeof(a)))
6 、typeof
可以把数据类型当作字符串返回
1 | 2 | 3 | 4 | 5 | 6 | |
---|---|---|---|---|---|---|
typeof的运算参数: | 数字、NaN | 字符串 | 布尔值 | 对象{}、数组[]和null | 函数、Object | 运算数未定义、undefined |
例子: | typeof(123) | typeof(“123”) | typeof(true) | typeof([]) | typeof(function(){}) | typeof(arr) |
返回值: | “number” | “string” | “boolean” | “object” | “function” | “undefined” |
typeof null; //"object"
对于array、null和对象,typeof一律返回object。对此可以通过instanceof来区分
7、 instanceof
判断一个变量是否是某个对象的实例
var a = new Array();
a instanceof Array // true
a instanceof Object // true
// 判断 foo 是否是 Foo 类的实例 , 并且是否是其父类型的实例
function Aoo(){}
function Foo(){}
Foo.prototype = new Aoo(); // JavaScript 原型继承
var foo = new Foo();
console.log(foo instanceof Foo) // true
console.log(foo instanceof Aoo) // true
- instanceof 运算符判断一个对象是否是另一个对象的实例。返回true或false
- instanceof 运算符用来检测 constructor.prototype 是否存在于参数 obj 的原型链上(或者说:检测obj的原型链上是否存在constructor.prototype )
8、 toLocaleString ( )
把数组转成本地字符串 使用本地数字格式顺序
let arr = ['1','2','3'];
arr.toLocaleString() // "1,2,3"
13. 迭代器
原理
迭代器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作。
-
Iterator接口主要供 ES6新的遍历命令for … of使用
-
原生具备Iterator接口的数据(即可用for … of遍历)
- array arguments set map string typedArray nodeList
// 声明一个数组
const array = ['a', 'b', 'c', 'd', 'e'];
// 使用 for...of 遍历数组,返回值
for(let v of array) {
console.log(v); // a b c d e
}
// 使用 for...in 遍历数组,返回下标
for(let v in array) {
console.log(v); // 0 1 2 3 4
}
// 工作原理
let iterator = array[Symbol.iterator](); // 对象有个next方法
console.log(iterator); // Array Iterator {} -> __proto__: Array Iterator -> next: ƒ next()
// 调用对象的next方法
console.log(iterator.next()); // {value: "a", done: false}
console.log(iterator.next()); // {value: "b", done: false}
console.log(iterator.next()); // {value: "c", done: false}
console.log(iterator.next()); // {value: "d", done: false}
console.log(iterator.next()); // {value: "e", done: false}
console.log(iterator.next()); // {value: undefined, done: true}
Symbol.iterator()工作原理
- 1、创建一个指针对象,指向当前数据结构的起始位置
- 2、第一次调用对象的next方法,指针自动指向数据结构的第一个成员
- 3、接下来不断调用next方法,指针一直往后移动,直到指向最后一个成员
- 4、每调用next方法返回一个包含value和done属性的对象
需要自定义遍历数据的时候,要想到迭代器
应用:自定义遍历数据
console.log(iterator.next()); // {value: "a", done: false}// 声明一个对象
const obj = {
name: 'haha',
student: [
'a',
'b',
'c'
],
// 自定义遍历数据,调用结果返回student里面的数据
[Symbol.iterator]() {
let index = 0; // 索引变量
let _this = this; // 改变this指向
return {
next: function() {
if(index < _this.student.length) {
const result = {value: _this.student[index], done: false};
index++; // 下标自增
return result; // 返回结果
} else {
return {value: undefined, done: true};
}
}
}
}
}
// 遍历对象
for(let item of obj) {
console.log(item); // Uncaught TypeError: obj is not iterable不可迭代
// 加 [Symbol.iterator]()后返回结果: a b c
}
let iterator = obj[Symbol.iterator]();
console.log(iterator.next()); // {value: "a", done: false}
console.log(iterator.next()); // {value: "b", done: false}
console.log(iterator.next()); // {value: "c", done: false}
console.log(iterator.next()); // {value: undefined, done: true}
14. 生成器
生成器函数是ES6提供的一种异步编程解决方案。
三个特殊:
-
声明特殊: 在function与函数名之间要加
*
号 -
执行特殊: 必须用.next()才能输出值
-
生成器函数中可以出现 yield语句。 yield 函数代码的分隔符,后跟表达式或者字面量
function * gen() {
console.log('hello generator');
}
let generator = gen();
console.log(generator); // gen {<suspended>}
generator.next(); // hello generator 再返回next的返回结果 {value: undefined, done: true}
function * gen() {
console.log(111);
yield 'a';
console.log(222);
yield 'b';
console.log(333);
yield 'c';
console.log(444);
}
let generator = gen();
generator.next(); // 打印出:111 next的返回结果: {value: "a", done: false}
generator.next(); // 打印出:222 next的返回结果: {value: "b", done: false}
generator.next(); // 打印出:333 next的返回结果: {value: "c", done: false}
generator.next(); // 打印出:444 next的返回结果: {value: undefined, done: true}
// 遍历
for(let item of generator) {
console.log(item); // 111 a 222 b 333 c 444
}
function * gen() {
yield 'a';
yield 'b';
yield 'c';
}
let generator = gen();
// 遍历
for(let item of generator) {
console.log(item); // a b c
}
生成器函数的参数传递
function * gen(arg) {
console.log(arg); // AAA
let one = yield 'a';
console.log(one); // CCC
let two = yield 'b';
console.log(two); // DDD
let three = yield 'c';
console.log(three); // EEE
}
let generator = gen('AAA');
// next里面传的参数将作为前一个yield语句的返回结果
generator.next('BBB'); // 打印出:AAA next的返回结果: {value: "a", done: false}
generator.next('CCC'); // 打印出:CCC next的返回结果: {value: "b", done: false}
generator.next('DDD'); // 打印出:DDD next的返回结果: {value: "c", done: false}
generator.next('EEE'); // 打印出:EEE next的返回结果: {value: undefined, done: true}
生成器函数实例
// 回调地狱
setTimeout(function() {
console.log(111);
setTimeout(function() {
console.log(222);
setTimeout(function() {
console.log(333);
}, 3000)
}, 2000)
}, 1000)
// 异步编程 1s后控制台输出 111 2s后输出 222 3s后输出 333
function * gen() {
console.log(111);
yield 111;
console.log(222);
yield 222;
console.log(333);
yield 333;
}
let generator = gen();
setTimeout(function() {
generator.next();
}, 1000)
setTimeout(function() {
generator.next();
}, 2000)
setTimeout(function() {
generator.next();
}, 3000)
// 讲的方法
function one() {
setTimeout(() => {
console.log(111);
generator.next();
}, 1000)
}
function two() {
setTimeout(() => {
console.log(222);
generator.next();
}, 2000)
}
function three() {
setTimeout(() => {
console.log(333);
generator.next();
}, 3000)
}
function * gen() {
yield one();
yield two();
yield three();
}
let generator = gen();
generator.next();
实例2: 获取到111再获取222,每隔1s执行一次
function one() {
setTimeout(() => {
let num = 111;
generator.next(num);
}, 1000)
}
function two() {
setTimeout(() => {
let num = 222;
generator.next(num);
}, 1000)
}
function three() {
setTimeout(() => {
let num = 333;
generator.next(num);
}, 1000)
}
function * gen() {
let a = yield one();
console.log(a);
let b = yield two();
console.log(b);
let c = yield three();
console.log(c);
}
let generator = gen();
generator.next();
15. Promise
promise
promise构造函数,用来封装异步操作并可以获取其成功或失败的结果
1、promise构造函数:Promise(excutor) {}
2、Promise.prototype.then()方法
3、Promise.prototype.catch()方法
// 实例化Promise对象
const p = new Promise(function(resolve, reject) {
let data = "成功数据";
// resolve(data);
let err = "失败数据";
reject(err);
})
// 调用promise的then方法
p.then(function(value) {
console.log(value); // 成功数据 ===》 resolve执行
}, function(reason) {
console.error(reason); // 失败数据 ===》没有resolve时, reject执行
})
用promise读取文件:
// 1. 引入 fs 模块 (用到了node.js)
const fs = require('fs');
// 2. 调用方法读取文件
fs.readFile('文件地址', (err, data) => {
// 如果失败,抛出错误
if(err) throw err;
// 没有失败则输出内容
console.log(data);
})
// 3. 不用2,用promise读取
const p = new Promise(function(resolve, reject) {
fs.readFile('文件地址', (err, data) => {
if(err) reject(err)
resolve(data);
})
})
// 调用promise的then方法
p.then(function(value) {
console.log(value.toString()); // 转换为文字
}, function(reason) {
console.log('读取失败');
})
promise封装ajax:
// 接口地址: https://api.apiopen.top/getJoke
// 1. 创建对象
const xhr = new XMLHttpRequest();
// 2. 初始化
xhr.open("GET", "https://api.apiopen.top/getJoke");
// 3. 发送
xhr.send();
// 4. 绑定事件, 处理响应结果
xhr.onreadystatechange = function() {
// 判断状态, 4所有响应体都返回
if(xhr.readyState === 4) {
// 判断响应状态码 200-199表示成功
if(xhr.status >= 200 && xhr.status < 300) {
console.log(xhr.response); // 成功
} else {
console.error(xhr.status); // 失败
}
}
}
// promise 方法
const p = new Promise(function(resolve, reject) {
const xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.apiopen.top/getJoke");
xhr.send();
xhr.onreadystatechange = function() {
if(xhr.readyState === 4) {
if(xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response); // 成功
} else {
reject(xhr.status); // 失败
}
}
}
})
p.then(function(value) {
console.log(value);
}, function(reason) {
console.error(reason);
})
promise.then()
const p = new Promise(function(resolve, reject) {
let data = "成功数据";
resolve(data);
//let err = "失败数据";
//reject(err);
})
// promise.then方法的返回结果是 Promise 对象,对象状态由回调函数的执行结果决定
// 1. 回调函数返回结果是 非 Promise 类型的属性,则状态为成功,返回值为对象的成功值
// 2. 回调函数返回结果是 Promise 类型的属性, 内部promise状态决定then返回的promise状态
const result = p.then(value => {
console.log(value);
// 1. 非 Promise 类型的属性
return 123; // result: [[PromiseState]]:"fulfilled" [[PromiseResult]]: 123
// 不写return: [[PromiseState]]: "fulfilled" [[PromiseResult]]: undefined
// 2. promise 对象
return new Promise((resolve, reject) => {
resolve('ok'); // [[PromiseState]]: "fulfilled" [[PromiseResult]]: "ok"
reject('error'); // [[PromiseState]]: "rejected" [[PromiseResult]]: "error"
})
// 3. 抛出错误
throw new Error('xxxx'); // [[PromiseState]]: "rejected" [[PromiseResult]]: Error: xxxx
}, reason => {
console.error(reason);
})
console.log(result);
链式调用:
const fs = require('fs');
const p = new Promise(function(resolve, reject) {
fs.readFile('文件地址1', (err, data) => {
resolve(data);
})
})
// 调用promise的then方法
p.then(value => {
return new Promise((resolve, reject) => {
fs.readFile('文件地址2', (err, data) => {
resolve([value, data]); // 把第一个文件的内容与第二个文件的内容进行整合
});
})
}).then( value => {
return new Promise((resolve, reject) => {
fs.readFile('文件地址3', (err, data) => {
value.push(data); // 这个时候的value是[value, data]
resolve(value);
});
})
}).then( value => {
console.log(value.join('\r\n')); // 打印出三个文件的结果
})
promise.catch()
const p = new Promise((resolve, reject) => {
reject('出错')
})
// promise.catch:失败时的回调函数
p.catch(reason => {
console.error(reason); // 出错
})
16. Set
ES6新的数据结构 set(集合),类似于数组,但成员的值都是唯一的,集合实现了iterator接口,所以可以用 扩展运算符...
和for ... of
遍历。
集合的属性与方法:
1、 size 返回集合的元素个数
2、 add 增加一个新元素,返回当前集合
3、 delete 删除元素,返回boolean值
4、 has 检测集合中是否包含某个元素,返回boolean值
5、 clear 清空集合
let s = new Set();
console.log(s, typeof s); // Set(0) {} "object"
let s2 = new Set([1,2,4,1,5]);
console.log(s2); // Set(4) {1, 2, 4, 5} 自动去重
console.log(s2.size); // 4
s2.add(8); // Set(5) {1, 2, 4, 5, 8}
s2.delete(4); // true s2: {1, 2, 5, 8}
console.log(s2.has(1)); // true
s2.clear(); // s2: Set(0) {}
let s2 = new Set([1,2,4,1,5]);
for(let item of s2) {
console.log(item); // 1 2 4 5
}
console.log(...s2); // 1 2 4 5
set集合实践
let arr = [1,2,3,4,5,4,3,2,1];
let arr2 = [4,5,6,5,6];
// 1. 数组去重
console.log([...new Set(arr)]); // [1, 2, 3, 4, 5]
// 2. 交集
let result = [...new Set(arr)].filter(item => new Set(arr2).has(item));
console.log(result); // [4, 5]
// 3. 并集
let result = [...new Set([...arr,...arr2])];
console.log(result); // [1, 2, 3, 4, 5, 6]
// 4. 差集
let result = [...new Set(arr)].filter(item => !new Set(arr2).has(item));
console.log(result); // [1, 2, 3]
let result = [...new Set(arr2)].filter(item => !new Set(arr).has(item));
console.log(result); // [6]
17. Map
ES6 新增 Map 数据结构, 类似于对象,也是键值对的集合。但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当做键。Map也实现了iterator接口,所以可以用 扩展运算符...
和for ... of
遍历。
Map的属性与方法:
1、 size 返回Map的元素个数
2、 set(key, value) 增加一个新元素,返回当前Map
3、 get 返回键名对象的键值
4、 has 检测Map中是否包含某个元素,返回boolean值
5、 clear 清空集合,返回 undefined
6、 delete(key) 删除
let m = new Map();
console.log(m, typeof m); // Map(0) {} "object"
m.set('name', '张三'); // Map(1) {"name" => "张三"}
m.set('change', function() {
console.log('hahahahha'); // {"name" => "张三", "change" => ƒ}
})
let key = {
school: 'tsing'
}
m.set(key, ['a', 'b', 'c']); // {"name" => "张三", "change" => ƒ, {…} => Array(3)}
// 0: {"name" => "张三"}
// 1: {"change" => function() { console.log('hahahahha'); }}
// 2: {Object => Array(3)}
// key: {school: "tsing"}
// value: (3) ["a", "b", "c"]
m.delete('name'); // Map: {"change" => ƒ, {…} => Array(3)}
m.get('change'); // ƒ () {console.log('hahahahha');}
m.get('name'); // undefined
m.has('change'); // true
for(let item of m) {
console.log(item); // ["change", ƒ] [{…}, Array(3)]
}
18. class类
class
ES6引入了class类的概念,作为对象的模板。可以看做只是一个语法糖,ES5可以做到绝大部分功能,新的class写法只是让对象原型的写法更加清晰,更像面向对象编程的语法。
1、 class 声明类
2、 constructor 定义构造函数初始化
3、 extends 继承父类
4、 super 调用父级构造方法
5、 static 定义静态方法和属性
6、 父类方法可以重写
// ES5 实例化对象
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.speak = function() {
console.log('说话之道');
}
let a = new Person('zhangsan', 14);
console.log(a); // Person {name: "zhangsan", age: 14}
a.speak(); // 说话之道
// class 实例化对象
class Person{
// 构造函数 名字不能修改
constructor(name, age) {
this.name = name;
this.age = age;
}
// 方法必须采用该语法,不能使用ES5的对象完整形式
speak() {
console.log('说话之道');
}
}
let a = new Person('zhangsan', 14);
console.log(a); // Person {name: "zhangsan", age: 14}
a.speak(); // 说话之道
静态成员
实例对象无法访问构造函数对象上的属性
实例对象可以访问构造函数原型对象上的属性
// ES5
function Person() {
}
Person.name = '张三';
Person.change = function() {
console.log('aaaa');
};
Person.prototype.speak = function() {
console.log('说话之道');
}
let a = new Person();
console.log(a.name); // undefined undefined
console.log(a.change()); // a.change is not a function
console.log(a.speak()); // 说话之道
// class
class Person{
// 静态属性 属于类,不属于实例对象
static name = '张三';
static change() {
console.log('aaaa');
}
}
let a = new Person();
console.log(a.name); // undefined
console.log(Person.name); // 张三
对象继承
// ES5 构造函数继承
function Person(firstName, color) {
this.firstName = firstName;
this.color = color;
}
Person.prototype.sing = function() {
console.log('hahahah');
}
function SonPerson(firstName, color, lastName, size) {
Person.call(this, firstName, color);
this.lastName = lastName;
this.size = size;
}
// 设置子级构造函数的原型
SonPerson.prototype = new Person;
SonPerson.prototype.constructor = SonPerson;
// 声明子类的方法
SonPerson.prototype.speak = function() {
console.log('巴卡巴卡');
}
const b = new SonPerson('Lee', '黄色', 'jason', 'small');
console.log(b); // {firstName: "Lee", color: "黄色", lastName: "jason", size: "small"}
// __proto__: Person下面有speak方法,与speak同级的 __proto__: Object下有sing: ƒ ()
类继承
class Person{
constructor(firstName, color) {
this.firstName = firstName;
this.color = color;
}
sing() {
console.log('hahahah');
}
}
class SonPerson extends Person{
constructor(firstName, color, lastName, size) {
super(firstName, color);
this.lastName = lastName;
this.size = size;
}
speak() {
console.log('巴卡巴卡');
}
}
const b = new SonPerson('Lee', '黄色', 'jason', 'small');
console.log(b); // {firstName: "Lee", color: "黄色", lastName: "jason", size: "small"}
子类对父类方法的重写
直接在子类里面写个跟父类一样名称的方法
class Person{
constructor(firstName, color) {
this.firstName = firstName;
this.color = color;
}
sing() {
console.log('hahahah');
}
}
class SonPerson extends Person{
constructor(firstName, color, lastName, size) {
super(firstName, color);
this.lastName = lastName;
this.size = size;
}
speak() {
console.log('巴卡巴卡');
}
sing() { // 子类对父类方法的重写
console.log('重写');
}
}
const b = new SonPerson('Lee', '黄色', 'jason', 'small');
console.log(b.sing()); // 重写
get、set
class Person{
get name() {
console.log('获取name');
}
set name(val) { // 必须有一个参数 Setter must have exactly one formal parameter.
console.log('设置name')
}
}
let a = new Person();
console.log(a.name); // 获取name
a.name = 'aax'; // 设置name 还会返回 aax
console.log(a.name = 'aax'); // 设置name aax
19. ES6 数值扩展
1、Number.EPSILON 是 js 表示的最小精度 EPSILON 属性的值接近于 2.22E-16
判断两个数是否相等时,若二者差值小于EPSILON,可以认为二者相等
let equal = (a, b) => Math.abs(a-b) < Number.EPSILON
console.log(0.1 + 0.2 === 0.3); // false
console.log(equal(0.1 + 0.2, 0.3)); // true
2、二进制和八进制
二进制 0b开头,八进制0o开头,十六进制0x
3、Number.isFinite 检测是否有限
4、Number.isNaN 检测是否为 NaN(非数字)
5、Number.parseInt Number.parseFloat字符串转整数
6、Number.isInteger 是否为整数
7、Math.trunc 将数字的小数部分抹掉
8、Math.sign 判断一个数是 正数、负数 还是 0
Math.sign(0); // 0
Math.sign(10); // 1
Math.sign(-10); // -1
Math.sign(-0); // -0
20. ES6 对象方法扩展
1、 Object.is 判断两个值是否完全相等(基本等同于===
,但是个别的不等于 ===
)
console.log(Object.is(10, 10)) // true
console.log(Object.is(NaN, NaN)) // true
console.log(NaN === NaN) // false
2、 Object.assign 对象的合并
const a = {
name: 'zhangsan',
age: 14,
color: 'yellow',
like: 'play'
}
const b = {
name: 'lisi',
age: 19,
color: 'black'
}
Object.assign(a, b); // {name: "lisi", age: 19, color: "black", like: "play"}
// 后一个的值会覆盖前一个的值,如果后一个没有该属性值则保留前一个的属性值
3、 Object.setPrototypeOf 设置原型对象 Object.getPrototypeOf 获取原型对象
const person = {
name: 'zhangsan'
}
const address = {
city: ['beijing', 'shanghai']
}
Object.setPrototypeOf(person, address);
console.log(person); // name: "zhangsan"
// __proto__:
// city: (2) ["beijing", "shanghai"]
// __proto__: Object
console.log(Object.getPrototypeOf(person)) // city: (2) ["beijing", "shanghai"]
21. ES6 模块化
模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来
模块化的优势:
-
防止命名冲突
-
代码复用
-
高维护性
模块化规范产品:
- ES6之前的:
- CommonJS => NodeJS browserify
- AMD => RequireJS
- CMD => seaJS
ES6 模块化语法:
- export 命令用于规定模块的对外接口
- import 命令用于输入其他模块提供的功能
// 分别暴露接口数据 假设文件地址为: a.js
export let name = 'zhangsan'
export function speak() {
console.log('aaa');
}
<script type="module">
import * as chongmingminga from "./a.js"; // 引入a.js模块
console.log(chongmingminga); // name: 'zhangsan' speak: f speak()
</script>
// 统一暴露
let name = 'zhangsan'
function speak() {
console.log('aaa');
}
export {name, speak};
// 默认暴露
export default {
let name = 'zhangsan'
function speak() {
console.log('aaa');
}
}
// 数据默认在default下,比如要调用函数speak,需要chongmingminga.default.speak()
引入模块数据的语法:
// 1、通用的导入方式
import * as chongmingminga from "./a.js";
// 2、解构赋值形式
import {name, speak} from "./a.js";
// 如果引入多个文件,里面有属性名重名,可以使用别名
import {name as newName, speak} from "./a.js";
// 默认暴露的解构赋值形式,不能直接用default要用别名
import {default as m3} from "./a.js";
// 3、简便形式,只针对默认暴露
import m3 from "./a.js";
Babel网址将js语法转换为浏览器兼容的ES5语法
// 1、安装工具 babel-cli babel-preset-env browserify(或者webpack)
npm i babel-cli babel-preset-env browserify -D
// 2、
npx babel a.js文件的上一层地址(src/js) -d dist/js --presets=babel-preset-env
// 这样得到的dist/js/a.js里面的代码就是兼容浏览器的
// 3、打包 -o 输出到哪
npx browserify dist/js/a.js -o dist/bundle.js
// 在script里面直接引入bundle文件就行
<script src="dist/bundle.js"></script>
ES6模块化引入NPM包
// 1、安装jquery
npm i jquery
// 2、a.js里面修改背景颜色
import $ from 'jquery'; // 等同于 const $ = require("jquery");
$('body').css('background', 'pink');
// 3、重新打包后生效