es6笔记

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  
    

实例:

  1. 点击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);
});
  1. 从数组中返回偶数的元素
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对象绑定一下。
区别callapplybind
调用立即调用立即调用先返回一个函数稍后()调用
传参把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. 函数参数默认值

  1. 形参初始值 具有默认值的参数,一般位置要靠后(如果放在前面,传入的值会覆盖掉,后面的参数仍然是undefined,结果是NaN)

    function add(a, b, c=4) {
        return a + b + c;
    }
    let result = add(1, 2);
    console.log(result); // 1+2+4
    
  2. 可与解构赋值结合

    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 放在形参里面

应用:

  1. 数组的合并

    const kuaizi = ['王太利', '肖央'];
    const fenghuang = ['曾毅', '玲花'];
    const zuixuanxiaopingguo = kuaizi.concat(fenghuang); // ES5
    const zuixuanxiaopingguo = [...kuaizi, ...fenghuang]; // 扩展运算符
    console.log(zuixuanxiaopingguo);// ["王太利", "肖央", "曾毅", "玲花"]
    
  2. 数组的克隆

    const a = ['A', 'B', 'C'];
    const b = [...a]; // ['A', 'B', 'C'];  浅拷贝??? 引用类型数据变化
    console.log(b);  // ["A", "B", "C"]
    
  3. 将伪数组转化为真正的数组

    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的属性,只拷贝对象自身的可枚举的属性。
  • 设置可枚举属性

    Object.defineProperty(person,'age',{
        enumerable:true //可以被枚举
    });
    
    1. Object.defineProperty(obj, prop, descriptor)方法有三个参数
      第一个:目标对象
      第二个:目标属性,字符串
      第三个:对目标属性的行为,放在对象里
    2. enumerable为true时表示可枚举,enumerable为false表示不可枚举;
    3. 开发者自己定义的对象person中的所有属性默认都是可枚举的;
  • 判断是否可枚举propertyIsEnumerable

    用法: obj.propertyIsEnumerable(“属性名”);

    1. 这个属性必须属于实例的,并且不属于原型。

    2. 这个属性必须是可枚举的。

    3. 如果对象没有指定的属性,该方法返回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即使该属性的值为nullorundefined,也返回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不可枚举

  1. 如果属性名的类型是Number,那么Object.keys返回值是按照key从小到大排序
  2. 如果属性名的类型是String,那么Object.keys返回值是按照属性被创建的时间升序排序。
  3. 如果属性名的类型是Symbol,那么逻辑同String相同
  4. 负数是作为字符串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被调用时,执行以下步骤(其实就一步):

  1. Return ! OrdinaryOwnPropertyKeys(O).

OrdinaryOwnPropertyKeys是这样规定的:

  1. 声明变量keys值为一个空列表(List类型)
  2. 把每个Number类型的属性,按数值大小升序排序,并依次添加到keys
  3. 把每个String类型的属性,按创建时间升序排序,并依次添加到keys
  4. 把每个Symbol类型的属性,按创建时间升序排序,并依次添加到keys
  5. 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类型非常简单:

  1. 先声明一个变量array,值是一个空数组
  2. 循环属性列表,将每个元素添加到array
  3. array返回

上面介绍的排序规则同样适用于下列API:

  1. Object.entries
  2. Object.values
  3. for...in循环
  4. Object.getOwnPropertyNames
  5. 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所能够表示的最大值。
NaN0/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()转换规则
  • 如果是布尔值,truefalse会被转换为10
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",则将其转换为对应的浮点数值。
    • 字符串中包含有效的十六进制格式(一般用数字09和字母AF(或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

为什么NumberparseInt/parseFloat计算结果不一样呢?

  • Number转换规则是浏览器底层渲染规则,是浏览器的一个非常重要的方法,parseInt/parseFloat是一个单独方法的规则,就是用来处理字符串的。
  • Number走的是最底层的机制,遇到其他类型,底层机制会告诉我们哪个类型跟哪个类型应该怎么转换,这是底层机制已经规定好的,比如遇到布尔值,true转为1false转为0
    parseIntparseFloat是额外提供的方法,就是浏览器提供的方法,它们的源码处理机制很简单,会先把传入的参数转换为字符串,然后在从左到右查找数字有效字符。
(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

可以把数据类型当作字符串返回

123456
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、重新打包后生效
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值