JavaScript高级

JavaScript 高级 made by cherry

代码执行(全局)

1.代码解析,产生Global Object全局对象(会将所有的全局变量初始化)

2.运行代码:

1)执行代码时,Js(javascript),内部会有一个执行上下文栈(ECStack)(函数调用栈)

2)如果执行的代码是全局作用域代码(作用域概念后面解析),js 引擎为了执行全局代码,需要创建,全局执行上下文 (GEC)(全局作用域代码需要执行的时候创建)

//代码解析时产生的Global Object 全局对象
var globalObject = {
  //初始化,内置类
  window: globalObject,
  Math: "Math类",
  //预解析时产生的变量(全局变量),初始值为undefined
  name: undefined,
  age: undefined,
};
var name = "cherry"; //全局作用域代码
//var name; 第一步,
//name = "cherry"; 第二步 给name 赋值
console.log(name); //cherry
console.log(age); //undefined (age还未进行赋值打印初始值 undefined)
var age = "18";

函数执行过程(全局)

当以普通方式定义函数时(function foo(){}),在 Js 编译解析时,全局对象(GO)添加 foo 属性,并会创建存储函数的一个堆空间(假定内存空间地址为 0xf00)来保存函数执行体,以及他的作用域链(栈),此时,全局对象(GO)的 foo 属性将被赋值为 0xf00,编译完成后 ,调用 foo 函数(foo())时,可以直接访问 0xf00 函数体并创建 foo 函数执行上下文对象,并初始化函数执行时所需要的作用域数据变量环境(VO),当函数执行完后 ,函数执行上下文对象将被垃圾回收.

foo("foo"); // foo函数
AO = { //初始化函数执行时所需要的变量(虚拟)
  params = undefined,
  name:undefined,
  age:undefined
}

function foo(params) {
  console.log(params); //foo
  var name = "cherry";
  console.log(name); // cherry
  console.log(age); // undefined
  var age = "18";
  console.log("foo函数");
}

函数执行上下文对象

函数执行上下文对象存在**Scope chain(作用域链)**属性,该属性保存着(变量(ActivationObject) ->链 …),当前函数的 scope 同时也保存着 Parent scope …

作用域链:与函数定义的位置有关,与调用位置无关(重点)

变量的查找方式scope -> parent scope -> …

ES5 之前) , 每一个执行上下文都被会关联一个变量对象(variable object , VO),在执行代码中的变量和函数声明会作为属性添加到 VO 中,对于函数来说,参数也会被添加到 VO 中.

ES5 之后),每一个执行上下文对象都会被关联一个变量环境(VariableEnvironment)中, 在执行代码中变量和函数声明会作为环境记录(Environment Record)添加到变量环境中,对于函数来说,参数也会被作为环境记录添加到变量环境中.

示例 1:

var message = "global";
function foo() {
  console.log(message);
}
function bar() {
  var message = "bar";
  foo();
}

bar(); //global

示例二:

var n = 100;
function foo() {
  n = 200;
}
foo();
console.log(n); //200

示例三:

function foo() {
  console.log(n); //undefined
  var n = 200;
  console.log(n); //200
}
var n = 100;
foo(); // undefined 200

示例四:

var n = 100;
function bool() {
  console.log(n); // 100
}

function foo() {
  var n = 200;
  console.log(n); //200
  bool();
}

foo();
console.log(n); //100

示例五:

var n = 100;
function foo() {
  console.log(n); //undefined
  return;
  var n = 200;
}
foo();

示例六:

function foo() {
  var a = (b = 200);
}
foo();
console.log(b); //200
console.log(a); //报错

内存管理

JavaScript 的内存管理:

内存管理的生命周期:申请内存->使用内存(存放数据)->释放内存

简单数据类型存放在栈内存,复杂数据类型(object)存放在堆空间

JavaScript 的垃圾回收机制(GC)
常见的 GC 算法–引用计数

创建的每一个对象都有一个retainCount 属性来保存实时被引用的次数,当 retainCount 为 0 的时候,就证明,没有东西来引用这个对象,那么这个对象将被 GC 回收掉.

引用计数,缺点:循环引用,相互引用(会存在内存泄漏) ,解决办法,对象使用完时,置为 null

常见的 GC 算法–标记清除(js)

设置一个根对象(root object),垃圾回收器会定期从这个根开始,寻找所有从根开始有引用到的对象,对于那些没有引用到的对象,就认为是不可用对象(规避掉了循环引用);将被 GC 回收掉.

闭包

闭包的定义

一个函数和对其周围状态(lexical environment , 词法环境)的引用(可以访问外层作用域的变量),捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure),也就是说,闭包能让你在内层中访问到其他层级的作用域,在 JavaScript 中,当函数创建时,闭包对象就已经被定义.

闭包原理

函数被创建时,会在堆空间里创建一个函数对象,该对象,存在着对父级作用域的引用,故,当上层函数执行完后,它的作用域,AO 对象并没有销毁.

闭包的内存泄漏

当闭包对象不在使用时,他依旧保存在内存空间

//闭包(bar函数 与 bar的父级作用域的组合)
function foo() {
  var n = 100;
  return function bar() {
    console.log(n); // 100
  };
}
var fn = foo();
fn();
//当fn函数不在使用时(防止内存泄漏)
fn = null;

闭包中引用的自由变量的销毁情况

根据 ECMA 规范:对象被引用时,不可被销毁,所以 name , age 自由变量是不会被释放的,但是,以常理来说,没有被使用的变量他们应当被回收,所以V8 引擎对性能的优化会自动回收掉没有使用的自由变量

//闭包中引用的自由变量的销毁情况
function foo() {
  var name = "cherry";
  var age = 18;
  return function bar() {
    console.log(name); //cherry
  };
}
var fn = foo();
fn();

闭包的应用场景

保护函数内的变量安全:如迭代器、生成器。

在内存中维持变量:缓存数据、柯里化。

this 关键字

全局作用域的 this(浏览器指向是 window , node 指向{})

node 环境默认会让单个 js 文件作为一个模块,this 的表现是指向这个模块,默认是{}

this.exports = {}
(function).call(this.exports , ...)

函数中 this 的绑定与函数定义的位置无关,this 的绑定和调用方式,以及调用者有关,this 是在运行时被绑定的

function foo() {
  console.log(this);
}
var p = {
  name: "cherry",
  foo: foo,
};

foo(); //window对象
p.foo(); //p对象
foo.call("ccc"); //String对象
this 的默认绑定

独立调用时,this 指向 window

function foo() {
  return function () {
    console.log(this); //window
  };
}
var fn = foo();
fn();
this 的隐式绑定

object.fn( ) ,fn 函数的执行上下文会将 this 绑定在 object 上

var p = {
  name: "cherry",
  foo: function () {
    console.log(this.name);
  },
};
p.foo(); //cherry
var fn = p.foo;
fn(); //  "" this是window
this 的显式绑定
function foo() {
  console.log(this.name);
}
foo.call(); // ""
foo.apply(); // ""
var p = {
  name: "cherry",
};
foo.call(p); //cherry
foo.apply(p); // cherry

call 与 apply都可以将函数的 this 绑定到 p 对象上面,注意,p 对象必须为函数的第一个参数,否则 this 无法按照意愿的方式绑定.

call,apply 的区别,当函数需要传参数的时候, call 的参数集,是依次放入,而apply 的(参数集>1)的时候必须是数组结构保存传参.

function foo(num1, num2) {
  console.log(this.name, num1 + num2);
}
var p = {
  name: "cherry",
};
foo.call(p, 10, 20); //cherry 30
foo.apply(p, [10, 20]); //cherry 30

bind,使用 bind 方法绑定 this,那么返回的函数单独调用的 this 永远都是绑定的 this

传参如下:

var bar = foo.bind(p);
bar(10, 20); //cherry 30
//如果返回的函数再次使用call绑定this,将无效
var bar = foo.bind("a");
bar.call(p, 10, 20); // undefined 30
new 绑定

函数使用 new 关键字调用时,会把这个函数认为是构造器,这时候函数的 this 是指向调用构造器创建出来的对象

this = 创建出来的对象

构造器的返回值,相当于就是 this

// new 关键字
function Person(name, age) {
  this.name = name;
  this.age = age;
  console.log(this); // {name:"cherry" , age:18}
}

const p = new Person("cherry", 18);
console.log(p); // {name:"cherry" , age:18}
this 绑定的优先级

(new 绑定 >显示绑定>隐式绑定>默认绑定)

testCode

function bar() {
  console.log(this.name);
}
var np = {
  name: "cherry",
};
//定义p对象及方法
var p = {
  name: "kobe",
  foo: function () {
    console.log(this.name);
  },
  //使用bind绑定this指向np对象
  bar: foo.bind(np),
};

//call apply 与 隐式绑定
p.foo(); //kobe
p.foo.call(np); //cherry
p.foo.apply(np); //cherry
p.bar(); //cherry

//new 与隐式绑定

var O = new p.foo(); // undefined
console.log(O); // foo{} 对象

//new 与 bind
var newBar = bar.bind(np);
var B = new newBar(); //undefined
console.log(B); //bar{} 对象

this 的特殊绑定

function foo() {
  console.log(this);
}
foo.apply(null); //全局对象 window globalobject
foo.apply(undefined); //全局对象window globalobject
箭头函数

箭头函数不绑定 this, arguments 属性

箭头函数不能作为构造函数(笔者见解:因为当,new 构造函数的时候,构造函数的 this 是会指向创建出来的新对象,但是箭头函数没有 this,无法指向,故不能作为构造函数)

var foo = (参数) => {
  函数执行体;
};

箭头函数的 this

箭头函数无法绑定 this,箭头函数的 this 是他上层作用域的 this

var p = {
  names: [],
  getNames: function () {
    //模拟网路请求
    //方式一
    // setTimeout(function () {
    //   var res = ["cherry", "kobe"];
    //   this.names = res;
    // }, 2000);
    //方式二
    setTimeout(() => {
      var res = ["cherry", "kobe"];
      this.names = res;
    }, 2000);
  },
};

p.getNames();

setTimeout(() => {
  //方式一  [] 不是预期结果 ,因为定时器执行内部函数时,函数的调用是默认this
  //方式二  ["cherry", "kobe"] ,当定时器中的回调函数是箭头函数时,箭头函数无法绑定this,箭头函数	 //的this是他上层作用域的this,这里是(p)
  console.log(p.names);
}, 3000);
面试题

part1:

var name = "window";
var p = {
  name: "cherry",
  foo1: function () {
    console.log(this.name);
  },
  foo2: () => {
    console.log(this.name);
  },
  foo3: function () {
    return function () {
      console.log(this.name);
    };
  },
  foo4: function () {
    return () => {
      console.log(this.name);
    };
  },
};

var p1 = {
  name: "kobe",
};

p.foo1(); //cherry (隐式绑定)
p.foo1.call(p1); //kobe(显示绑定大于隐式绑定)

p.foo2(); // window(上层作用域 window)
p.foo2.call(p); //window

p.foo3()(); //window (独立函数调用)
p.foo3.call(p1)(); //window (独立函数调用)
p.foo3().call(p2); //kobe (显式函数调用)

p.foo4()(); //cherry (在上层作用域查找)
p.foo4.call(p1)(); //kobe (在上层作用域查找)
p.foo4().call(p1); //cherry (在上层作用域查找)

part2:

var name = "window";
function Person(name) {
  this.name = name;
  (this.foo1 = function () {
    console.log(this.name);
  }),
    (this.foo2 = () => console.log(this.name)),
    (this.foo3 = function () {
      return function () {
        console.log(this.name);
      };
    }),
    (this.foo4 = function () {
      return () => {
        console.log(this.name);
      };
    });
}
var person1 = new Person("person1");
var person2 = new Person("person2");

person1.foo1();
person1.foo1.call(person2);

person1.foo2();
person1.foo2.call(person2);

person1.foo3()();
person1.foo3.call(person2)();
person1.foo3().call(person2);

person1.foo4()();
person1.foo4.call(person2)();
person1.foo4().call(person2);

//解析

// 隐式绑定
person1.foo1(); // peron1
// 显示绑定优先级大于隐式绑定
person1.foo1.call(person2); // person2

// foo是一个箭头函数,会找上层作用域中的this,那么就是person1
person1.foo2(); // person1

// foo是一个箭头函数,使用call调用不会影响this的绑定,和上面一样向上层查找
person1.foo2.call(person2); // person1

// 调用位置是全局直接调用,所以依然是window(默认绑定)
person1.foo3()(); // window

// 最终还是拿到了foo3返回的函数,在全局直接调用(默认绑定)
person1.foo3.call(person2)(); // window

// 拿到foo3返回的函数后,通过call绑定到person2中进行了调用
person1.foo3().call(person2); // person2

// foo4返回了箭头函数,和自身绑定没有关系,上层找到person1
person1.foo4()(); // person1

// foo4调用时绑定了person2,返回的函数是箭头函数,调用时,找到了上层绑定的person2
person1.foo4.call(person2)(); // person2

// foo4调用返回的箭头函数,和call调用没有关系,找到上层的person1
person1.foo4().call(person2); // person1

part3:

var name = "window";
function Person(name) {
  this.name = name;
  this.obj = {
    name: "obj",
    foo1: function () {
      return function () {
        console.log(this.name);
      };
    },
    foo2: function () {
      return () => {
        console.log(this.name);
      };
    },
  };
}
var person1 = new Person("person1");
var person2 = new Person("person2");

person1.obj.foo1()();
person1.obj.foo1.call(person2)();
person1.obj.foo1().call(person2);

person1.obj.foo2()();
person1.obj.foo2.call(person2)();
person1.obj.foo2().call(person2);

//解析

// obj.foo1()返回一个函数
// 这个函数在全局作用于下直接执行(默认绑定)
person1.obj.foo1()(); // window

// 最终还是拿到一个返回的函数(虽然多了一步call的绑定)
// 这个函数在全局作用于下直接执行(默认绑定)
person1.obj.foo1.call(person2)(); // window
person1.obj.foo1().call(person2); // person2

// 拿到foo2()的返回值,是一个箭头函数
// 箭头函数在执行时找上层作用域下的this,就是obj
person1.obj.foo2()(); // obj

// foo2()的返回值,依然是箭头函数,但是在执行foo2时绑定了person2
// 箭头函数在执行时找上层作用域下的this,找到的是person2
person1.obj.foo2.call(person2)(); // person2

// foo2()的返回值,依然是箭头函数
// 箭头函数通过call调用是不会绑定this,所以找上层作用域下的this是obj
person1.obj.foo2().call(person2); // obj

该面试题及解析,摘至,

coderwhy 老师,原文链接:https://mp.weixin.qq.com/s/hYm0JgBI25grNG_2sCRlTA

实现 call , apply , bind
//测试对象
const p = {
  name: "cherry",
};
call
//call
Function.prototype.myCall = function (thisArg, ...arg) {
  //获取foo函数并执行
  const fn = this;
  //当存入的值是null undefined 时默认为 globalThis
  thisArg =
    thisArg !== null || thisArg !== undefined ? Object(thisArg) : globalThis;
  thisArg.fn = fn; //给thisArg对象添加方法fn
  //展开运算符arg
  const result = thisArg.fn(...arg); //执行fn
  delete thisArg.fn; //删除当前临时对象
  return result; //返回结果
};
function foo(num) {
  console.log("cherry", num);
}
foo.myCall(null, 18); //cherry 18
apply
//apply
Function.prototype.myApply = function (thisArg, arg) {
  const fn = this;
  thisArg =
    thisArg !== null || thisArg !== undefined ? Object(thisArg) : globalThis;
  thisArg.fn = fn;
  const result = arg ? thisArg.fn(...arg) : thisArg.fn();
  delete thisArg.fn;
  return result;
};

function bar(num1, num2) {
  console.log(this.name, num1 + num2);
}

bar.myApply(p, [10, 8]); //cherry 18
bind
Function.prototype.myBind = function (thisArg, ...arg) {
  //将传入的函数保存
  const fn = this;
  thisArg =
    thisArg !== null || thisArg !== undefined ? Object(thisArg) : globalThis;
  function proxyFn(...args) {
    //使用闭包将thisArg对象 , fn函数 保存,后面在调用的时候,this永远指向传入的对象
    thisArg.fn = fn;
    //合并两次调用时传入的函数
    const finalArgs = [...args, ...arg];
    //使用thisArg调用fn函数,this指向thisArg
    var result = thisArg.fn(...finalArgs);
    delete thisArg.fn;
    //返回结果
    return result;
  }
  return proxyFn;
};

const newFoo = foo.myBind(p, 18);
newFoo(); // cherry 18

arguments 对象

arguments 是一个对应传递给函数的参数的类数组对象

function foo() {
  console.log(arguments); //[Arguments] { '0': 1, '1': 3, '2': 2, '3': 3, '4': 5 }
  console.log(arguments[1]); //3
  console.log(arguments.length); //5
  console.log(arguments.callee); // foo函数
}
foo(1, 3, 2, 3, 5);

arguments 是可迭代对象,但是他没有数组的原型方法

//简单实现Array.prototype.slice()方法
Array.prototype.mySlice = function (start, end) {
  var arr = this;
  var newArr = [];
  if (!start || !end) {
    start = 0;
    end = arr.length;
  }
  for (let i = start; i < end; i++) {
    newArr.push(arr[i]);
  }
  return newArr;
};
var arr = [1, 2, 3, 4];
console.log(arr.mySlice(1, 3)); // 2 3
Array.prototype.mySlice.call(arr, 1, 3); //2 ,3

可见,数组对象上的原型方法,本质是对调用者 this 数组的遍历,那么我们可以改变 this 的指向伪数组,类数组对象.

示例:对伪数组和 likeArray 的复制

function foo() {
  //将数组原型上的slice方法的this指向arguments类数组对象
  var arr = Array.prototype.slice.call(arguments);
  //展开运算符同样可以对伪数组,类数组对像进行数组化复制
  var arr = Array.prototype.slice.call(arguments);
  var arr1 = [].slice.call(arguments);
  var arr2 = [...arguments];
  var arr3 = Array.from(arguments);
  console.log(arr, arr1, arr2, arr3); //[1,2,3,4,5],[1,2,3,4,5]...
}
foo(1, 2, 3, 4, 5);

箭头函数没有 arguments 对象

箭头函数的参数问题(推荐使用"剩余参数特性")

var bar = (num1, num2, ...arg) => {
  //arg是数组
  console.log(num1, num2, ...arg); //1,2,3,4
};
bar(1, 2, 3, 4);

JavaScript 的函数式编程

纯函数

输入确定的值,一定会产生确定的输出,函数执行中,不允许产生副作用(作用域链中的自由变量产生依赖).

var arr = [1, 2, 3, 4];
var newArr = arr.splice(1, 3);
console.log(newArr); // [2,3,4]
console.log(arr); // [1]

不难看出,splice 方法对原始数组产生了影响,看下一个例子

var arr = [1, 2, 3, 4];
var newArr = arr.slice(1, 3);
console.log(newArr); // [2,3]
console.log(arr); // [1,2,3,4]

可以看出 slice 方法对原始数组没有产生任何副作用(影响)

所以, slice 是纯函数,而 splice 不是纯函数

示例 2:

var num = 5;
function foo(info) {
  return num + info;
}
console.log(foo(5)); //10
num = 10;
console.log(foo(5)); //15

外面 num 变量,虽然没有在函数内部修改它,但是 num 变量自己修改时,函数的参数没有变但是输出的值却变了,所以,foo 不是纯函数

函数的柯里化

是把接收多个参数的函数,变成接收一个单一参数(最初函数只有一个参数)的函数,并且返回接受余下的参数,而且返回结果是新函数的技术

如果你固定某些参数,你将得到接受余下参数的一个函数

function foo(x, y, z) {
  return x + y + z;
}

console.log(foo(10, 20, 30)); //60

//柯里化
function bar(x) {
  return function (y) {
    return function (z) {
      return x + y + z;
    };
  };
}
console.log(bar(10)(20)(30)); //60

//柯里化的简化(书写代码时不建议使用简化)
var baz = (x) => (y) => (z) => x + y + z;
console.log(baz(10)(20)(30)); //60
柯里化优点

1.让函数的职责单一

希望一个函数处理问题尽可能单一,而不是一大堆处理过程交给一个函数进行处理

2.可定制化函数执行逻辑(传入函数)

3.代码可复用性强(在上层函数中写入公共逻辑)

function foo(x, y, z) {
  x = x + 2;
  y = y * 2;
  z = z / 2;

  return x + y + z;
}

console.log(foo(1, 2, 10)); //12
function bar(x) {
  x = x + 2; //逻辑一
  return function (y) {
    y = y * 2; //逻辑二
    return function (z) {
      z = z / 2; //逻辑三
      return x + y + z;
    };
  };
}

console.log(bar(1)(2)(10)); //12
柯里化函数的实现

功能:自动将函数柯里化

//自动柯里化
function myCurrying(fn) {
  //fn是传入的原始函数
  return function curred(...args) {
    //如果调用时,把参数全部都传入,就直接调用原函数fn
    if (args.length >= fn.length) {
      //如果原函数显示绑定的this,使用apply
      return fn.apply(this, args);
    } else {
      //如果参数是不确定传入时。返回一个函数
      return function (...newArgs) {
        //递归调用,将参数合并
        return curred.apply(this, args.concat(newArgs));
      };
    }
  };
}
function foo(x, y, z) {
  x = x + 2;
  y = y * 2;
  z = z / 2;

  return x + y + z;
}
var newCurrying = myCurrying(foo);

console.log(newCurrying(1, 2, 10)); //12
console.log(newCurrying(1)(2)(10)); //12
console.log(newCurrying(1, 2)(10)); //12
组合函数

如果你的执行逻辑是多个函数,并且每一个函数都会处理参数并返回结果,可以使用组合函数或者链式调用函数,这里讲组合函数

组合函数的实现

function myCompose(...fns) {
  var length = fns.length;
  fns.some((fn) => {
    if (typeof fn !== "function") {
      throw new TypeError(`${fn} is not function`);
    }
  });
  return function (...args) {
    var index = 0;
    //先执行传入的第一个函数并拿到结果
    var result = length ? fns[index].apply(this, args) : args;
    while (++index < length) {
      //将新的结果赋给result并绑定this
      result = fns[index].call(this, result);
    }
    //返回结果
    return result;
  };
}

function multiplication(num) {
  return num * 2;
}

function square(num) {
  return num ** 2;
}

const compose = myCompose(multiplication, square);
console.log(compose(2)); // 16

JavaScript 其他语法补充

with 语句

with(obj){},使函数执行体的最近作用域指向 obj

// with 可以产生作用域
var p1 = {
  name: "cherry",
};
function bar() {
  var message = "bar";
  function baz() {
    var name = "kobe";
    with (p1) {
      console.log(name); //cherry
      console.log(message); //bar
    }
  }
  baz();
}
bar();
eval 函数

eval 函数可以解析字符串成代码,并执行代码

var str = 'var message = "hello cherry";console.log(message);';
eval(str); //hello cherry

严格模式下,单独调用函数时(默认绑定),this 是指向 undefined

面向对象

创建对象方式
//创建方式一
var obj = new Object(); //创建一个新的空对象
obj.name = "cherry";
obj.age = 18;
//创建方式二(对象字面量)
var obj2 = {
  name: "cherry",
  age: 18,
};
对象的属性的操作
// 对对象属性的操作
var p = {
  name: "cherry",
  age: 18,
};

//获取属性
console.log(p.name); //cherry
//给属性重新赋值
p.age = 20;
console.log(p.age); //20
//删除属性
delete p.age;
console.log(p); //{name:"cherry"}
//对属性操作的限制语句(对字面量直接定义的属性无效)
//1.不允许对某一个属性赋值
//2.不允许对某一个属性删除
//3.不允许某些属性被遍历出来
对象属性描述符

当我们想对一个属性进行比较精准的操作控制,那么我们可以使用对象描述符

通过属性描述符可以精准的添加或修改对象的属性,并能对属性的特性(不可删除,不可赋值,不可被遍历)的修改和设置

属性描述符需要使用Object.defineProperty方法对属性的控制

Object.defineProperty(obj, prop, descriptor);
// obj:操作的对象
//prop要定义或修改的属性名称或者Symbol
//descriptor要定义或修改的属性的特性描述

属性描述符分两种

数据属性描述符,存取描述符

typeconfigurable(可配置属性)enumerable(可枚举)valuewritable(可被修改值)getset
数据属性描述符可用可用可用可用不可用不可用
存取属性描述符可用可用不可用不可用可用可用
默认值falsefalseundefinedfalse
数据属性描述符测试

定义 p 对象,包含 name age 属性

var p = {
  name: "cherry",
  age: 18,
};
configurable

当 configurable 为 true 的时候,可删除

//1.configurable test
Object.defineProperty(p, "address", {
  value: "重庆市",
  configurable: false,
});

console.log(p); //{ name: 'cherry', age: 18 }
console.log(p.address); //重庆市
delete p.address;
console.log(p.address); //重庆市 delete没有删掉address属性 当configurable为true的时候,可删除
enumerable

当 enumerable 为 true 时当前定义的属性是 ,可枚举的

// 2.enumerable test
Object.defineProperty(p, "address", {
  value: "重庆市",
  enumerable: false,
});

for (const key in p) {
  console.log(key); // name age
}

console.log(Object.keys(p)); // [name , age] 无法将address枚举出来 当enumerable为true时当前定义的属性是可枚举
writable

当 writable 为 true 时当前定义的属性是 , 可被重新赋值

// 2.writable test
Object.defineProperty(p, "address", {
  value: "重庆市",
  enumerable: false,
});

p.address = "北京市";
console.log(p.address); //重庆市 当writable为true时当前定义的属性是可被重新赋值
存取属性描述符测试

configurable,enumerable 同上

当配置中含有 get set 不含有 value 以及 writable 属性时那么他就是存取描述符

用处:1.我们不希望某些属性被暴露出来 , 2.我们希望能截获某一个属性的修改和设置值的过程(hooks 函数)

//存取描述符
// 当配置中含有get set 不含有 value 以及 writable属性时那么他就是存取描述符
// 用处:1.我们不希望某些属性被暴露出来 , 2.我们希望能截获某一个属性的修改和设置值的过程
var p = {
  name: "cherry",
  age: 18,
  _address: "重庆市",
};

Object.defineProperty(p, "address", {
  configurable: true,
  enumerable: true,
  get() {
    return this._address;
  },
  set(value) {
    this._address = value;
  },
});
console.log(p); // { name: 'cherry', age: 18, _address: '重庆市', address: [Getter/Setter] }
console.log(p.address); //重庆市 调用get方法
p.address = "北京市"; //调用set方法
console.log(p.address); //北京市

//2希望能截获某一个属性的修改和设置值的过程
Object.defineProperty(p, "address", {
  configurable: true,
  enumerable: true,
  get() {
    foo(); //获取值前的回调函数
    return this._address;
  },
  set(value) {
    bar(); //设置前的回调函数
    this._address = value;
  },
});

function foo() {
  console.log("值正在获取");
}
function bar() {
  console.log("值正在设置");
}
p.address = "北京市"; // 值正在设置
console.log(p.address); //值正在获取 北京市

存取属性描述其他写法

//其他写法
var p = {
  name: "cherry",
  age: 18,
  _address: "重庆市",
  set address(value) {
    this._address = value;
  },
  get address() {
    return this._address;
  },
};
console.log(p.address); //重庆市
对象属性描述符复数写法
var p = {
  name: "cherry",
  age: 18,
  _address: "重庆市",
};
Object.defineProperties(p, {
  address: {
    configurable: true,
    enumerable: true,
    get() {
      return this._address;
    },
    set(value) {
      this._address = value;
    },
  },
  sex: {
    configurable: true,
    enumerable: true,
    value: "男",
    writable: true,
  },
});
console.log(p);
//{name: 'cherry',age: 18,_address: '重庆市',address: [Getter/Setter],sex: '男'}
对象中的其他方法

1.获取,对象中某一个属性的对象属性描述符的信息

Object.getOwnPropertyDescriptor(p, “sex”)

var p = {
  name: "cherry",
  age: 18,
  _address: "重庆市",
};
//复数定义属性
Object.defineProperties(p, {
  address: {
    configurable: true,
    enumerable: true,
    get() {
      return this._address;
    },
    set(value) {
      this._address = value;
    },
  },
  sex: {
    configurable: true,
    enumerable: true,
    value: "男",
    writable: true,
  },
});

console.log(Object.getOwnPropertyDescriptor(p, "sex"));
//{ value: '男', writable: true, enumerable: true, configurable: true }
console.log(Object.getOwnPropertyDescriptor(p, "address"));
/**{
  get: [Function: get],
  set: [Function: set],
  enumerable: true,
  configurable: true
}*/

2.获取对象的所有描述符信息

Object.getOwnPropertyDescriptors§

console.log(Object.getOwnPropertyDescriptors(p));
/**
 * { value: '男', writable: true, enumerable: true, configurable: true }
{
  get: [Function: get],
  set: [Function: set],
  enumerable: true,
  configurable: true
}
  address: {
    get: [Function: get],
    set: [Function: set],
    enumerable: true,
    configurable: true
  },
  sex: { value: '男', writable: true, enumerable: true, configurable: true }
}
 */

3.阻止对象再扩展(添加新属性)

Object.preventExtensions§;

//阻止对象再扩展(添加新属性)

Object.preventExtensions(p);

p.girlfriend = "红伞女孩";

console.log(p.girlfriend); //undefined

4.使对象目前的所有属性都不可配置

Object.seal§;

//使对象目前的所有属性都不可配置
Object.seal(p);
delete p.name;
console.log(p.name); //cherry

5.使对象目前的所有属性值不可修改,变成只读属性

Object.freeze§;

// 使属性值不可修改,变成只读属性
Object.freeze(p);
p.name = "kobe";
console.log(p.name); //cherry
构造函数
//构造函数
function Foo() {}
var foo = new Foo();
console.log(foo); // foo {}
new 关键字执行过程

1.在内存中创建一个新的对象(空对象)

2.构造函数的内部 this,会指向创建出来的新对象

3.这个对象内部的[[prototype]]属性会被赋值为该构造函数的 prototype 属性

this.__proto__  =  Foo.prototype;

4.执行函数的内部代码(函数体代码)

5.如果构造函数没有返回非空,则返回创建出来的新对象

原型

JavaScript 当中的每一个对象都有一个特殊的内置属性[[prototype]],这个特殊的属性可以指向另一个对象

我们该如何访问原型对象呢?

当每一个对象被创建时,都会绑定一个**隐式原型(–proto–)**属性,这个属性是可以直接访问原型对象,(注:”–“是下划线”__“)

var p = {};
console.log(p.__proto__); //[Object: null prototype] {}

原型有什么用?

我们从对象中访问属性时,会触发[[get]]操作,1 如果在当前对象中,找到属性就直接访问,如果没有找到时,它将会沿着原型(链)查找[[prototype]]

var p = {
  name: "cherry",
};
console.log(p.age); //undefined
p.__proto__.age = 18;
console.log(p); // {name:"cherry"}
console.log(p.age); // 18

访问对象的原型时还有显示原型属性(prototype)可以访问,但是这个属性是函数对象特有的

所以函数,既有隐式原型(–proto–)属性也有显示原型属性(prototype)

var p = {
  name: "cherry",
};

console.log(p.prototype); //undefined
function foo() {}
console.log(foo.prototype); //{}
console.log(foo.__proto__); //{}
//这两个原型属性指向的对象是同一个对象吗? false
console.log(foo.__proto__ === foo.prototype); //false

如果将函数作为构造函数时,创建出来的新对象与函数的原型关系又是什么呢?

当我们使用 new 关键字时,函数内部会执行

this.__proto__ = Foo.prototype;

所以 new 出来的新对象的隐式原型属性是指向构造函数的显示原型对象

function Foo() {}
var f1 = new Foo();
var f2 = new Foo();
console.log(f1.__proto__ === Foo.prototype); //true
console.log(f2.__proto__ === Foo.prototype); //true

上述代码不难看出,f1 的原型对象是全等于 f2 的原型对象

console.log(f1.__proto__ === f2.__proto__); //true

所以,在构造函数 Foo 的显示原型上定义的属性和方法,那么 new 出来的每一个新对象都会拥有

function Foo(name) {
  this.name = name;
}
Foo.prototype.eating = function () {
  console.log(this.name + " eating");
};
var f1 = new Foo("cherry");
var f2 = new Foo("kobe");
Foo.prototype.age = "18";
console.log(f1.age); //18
console.log(f2.age); //18
f1.eating(); // cherry eating
f2.eating(); //kobe eating

Foo 的显示原型对象是有一个不可枚举的属性 constructor,这个属性是指向构造函数 Foo 的

console.log(Foo.prototype); // {}
console.log(Object.getOwnPropertyDescriptors(Foo.prototype));
/**
 * {
  constructor: {
    value: [Function: Foo],
    writable: true,
    enumerable: false,
    configurable: true
  }
} */

console.log(Foo.prototype.constructor === Foo); //true
JavaScript 中的类与对象
原型链

一个对象是有原型对象的,那么原型对象依然也有它的原型对象,我们将这些原型对象通过(–proto–)链接起来时,这时就形成了原型链

var p = {
  name: "cherry",
};

p.__proto__ = {};
p.__proto__.__proto__ = {};
p.__proto__.__proto__.__proto__ = {
  age: 18,
};
//访问属性时,触发[[get]]操作
// 如果当前对象没有找到该属性,那么它将会沿着它的原型(__proto__)链查找
console.log(p.age); // 18

原型链的顶层

var p1 = {};
var p2 = new Object();
/**
 * new 过程
 * 1.在内存中创建一个新的对象(空对象)

 * 2.构造函数的内部 this,会指向创建出来的新对象

 * 3.这个对象内部的[[prototype]]属性会被赋值为该构造函数的 prototype 属性
 * this.__proto__  =  Foo.prototype;

 * 4.执行函数的内部代码(函数体代码)

 *  5.如果构造函数没有返回非空,则返回创建出来的新对象
 */
// p1对象字面量创建对象是new Object()的语法糖,则根据new过程
// p1.__proto__ === Object.prototype;
// p2.__proto__ === Object.prototype;
//验证
console.log(p1.__proto__ === Object.prototype); //true
console.log(p1.__proto__ === p2.__proto__); //true
//Object.prototype 是一个对象 ,根据原型链的表现
console.log(Object.prototype.__proto__); //null
//所以Object.prototype就是顶层原型对象
//顶层原型对象内置了很多方法
console.log(Object.prototype); //[Object: null prototype] {}
console.log(Object.getOwnPropertyDescriptors(Object.prototype));
// toString valueOf 等等

构造函数的原型链

如果在构造函数的显示原型对象上定义属性和方法,那么,创建出来的新对象同样拥有,这样就形成了继承

Object 是所有类的父类

function Person() {}
var p1 = new Person();
console.log(Person.prototype.__proto__); //[Object: null prototype] {} (顶层原型)
console.log(Person.prototype.__proto__.__proto__); //null
console.log(p1.__proto__.__proto__); //[Object: null prototype] {} (顶层原型)
console.log(p1.__proto__.__proto__.__proto__); //null
JavaScript 的继承实现
原型链实现继承
// 继承
//父类
function Person(name) {
  this.name = "cherry";
}
Person.prototype.eating = function () {
  console.log(this.name + " eating");
};
// 子类

function Student() {
  this.sno = 100;
}
Student.prototype = new Person(); //实现继承

Student.prototype.studying = function () {
  console.log(this.name + " studying");
};

var student = new Student("cherry");
student.eating(); // cherry eating

//弊端
//1 . 类型是Person,看不到eating方法
console.log(student); // Person { name: 'cherry', sno: 100 }
//在父类中定义的属性age,
//2 . 如果new出了两个student对象,他们的age属性应该是相互独立的
var stu1 = new Student("cherry");
var stu2 = new Student("kobe");
stu1.friends.push("张三");
console.log(stu2.friends); // ["张三"]
//3 . 给父类传参数麻烦

原型链继承的弊端的解决方案

借用父类构造函数

利用父类构造函数,将构造函数 this 绑定到 new 出来的新对象上,执行该函数,使父类所有定义的属性都添加到新对象上

function Person(name, age, friends) {
  this.name = name;
  this.age = age;
  this.friends = friends;
}
Person.prototype.eating = function () {
  console.log(this.name + " eating");
};
// 子类
function Student(name, age, friends) {
  //解决方案(将Person函数执行时,将属性添加到创建出来的新对象上)并不是严格上的继承
  Person.call(this, name, age, friends);
}
Student.prototype = new Person(); //实现继承

Student.prototype.studying = function () {
  console.log(this.name + " studying");
};

var stu1 = new Student("cherry", 18, ["kd"]);
var stu2 = new Student("kobe", 32, ["curry"]);

console.log(stu1); //Person { name: 'cherry', age: 18, friends: [ 'kd' ] }
console.log(stu2); //Person { name: 'kobe', age: 32, friends: [ 'curry' ] }

stu1.friends.push("钟离"); //给friends增加元素

console.log(stu1); //Person { name: 'cherry', age: 18, friends: [ 'kd', '钟离' ] }
console.log(stu2); //Person { name: 'kobe', age: 32, friends: [ 'curry' ] }

console.log(stu1.__proto__);
// Person {name: undefined,age: undefined,friends: undefined,studying: [Function (anonymous)]}
console.log(stu1.__proto__.__proto__); //{ eating: [Function (anonymous)] }

//弊端
/**
 * 虽然解决了原型链继承的弊端
 * 但是也产生了新的弊端
 * 1.Person构造函数执行了两次
 * 2.新对象的原型对象上多出来些不必要的值undefined的属性
 */
寄生式组合继承

原理 : 创建一个新的对象 , 将这个新的对象的原型指向父类 , 然后 ,子类的原型指向这个新对象

//寄生式组合继承
function Person(name, age, friends) {
  this.name = name;
  this.age = age;
  this.friends = friends;
}

Person.prototype.eating = function () {
  console.log(this.name + " eating");
};

function Student(name, age, friends, address) {
  Person.call(this, name, age, friends);
  this.address = address;
}

//寄生式组合继承 核心代码
Student.prototype.studying = function () {
  console.log(this.name + " studying");
};

//继承函数
function createObject(o) {
  function Fn() {}
  Fn.prototype = o;
  return new Fn();
}
function inheritPrototype(_child, _super) {
  _child.prototype = createObject(_super.prototype);
  Object.defineProperty(_child.prototype, "constructor", {
    enumerable: false,
    configurable: true,
    writable: true,
    value: _child,
  });
}
inheritPrototype(Student, Person);
const stu = new Student("cherry", 18, ["kobe"], "重庆");
console.log(stu); //Student { name: 'cherry', age: 18, friends: [ 'kobe' ], address: '重庆' }
stu.eating(); //cherry eating
原型对象的补充
hasOwnProperty

hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。

const P = {
  name: "cherry",
  age: 18,
};
P.__proto__ = {
  sex: "男",
};

const user = Object.create(P, {
  address: {
    enumerable: true,
    configurable: true,
    writable: true,
    value: "重庆",
  },
});

console.log(user); //{ address: '重庆' }
console.log(user.__proto__); //{ name: 'cherry', age: 18 }
//查看对象自身属性
console.log(user.hasOwnProperty("address")); //true
//查看原型对象上的属性
console.log(user.hasOwnProperty("name")); // false
in 操作符

如果指定的属性在指定的对象或其原型链中,则**in 运算符**返回true

//in 操作符
console.log("address" in user); //true
console.log("name" in user); //true
console.log("sex" in user); //true

for (const key in user) {
  console.log(key); // name , age ,address ,sex
}
instanceof 运算符

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
const auto = new Car("Honda", "Accord", 1998);

console.log(auto instanceof Car);
// expected output: true

console.log(auto instanceof Object);
// expected output: true

//instanceof 实现
function instance_of(L, R) {
  //L 判断对象,R 原型对象
  const O = R.prototype; // 取 R 的显示原型
  let = L.__proto__; // 取 L 的隐式原型
  while (true) {
    if (L === null) return false;
    if (O === L)
      // 当 O 显式原型 严格等于  L隐式原型 时,返回true
      return true;
    L = L.__proto__;
  }
}
isPrototypeOf

isPrototypeOf()方法用于测试一个对象是否存在于另一个对象的原型链上。

//父类
function Foo() {}
//子类
function Bar() {}
function Baz() {}

Bar.prototype = Object.create(Foo.prototype);
Baz.prototype = Object.create(Bar.prototype);

var baz = new Baz();

console.log(Baz.prototype.isPrototypeOf(baz)); // true
console.log(Bar.prototype.isPrototypeOf(baz)); // true
console.log(Foo.prototype.isPrototypeOf(baz)); // true
console.log(Object.prototype.isPrototypeOf(baz)); // true
理解 Javascript Object Layout

对象里面是有一个proto对象: 隐式原型对象

Foo 是一个函数, 那么它会有一个显示原型对象: Foo.prototype

Foo.prototype 来自哪里?

答案: 创建了一个函数, Foo.prototype = { constructor: Foo }

Foo 是一个对象, 那么它会有一个隐式原型对象: Foo.proto

Foo.proto来自哪里?

答案: new Function() Foo.proto = Function.prototype

Function.prototype = { constructor: Function }

var Foo = new Function()

function Foo() {

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zd5Lx9y0-1639031817774)(C:\Users\coderCherry\AppData\Roaming\Typora\typora-user-images\image-20211026214558860.png)]

ES6 中的类(class)
声明一个类
//类的声明
class Person {
  //构造函数(传参)
  /**
   *1.创建一个新的空对象obj
   *2.将Person.prototype赋给新对象obj.__proto__
   *3.将类中(Person)的this指向obj(new 绑定)
   *4.执行构造函数中的代码
   *5.自动返回新对象
   */
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  eating() {
    console.log(this.name + " eating");
  }
}

//测试
var p = new Person("cherry", 18);
console.log(p.__proto__); //{}
console.log(Person.prototype); //{}
console.log(Person.prototype.constructor); // [class Person]
console.log(p.__proto__ === Person.prototype); //true
类中的普通方法

在类中定义的方法均放在原型对象上(Person.prototype)

//在类中定义的方法都放在了原型对象上
console.log(Object.getOwnPropertyDescriptors(Person.prototype)); // 存在eating属性
console.log(Object.getOwnPropertyDescriptors(p.__proto__)); // 存在eating属性
类中的访问器

get 与 set,可以拦截访问过程

class Person {
  constructor(address) {
    this._address = address;
  }
  //访问器
  get address() {
    console.log("get操作");
    return this._address;
  }
  set address(newValue) {
    console.log("set操作");
    this._address = newValue;
  }
}

//访问器
var p = new Person("重庆");
console.log(p.address); //重庆 (打印 - get操作)
p.address = "北京"; // set操作
console.log(p.address); //北京 (打印 - get操作)
类中的静态方法

静态方法是放在类中的,并没有放在 Person 显示原型上(Person.prototype)中

class Person {
  constructor() {}
  static getClassName() {
    console.log(this.name);
  }
}

//类中的静态方法
Person.getClassName(); //Person
类的继承

super 关键字的第一种方法

调用构造函数

//类的继承
//父类
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  running() {
    console.log(" person running");
  }
}
//子类
class Student extends Person {
  constructor(name, age, address) {
    //调用父类的构造函数
    super(name, age);
    this.address = address;
  }

  running() {
    console.log(super.constructor === Person); //true
    console.log(" students running");
  }
}
//测试
const stu = new Student("cherry", 18, "重庆");
console.log(Object.getOwnPropertyDescriptors(stu.__proto__.__proto__)); //存在running
console.log(Object.getOwnPropertyDescriptors(stu.__proto__)); // 存在running
stu.running(); // students running
对父类中的方法重写

super 关键字的第二种用法

super 关键字映射父类

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  running() {
    console.log(" person running");
  }

  perMethod() {
    console.log(1);
    console.log(2);
    console.log(3);
  }

  static perStaticMethod() {
    console.log("perStaticMethod");
  }
}

class Student extends Person {
  constructor(name, age, address) {
    //调用父类的构造函数
    super(name, age);
    this.address = address;
  }

  running() {
    console.log(super.constructor === Person); //true
    console.log(Person.prototype.constructor === super.constructor); //true
    console.log(super.__proto__); //Person {}
    console.log(" students running");
  }

  childMethod() {
    super.perMethod();
    console.log(4);
    console.log(5);
  }

  static childStaticMethod() {
    super.perStaticMethod();
    console.log("childStaticMethod");
  }
}
//测试
stu.childMethod(); // 12345
Student.childStaticMethod(); //perStaticMethod childStaticMethod
JavaScript 中的多态

定义:多态指为不同数据类型的的实体提供统一的接口,或使用一跟单一符号来表示多个不同的数据类型.

笔者:不同的数据类型进行同一个操作,表现出不同的行为,就是多态的表现

function calcArea(o) {
  o.area();
}

var p = {
  area() {
    console.log("p area");
  },
};

class Shape {
  area() {
    console.log("shape area");
  }
}
const s = new Shape();
calcArea(p);
calcArea(s);

ES6

字面量的增强写法
var p = {
  name: "Cherry",
  foo() {},
  bar: function () {},
  baz: () => {},
  ["super" + 123]: function () {
    console.log(123);
  },
};
p.super123(); // 123
数据的解构
数组的解构
// 数组的解构
var names = ["cherry", "kobe", "james", "KD"];
//数组的解构时,映射的元素有顺序要求,可写别名
var [item1, item2, item3, item4] = names;
console.log(item1); //cherry
console.log(item4); //KD

//技巧二
var [, name2, , name4] = names;
console.log(name2, name4); //kobe kD

//技巧三
var [itemA, ...namesArgs] = names;
console.log(itemA, namesArgs); //cherry [ 'kobe', 'james', 'KD' ]

//解构元素时 ,可设置默认值(当解构的元素为undefined时使用)
var [item1, item2, item3, item4, item5 = "AI"] = names;
console.log(item5); //AI
对象的解构
// 对象的解构
var p = {
  name: "cherry",
  age: 18,
  foo() {
    console.log(this.name);
  },
};
//注意  对象解构时 ,解构的属性名(没有顺序要求),属性名可与对象中的属性名相同,亦可不同
// 2. 解构的属性名的别名写法(本质:将属性值赋值给别名)
const { name, age, foo: getName } = p;
console.log(name); //cherry
console.log(age); //18
console.log(getName); // foo函数
p.foo(); //cherry
getName(); //undefined (原因:this关键字解释,这里就不展开了)

//解构的属性 ,可设置默认值(当解构的元素为undefined时使用)
const { name, age, foo, address = "重庆" } = p;
console.log(address); // 重庆

应用场景:

var p = {
  name: "cherry",
  age: 18,
};

function getUserInfo({ name, age }) {
  console.log(name, age);
}

getUserInfo(p); //cherry 18
let/const

let 不可以重复定义同一个变量

var foo = "foo";
var foo = "newFoo";
console.log(foo); //newFoo

//let不可以重复定义同一个变量
let bar = "newBar";
//Identifier 'bar' has already been declared
let bar = "fn"; //报错:bar已经被声明

1.const 定义的常量 不可以被修改(基本数据类型)

2.const 定义的常量如果是引用类型数据,可以修改引用类型里面的东西
原因:此时,const 保存的是引用类型数据的内存地址,故可修改引用类型里面的东西

var foo = "foo";
let bar = "bar";
//const 定义的常量 不可以被修改(基本数据类型)
const name = "cherry";
// name = "KD"; //报错

//const 定义的常量如果是引用类型数据,可以修改引用类型里面的东西
// 原因:此时,const保存的是引用类型数据的内衬地址
const userInfo = {
  name: "cherry",
  age: 18,
};
userInfo.name = "KD";

console.log(userInfo.name); //KD
let/const ,作用域提升问题

let/const,是否有作用域提升的问题吗?

社区:一直在争论

我先看下面示例

var name = "cherry";
function getName() {
  console.log(name);
  // let name = "kobe";
}

getName(); //报错 , Cannot access 'name' before initialization 初始化前无法访问name

//如果注释掉 let name = "kobe";
getName(); //cherry

这个示例,理论来说应该会直接访问 cherry,但是报错

由此,在 ECMA262 中,对此做了定义

这些变量会被创建在包含它们的词法环境被实例化时,但是,是不可以访问它们,直到词法被绑定求值.

说明,初始化的时候,这些变量已经被创建,但是不可以被访问

ES6 后对,执行上下文的描述

每一个执行上下文都会关联到一个变量环境(VE)中,在执行代码中变量和函数声明会作为环境记录(ER)添加到变量环境中

对于函数来说,参数也会被作为环境记录添加到变量环境中

变量环境:Variable Environment 是指向一个 variable _对象,这个对象的类型是 VariableMap,是哈希表来实现的。所以这个哈希表,里面存的元素,就是 var/let/const 以及函数声明的变量

块级作用域

在 es6 之前,作用域,主要有,全局作用域,函数作用域,还有 with 语句

在 es6 之后,出现了块级作用域

//块级作用域
{
  var myName = "cherry";
  let age = 18;
  function foo() {}
  class Person {}
}

console.log(myName); //cherry  对var变量不起效果
console.log(foo); //[Function: foo] (引擎的特殊处理,所以可被访问)
const p = new Person(); //Person is not defined
console.log(age); //age is not defined
for (var i = 0; i < 10; i++) {}
console.log(i); //10

for (let j = 0; j < 10; j++) {}
console.log(j); //j is not defined

let/const 的块级作用域

var 没有块级作用域

for (var i = 0; i < 10; i++) {}

for (let i = 0; i < 10; i++) {}

{
  let i = 0;
}

{
  let i = 5;
}

{
  var i = 15;
}

console.log(i);

//转成es5

("use strict");

for (var i = 0; i < 10; i++) {}

for (var _i = 0; _i < 10; _i++) {}

{
  var _i2 = 0;
}
{
  var _i3 = 5;
}
{
  var i = 15;
}
console.log(i); //15
暂时性死区

let/const 会造成暂时性死区,我们将下例的现象,称为,暂时性死区

var name = "cherry";
function getName() {
  console.log(name);
  let name = "kobe";
}

getName(); //报错 , Cannot access 'name' before initialization 初始化前无法访问name
字符串模板

语法:${变量/表达式}字符串

${变量/表达式/立即执行函数},{}里面可以写这些

const name = "cherry";
const age = 18;

const userInfo = name + "今年" + age + "岁了";
//字符串模板 ` ${变量/表达式/立即执行函数}字符串`
const userInfo1 = `${name}今年${age}岁了`;
//{}中可以写表达式
const userInfo2 = `${name}今年${age * 2}岁了`;
//{}中可以写函数,必须要执行
const userInfo3 = `${name}今年${(function () {
  return 18;
})()}岁了`;
console.log(userInfo); //cherry今年18岁了
console.log(userInfo1); //cherry今年18岁了
console.log(userInfo2); //cherry今年36岁了
console.log(userInfo3); //cherry今年18岁了
函数的默认参数

获取函数传入的正规参数的个数

function bar(num1, num2, num3) {}
console.log(bar.length); // 3

传入普通默认值

//函数的默认参数
//es5
function foo(m, n) {
  m = m || "cherry";
  n = n || 18;
  console.log(m, n);
}
foo(); //cherry 18
foo(0, ""); //cherry 18 -> bug(预期结果为 0)

//es6
function foo(m = "cherry", n = 18) {
  console.log(m, n);
}
foo(); //cherry 18
foo(0, ""); //cherry 18
默认参数的高级用法

函数的默认参数,可以是一个表达式(实际开发中,不建议)

function foo(m, n = m * 2) {
  console.log(n);
}

foo(10); // 20

传入对象的默认值

const p = {
  name: "cherry",
  age: 18,
};
//默认值写法
//({ name, age } = { name: "cherry", age: 18 })  写法一
//({ name = "cherry", age = 18 } = {}) 写法二
function bar({ name = "cherry", age = 18 } = {}) {
  console.log(name, age);
}
bar(p);
bar();
函数的剩余参数

arguments 对象不是一个真正的数组,是一个伪数组,需要转化后才有数组原型上的方法

剩余参数语法是,以…为前缀的形参,这个形参将会变成一个包含剩余实参的数组,可以使用数组原型上的方法

语法:…args , args 是一个数组

剩余参数只包含哪些,没有形参的实参,arguments 对象包含了传递函数所有的实参

function foo(num1, ...args) {
  console.log(num1, args);
}
foo(1, 2, 3); //1 [ 2, 3 ]
console.log(foo.length); // 1
箭头函数

箭头函数没有 this 的绑定,箭头函数的 this,是往上层作用域查找

为什么箭头函数不能作为构造函数?

1)箭头函数没有明确的 this 绑定

2)箭头函数没有显示原型

function foo() {}
console.log(foo.prototype); // {}

const bar = () => {};
console.log(bar.prototype); //undefined 箭头函数不可以作为构造函数使用
console.log(bar.arguments); // 箭头函数没有arguments对象
展开语法

展开语法(Spread syntax), 可以在函数调用/数组构造时, 将数组表达式或者 string 在语法层面展开;还可以在构造字面量对象时, 将对象表达式按 key-value 的方式展开。(笔者注: 字面量一般指 [1, 2, 3] 或者 {name: "mdn"} 这种简洁的构造方式)

1.函数调用

function foo(x, y, z) {
  console.log(x, y, z);
}
const names = ["cherry", "kobe", "KD"];

foo(...names); //cherry kobe KD

2.数组构造

const names = ["cherry", "kobe", "KD"];
const newNames = [...names, "james"];

console.log(newNames); //[ 'cherry', 'kobe', 'KD', 'james' ]

3.构造字面量对象

const names = ["cherry", "kobe", "KD"];
const userInfo = {
  name: "cherry",
  age: 18,
};

const infoSuper = {
  ...userInfo,
  ...names,
  address: "重庆",
};

console.log(infoSuper);
/**
 * {
  '0': 'cherry',
  '1': 'kobe',
  '2': 'KD',
  name: 'cherry',
  age: 18,
  address: '重庆'
  }
*/

展开运算符,是一个浅拷贝的过程

//展开运算符 是一个浅拷贝的过程
const info = {
  name: "cherry",
  friend: {
    name: "KD",
    age: 32,
  },
};
const newInfo = { ...info, name: "harden" };
newInfo.friend.name = "james"; //改变了newInfo对象中的friend对象
console.log(newInfo); //{ name: 'harden', friend: { name: 'james', age: 32 } }
console.log(info); //{ name: 'cherry', friend: { name: 'james', age: 32 } }
//改变了newInfo对象中的friend对象 , 结果来看,也影响了info对象的friend
JavaScript 中的进制
const num1 = 100; //十进制
const num2 = 0b100; //二进制
const num3 = 0o100; //八进制
const num4 = 0x100; //16进制

console.log(num1, num2, num3, num4); //100 4 64 256
Symbol

symbol 函数会产生唯一的值

let name = "cherry";
const s1 = Symbol(name);
const s2 = Symbol(name);
//symbol描述符
console.log(s1.description); //cherry
console.log(s1 === s2); //false

对象同名属性的解决办法(symbol)

const s1 = Symbol();
const s2 = Symbol();
const s3 = Symbol();
const s4 = Symbol();
//定义属性
const info = {
  [s1]: "cherry",
  [s2]: 18,
};

info[s3] = {
  name: "KD",
};

Object.defineProperty(info, s4, {
  value: "重庆",
  configurable: true,
  enumerable: true,
  writable: true,
});

//获取属性值(因为以symbol定义的属性,是一个表达式,所以获取值时,必须使用[])

console.log(info[s1]); //cherry

当以 Symbol 作为 key 时,这些属性将无法被枚举

//当以Symbol作为key时,这些属性将无法被枚举
const keys = Object.keys(info);
console.log(keys); //[]
//获取Symbol key 需要getOwnPropertySymbols方法
const skeys = Object.getOwnPropertySymbols方法(info);
console.log(skeys); //[ Symbol(), Symbol(), Symbol(), Symbol() ]

Symbol 在某些场景下是可以相等

Symbol.for(描述符) /Symbol.keyFor(sa)获取描述符

const key = "cherry";
const sa = Symbol.for(key);
const sb = Symbol.for(key);
const skey = Symbol.keyFor(sa);
console.log(skey === key); //true(cherry)
console.log(sa === sb); //true
Set

Set是es6新增的一个数据结构,里面的元素是不可以重复的

const set = new Set();
set.add(10);
set.add(20);
set.add(20);
const obj = { name: "cherry" };
set.add(obj);
set.add(obj);
set.add({});
set.add({});
console.log(set); //Set(5) { 10, 20, { name: 'cherry' }, {}, {} }

给数组去重

const arr = [10, 20, 30, 15, 10, 35, 20];
// const newArr = Array.from(new Set(arr));
const newArr = [...new Set(arr)];
console.log(newArr); //[ 10, 20, 30, 15, 35 ]
Set的属性

Set.size

const set = new Set([1, 5]);
console.log(set.size); //2
Set的方法
const set = new Set([1, 5]);
//add方法
arrSet.add(10); //添加元素
console.log(arrSet); //arrSet(3) { 1, 5, 10 }
//delete方法
arrSet.delete(5); //删除某一个元素
console.log(arrSet); //arrSet(2) { 1, 10 }
//has方法 判断元素是否存在
console.log(arrSet.has(10)); //true
//clear
arrSet.clear(); //清除所有元素
console.log(arrSet); //arrSet(0) {}

//枚举
arrSet.forEach((item) => {
  console.log(item); // 1 5
});
for (const item of arrSet) {
  console.log(item); // 1 5
}
WeakSet

与Set的区别

1)WeakSet中只能存放对象数据类型,不能存放基本数据类型

2)WeakSet对元素的引用是弱引用,如果没有其他引用指向该元素,那么GC将对该元素进行回收

3)WeakSet的元素不可被枚举

const wSet = new WeakSet();
wSet.add(10); //Invalid value used in weak set
const wSet = new WeakSet();
const set = new Set();
let obj = {
  name: "cherry",
};
//建立强引用
set.add(obj);
//建立弱引用
wSet.add(obj);
console.log(wSet); //WeakSet { <items unknown> }
obj = null;
console.log(set); // Set(1) { { name: 'cherry' } }
console.log(wSet); // 将来的某一时刻,wSet中的obj会被回收

WeakSet使用场景

// WeakSet 使用场景
const PersonSet = new WeakSet();
class Person {
  constructor() {
    PersonSet.add(this);
  }

  eating() {
    if (!PersonSet.has(this)) {
      throw new Error("不是构造函数所创建出的对象不可调用");
    }
    console.log("eating");
  }
}

let p = new Person();

p.eating(); //eating

const newP = {
  name: "cherry",
};

p.eating.call(newP); //Error: 不是构造函数所创建出的对象不可调用
Map

Map,用于存储映射关系

const obj1 = {
  name: "cherry",
};
const obj2 = {
  name: "KD",
};
const map = new Map();
map.set(obj1, "cherry");
map.set(obj2, "KD");
console.log(map); //Map(2) { { name: 'cherry' } => 'cherry', { name: 'KD' } => 'KD' }
const map2 = new Map([
  [obj1, "cherry"],
  [obj2, "KD"],
  [1, 100],
]);
console.log(map2);
// Map(3) {
//   { name: 'cherry' } => 'cherry',
//   { name: 'KD' } => 'KD',
//   1 => 100
// }
Map对象的方法
const map = new Map([
  [obj1, "cherry"],
  [obj2, "KD"],
  [1, 100],
]);
console.log(map.size); //3
//获取某一个元素
console.log(map.get(obj1)); //cherry
//删除某一个元素
console.log(map.delete(obj2)); //true
console.log(map); //Map(2) { { name: 'cherry' } => 'cherry', 1 => 100 }
//清除所有映射
map2.clear();
console.log(map); //Map(0) {}

map对象的枚举

//map对象的枚举
//forEach
map.forEach((item, key, map) => {
  console.log(item, key);
});
// cherry { name: 'cherry' }
// KD { name: 'KD' }
// 100 1

//forof
for (const item of map) {
  console.log(item);
}
// [ { name: 'cherry' }, 'cherry' ]
// [ { name: 'KD' }, 'KD' ]
// [ 1, 100 ]

for (const [key, value] of map) {
  console.log(key, value);
}
WeakMap

与Map的区别

1)WeakSet中只能存放对象数据类型,不能存放基本数据类型

2)WeakSet对元素的引用是弱引用,如果没有其他引用指向该元素,那么GC将对该元素进行回收

3)WeakSet的元素不可被枚举

const wMap = new WeakMap();
const obj = {
  name: "cherry",
};
wMap.set(obj, "cherry");

console.log(wMap); //WeakMap { <items unknown> }
wMap.set(1, 100); //Invalid value used as weak map key

WeakMap对象的属性和方法

//添加一个映射
wMap.set(obj, "cherry");
//获取映射的值
console.log(wMap.get(obj)); //cherry
//检查映射是否存在
console.log(wMap.has(obj)); //true
//删除某一个映射
console.log(wMap.delete(obj)); //true
console.log(wMap); //WeakMap { <items unknown> }

WeakMap的应用场景

let userInfo = {
  _name: "cherry",
  age: 18,
};

const infoNameChange1 = (name) => {
  console.log("infoNameChange1", name);
};
const infoNameChange2 = (name) => {
  console.log("infoNameChange2", name);
};

const infoMap = new Map();
infoMap.set("_name", [infoNameChange1, infoNameChange2]);
const infoWmap = new WeakMap();
infoWmap.set(userInfo, infoMap);

Object.defineProperty(userInfo, "name", {
  configurable: true,
  enumerable: true,
  get() {
    return this._name;
  },
  set(newValue) {
    implement(newValue);
    this._name = newValue;
  },
});
userInfo.name = "KD";
function implement(value) {
  const dependencyFunctions = infoWmap.get(userInfo).get("_name");
  dependencyFunctions.forEach((fn) => fn(value)); //infoNameChange1 KD infoNameChange2 KD
}

//当 userInfo 被销毁时
// userInfo = null
// 在将来的某一时刻 infoWmap 收集的userInfo 的infoMap 对象将被回收 提高了性能

ES7

Array Includes
// Array.prototype.includes

const arr = [1, 2, 35, 6, 3, 8, NaN];
//需求:判断数组类是否存在某个元素
//es7 前
if (arr.indexOf(3) !== -1) {
  console.log("arr数组中包含 {3}", true);
} else {
  console.log("arr数组中包含 {3}", false);
}

if (arr.indexOf(NaN) !== -1) {
  console.log("arr数组中包含 {NaN}", true);
} else {
  console.log("arr数组中不包含 {NaN}", false);
}

//arr数组中包含 {3} true
// arr数组中不包含 {NaN} false (错误 , 需要特殊处理才行)

if (arr.includes(NaN)) {
  console.log("arr数组中包含 {NaN}", true);
} else {
  console.log("arr数组中不包含 {NaN}", false);
}
//arr数组中包含 {NaN} true
指数运算

Math.pow() 函数返回基数(base)的指数(exponent)次幂,即 baseexponent

const result1 = Math.pow(3, 4);
const result2 = 3 ** 4;
console.log(result1, result2); // 81 81

ES8

Object.values

Object.values()方法返回一个给定对象自身的所有可枚举属性值的数组,值的顺序与使用for...in循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )

const info = {
  name: "cherry",
  age: 18,
  address: "重庆",
};

const keys = Object.keys(info);
console.log(keys); //[ 'name', 'age', 'address' ]
const values = Object.values(info);
console.log(values); //[ 'cherry', 18, '重庆' ]
Object.entries

Object.entries方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)

// Object.entries
const info = {
  name: "cherry",
  age: 18,
  address: "重庆",
};
const entries = Object.entries(info);
console.log(entries); //[ [ 'name', 'cherry' ], [ 'age', 18 ], [ 'address', '重庆' ] ]
const arr = ["cherry", "KD"];
const arrEntries = Object.entries(arr);
console.log(arrEntries); //[ [ '0', 'cherry' ], [ '1', 'KD' ] ]

ES9 (null)

ES10

Array flat
const nums = [10, 20, [20, [60, 90]], [23], [[80]]];
const newFlatNums = nums.flat(3);
console.log(newFlatNums); //[10, 20, 20, 60,90, 23, 80]
Array flatMap
const newFlatmapNums = nums.flatMap((item) => {
  return item;
});
console.log(newFlatmapNums); //[ 10, 20, 20, [ 60, 90 ], 23, [ 80 ] ]
Object.fromEntries
const obj = {
  name: "cherry",
  age: 18,
};

const eries = Object.entries(obj);
console.log(eries); //[ [ 'name', 'cherry' ], [ 'age', 18 ] ]
let newObj = {};
for (const item of eries) {
  newObj[item[0]] = item[1];
}
console.log(newObj); //{ name: 'cherry', age: 18 }

//Object.fromEntries
newObj = Object.fromEntries(eries);
console.log(newObj); //{ name: 'cherry', age: 18 }

Object.fromEntries应用场景

let queryInfo = "name=cherry&age=18";

const queryParams = new URLSearchParams(queryInfo);

for (const item of queryParams) {
  console.log(item);
}
const paramsObj = Object.fromEntries(queryParams);
console.log(paramsObj); //{ name: 'cherry', age: '18' }
trimStart & trimEnd
const message = "    message     ";

const newMessage = message.trim();
console.log(newMessage.length); //7

const newMessage1 = message.trimStart();
const newMessage2 = message.trimEnd();
console.log(message.length); //15
console.log(newMessage1.length); //12
console.log(newMessage2.length); //11

ES11

BigInt

大数

console.log(maxInt); //9007199254740991

let bigInt = 900719925474099100n;

console.log(bigInt + 10n); //隐式转化 900719925474099110n

const num = 100;
console.log(bigInt + BigInt(num)); //900719925474099200n
Null Lish Coalescing operator

空值合并运算

const foo = undefined;
const bar = foo ?? 100;
const baz = foo || 100;
console.log(bar); //100
console.log(baz); //100

const foo1 = 0; // "" 0
const bar1 = foo1 ?? 100;
const baz1 = foo1 || 100; //先转化成Boolean类型在判断

console.log(bar1, baz1); // 0 100 不是预期效果 0 0
Optional Chaining

可选链

const info = {
  name: "cherry",
  age: 18,
  friend: {
    name: "KD",
    friend: {
      name: "harden",
    },
  },
};

console.log(info?.friend?.friend?.name); //harden
info.friend.friend = null;
console.log(info?.friend?.friend?.name); //undefined
console.log(info.friend.friend.name); //Cannot read properties of null (reading 'name')
globalThis

全局对象

console.log(globalThis); // 浏览器:window  node:global

ES12

FinalizationRegistry类

FinalizationRegistry 提供了这样的一种方法:当一个在注册表中注册的对象被回收时,请求在某个时间点上调用一个清理回调。(清理回调有时被称为 finalizer )。

// 监听对象销毁
const finalRegistry = new FinalizationRegistry((hanleValue) => {
  console.log(`${hanleValue}对象被销毁了`);
});
let info = {
  name: "cherry",
};

finalRegistry.register(obj, "info"); //在注册表中注册对象

info = null; // info对象被销毁了
WeakRef
const finalRegistry = new FinalizationRegistry((hanleValue) => {
  console.log(`${hanleValue}对象被销毁了`);
});
let info = {
  name: "cherry",
};

// let newInfo = {
//   info,
//   age: 18,
// };

{
  // const wSet = new WeakSet(); //弱引用 WeakMap 同理
  // wSet.add(info);
  //还是会被回收
}

let newInfo = new WeakRef(info); // 创建一个弱引用
// console.log(newInfo.deref()); //{name: 'cherry'}
// console.log(newInfo); //{ info: { name: 'cherry' }, age: 18 }
finalRegistry.register(info, "info"); //在FinalizationRegistry注册表中注册对象

info = null; // info对象被销毁了

setTimeout(() => {
  console.log(newInfo.deref()?.name); //undefined
}, 10000);
逻辑赋值运算
//逻辑赋值运算
// 1.||=
let message = undefined;
// message = message || "default value";
message ||= "default value";
console.log(message); //default value

// 2.&&= 会被隐式转换
let info = true;
//方式一
info &&= function () {};
//方式二
info = info && function () {};
console.log(info); // false || function

// 3.??=  不会被隐式转换
let str = 0;
str ??= "default value";
// str ||= "default value";
console.log(str); //0 (default value)

Proxy

ES6之前监听对象操作

Object.defineProperty

// Object.defineProperty
const info = {
  name: "cherry",
  age: 18,
  address: "重庆",
};

for (const key in info) {
  let value = info[key];
  Object.defineProperty(info, key, {
    get() {
      console.log(`${key}被访问了`);
      return value;
    },
    set(newValue) {
      console.log(`${key}被重新赋值`);
      value = newValue;
    },
  });
}

info.age = 21;
console.log(info.age); // 21 age被重新赋值,age被访问了

弊端:无法监听,新增,和删除属性的操作

ES6之后监听对象操作

Proxy类(代理)

监听一个对象的相关操作,可以先创建一个代理对象(Proxy对象);

之后对该对象的所有操作,都可以通过代理对象来完成,代理对象可以监听我们想要对原对象的那些操作

const info = {
  name: "cherry",
  age: 18,
};

const infoProxy = new Proxy(info, {}); //创建代理对象

console.log(infoProxy); //{ name: 'cherry', age: 18 }
infoProxy.name = "KD";
infoProxy.age = "33";
console.log(info); //{ name: 'KD', age: '33' }

属性值操作捕获器

get/set捕获器

const infoProxy = new Proxy(info, {
  //获取值的捕获器
  get(target, key) {
    console.log(`${key}被访问`);
    return target[key];
  },
  //设置值的捕获器
  set(target, key, newValue) {
    console.log(`${key}被重新赋值`);
    target[key] = newValue;
  },
});

console.log(infoProxy); //{ name: 'cherry', age: 18 }
console.log(infoProxy.name); //name被访问
infoProxy.name = "KD"; //name被重新赋值
infoProxy.age = "33"; //age被重新赋值
console.log(info); //{ name: 'KD', age: '33' } 

其他的捕获器

const info = {
  name: "cherry",
  age: 18,
};

const infoProxy = new Proxy(info, {
  //获取值的捕获器
  get(target, key) {
    console.log(`${key}被访问`);
    return target[key];
  },
  //设置值的捕获器
  set(target, key, newValue) {
    console.log(`${key}被重新赋值`);
    target[key] = newValue;
  },
  //监听in捕获器
  has(target, key) {
    console.log(`${key}的in操作`);
    return key in target;
  },
  //删除操作捕获器
  deleteProperty(target, key) {
    console.log(`${key}的删除操作`);
    delete target[key];
  },
});

//函数捕获器

function foo() {}

const funcProxy = new Proxy(foo, {
  apply(target, thisArg, argArray) {
    console.log(thisArg, argArray); //{} [ 1, 2 ]
    target.apply(thisArg, argArray);
  },
  construct(target, argArray, newTarget) {
    console.log(newTarget.name); //foo
    return new target(...argArray);
  },
});
funcProxy.apply({}, [1, 2]);
new funcProxy();

Reflect

它是一个对象,意思是反射

提供了很多操作JavaScript对象的方法

JavaScript对象操作方法
// Proxy Reflect
const info = {
  name: "cherry",
  age: 18,
};

const infoProxy = new Proxy(info, {
  //获取值的捕获器
  get(target, key) {
    console.log(`${key}被访问`);
    return Reflect.get(target, key);
  },
  //设置值的捕获器
  set(target, key, newValue) {
    console.log(`${key}被重新赋值为${newValue}`);
    const isSet = Reflect.set(target, key, newValue);
    console.log(isSet ? "设置成功" : "设置失败");
  },
});

infoProxy.name = "KD"; //name被重新赋值为KD 设置成功
console.log(infoProxy.name); //name被访问 KD
console.log(info.name); //kd
receiver参数

receiver 是 代理对象, 当传入Reflect中后,原对象的get/set的this是指向代理对象的

// Proxy Reflect
const info = {
  _name: "cherry",
  age: 18,
  get name() {
    console.log(this); //Proxy {_name: 'cherry', age: 18}
    return this._name;
  },
  set name(newValue) {
    console.log(this); //Proxy {_name: 'cherry', age: 18}
    this._name = newValue;
  },
};

const infoProxy = new Proxy(info, {
  //获取值的捕获器
  get(target, key, receiver) {
    //receiver 是 代理对象 当传入Reflect中后,
    //原对象的get/set的this是指向代理对象的
    return Reflect.get(target, key, receiver);
  },
  //设置值的捕获器
  set(target, key, newValue, receiver) {
    const isSet = Reflect.set(target, key, newValue, receiver);
    console.log(isSet ? "设置成功" : "设置失败");
  },
});

infoProxy.name = "KD"; //设置成功
console.log(infoProxy.name); // KD
console.log(info.name); //kd
Reflect.construct
class Person {
    constructor() {}
  }
  class Student {
    constructor(name, age) {
      this.name = name;
      this.age = age;
    }
  }
  const stu = new Student("cherry", 18);
  console.log(stu); //Student { name: 'cherry', age: 18 }
  console.log(stu.__proto__ === Student.prototype); //true

  //需求:希望 new Student 出的对象的类型设置为Person

  const newPerson = Reflect.construct(Student, ["cherry", 18], Person);
  console.log(newPerson); //Person { name: 'cherry', age: 18 }
  console.log(newPerson.__proto__ === Person.prototype); //true

响应式(vue2/3)原理

let activeEffect = null;
class Depend {
  constructor() {
    this.subscribers = new Set();
  }
    
  //收集 reactive 依赖函数
  depend() {
    activeEffect && this.subscribers.add(activeEffect);
  }

  //通知 所有reactive依赖函数执行
  notify() {
    this.subscribers.forEach((fn) => fn());
  }
}

function watchFn(fn) {
  activeEffect = fn;
  fn();
  activeEffect = null;
}
const targetWeakMap = new WeakMap();
function getDepend(o, key) {
  let oMap = targetWeakMap.get(o);
  if (!oMap) {
    oMap = new Map();
    targetWeakMap.set(o, oMap);
  }
  let oDepend = oMap.get(key);
  if (!oDepend) {
    oDepend = new Depend();
    oMap.set(key, oDepend);
  }
  return oDepend;
}

{
  // vue2
  function reactive(o) {
    for (const key in o) {
      let value = o[key];
      Object.defineProperty(o, key, {
        get() {
          const depend = getDepend(o, key);
          depend.depend();
          return value;
        },
        set(newValue) {
          value = newValue;
          const depend = getDepend(o, key);
          depend.notify();
        },
      });
    }
    return o;
  }
}

//vue3
function reactive(o) {
  return new Proxy(o, {
    get(target, key, receiver) {
      const depend = getDepend(target, key);
      depend.depend();
      return Reflect.get(target, key, receiver);
    },
    set(target, key, newValue, receiver) {
      Reflect.set(target, key, newValue, receiver);
      const depend = getDepend(target, key);
      depend.notify();
    },
  });
}

//test code
//监听对象变化
const info = {
  name: "cherry",
  age: 18,
  count: 5,
};
const newInfo = reactive(info);

watchFn(function () {
  console.log(newInfo.name);
});

watchFn(function () {
  console.log(newInfo.age);
});

watchFn(function () {
  newInfo.count + 10;
});

watchFn(function () {
  console.log(newInfo.count * 50);
});

newInfo.name = "KD";
newInfo.count = 10;
newInfo.age = 33;

Promise

网络请求的异步处理
回调函数方式处理
const info = {
  url: "/cherry",
};

//回调函数方式
function requsetData(info, successCb, errorCb) {
  //定时器模拟网路请求
  setTimeout(() => {
    if (info.url === "/cherry") {
      //成功的请求
      let names = ["cherry", "KD"];
      successCb(names);
    } else {
      let errMessage = `请求失败,${info.url}是错误的url`;
      errorCb(errMessage);
    }
  }, 3000);
}

//拿结果的处理函数successCb , errorCb
requsetData(
  info,
  (res) => {
    console.log(res);
  },
  (err) => {
    console.log(err);
  }
);
Promise 类的基本语法
class Promise {
  constructor(executor) {
    const resolve = function () {
      console.log("resolve");
    };
    const reject = function () {
      console.log("reject");
    };
    executor(resolve, reject);
  }
}
// new Promise(cb) , Promise传入一个executor的回调函数,将会被立即行
//如果传入的回调函数里面的状态的成功的,回调resolve
//如果传入的回调函数里面的状态的失败的,回调reject
function foo() {
  return new Promise((resolve, reject) => {
    console.log("回调函数被执行了");
    let state = true; //executor中的执行结果
    if (state) {
      resolve();
    } else {
      reject();
    }
  });
}

const promise = foo();
console.log(promise); //Promise

promise 是一个契约,无论里面的状态是否成功,都会返回一个 Promise 对象

Promise 的状态
new Promise((reslove, reject) => {
  //pending 待定状态 (执行executor中的代码)
  //fulfilled(reslove) 已兑现状态 调用reslove方法时,reslove()
  //rejectd 已拒绝状态 调用reject方法时,reject()
});

状态一旦被确定,将无法更改

new Promise((reslove, reject) => {
  //执行executor
  reslove();
  reject(); //无效
});
resolved 状态的结果

Promise.then(cb)

1.then 方法传入的回调函数,会在 Promise 中执行 resolve 方法时,被回调

2.then 方法可以传入两个回调函数, 第一个回调函数,会在 Promise 中执行 resolve 方法时,被执行;第一个回调函数,会在 Promise 中执行 reject 方法时,被执行(reject 状态的结果)

3.回调函数中的参数,是 resolve(value)传入的 value

const promise = foo();
//写法1
promise.then((res) => {
  console.log(res);
});
//写法二
promise.then(
  (res) => {
    console.log(res);
  },
  (err) => {
    console.log(err);
  }
);
rejectd 状态的结果

Promise.catch(cb)

1.catch 方法传入的回调函数,会在 Promise 中执行 reject 方法时,被回调

2.回调函数中的参数,是 reject(errorValue)传入的 errorValue

const promise = foo();
promise.catch((err) => {
  console.log(err);
});
Promise 方式处理网络请求
const info = {
  url: "/cherry",
};

function requsetwithPromise() {
  return new Promise((resolve, reject) => {
    //定时器模拟网路请求
    setTimeout(() => {
      if (info.url === "/cherry") {
        //成功的请求
        let names = ["cherry", "KD"];
        resolve(names);
      } else {
        let errMessage = `请求失败,${info.url}是错误的url`;
        reject(errMessage);
      }
    }, 3000);
  });
}

const promise = requsetwithPromise();
promise.then(
  (res) => {
    console.log(res); //[ 'cherry', 'KD' ] (调用resolve时)
  },
  (err) => {
    console.log(err); //请求失败,cherry是错误的url (调用reject时)
  }
);
resolve()的参数问题
  • reslove(),传入的参数
  • 1)普通参数或者对象
  • 2)参数是一个 Promise 对象,此时,当前的状态交由,该传入的 promise 决定
  •  相当于状态的移交
    
  • 3)当传入的对象,里面实现了 Promise.then 方法
  •  那么也会执行then方法,并且由该then方法决定后续状态
    
/**
 * reslove(),传入的参数
 * 1)普通参数或者对象
 * 2)参数是一个Promise对象,此时,当前的状态交由,该传入的promise决定
 *   相当于状态的移交
 * 3)当传入的对象,里面实现了Promise.then方法(thenable)
 *   那么也会执行then方法,并且由该then方法决定后续状态
 */

new Promise((reslove, reject) => {
  const newPromise = new Promise((reslove, reject) => {
    reject("newPromise ERROR");
  });
  reject(newPromise);
}).then(
  (res) => {
    console.log("res:", res);
  },
  (err) => {
    console.log("err:", err); //newPromise ERROR
  }
);

new Promise((reslove, reject) => {
  const obj = {
    then(resolve, reject) {
      reject("reject");
    },
  };
  reslove(obj);
}).then(
  (res) => {
    console.log("res:", res);
  },
  (err) => {
    console.log("err:", err); //err: reject
  }
);
Promise 对象方法
Promise 对象的方法
console.log(Object.getOwnPropertyDescriptors(Promise.prototype)); // then catch finally 方法
then()
  • then 方法传入的回调函数可以有返回值
  • then 方法本身是有返回值的,它的返回值是一个 Promise 对象>
  • 1)如果回调函数返回的是一个普通的值或对象
  •  那么这个值将被作为一个新的Promise的resolve的值
    
  • 2)如果回调函数返回的是一个 Promise 对象
  •  那么当前Promise的状态有返回的这个Promise对象决定
    
  • 3)如果回调函数返回的一个,实现 thenabel 的对象
  • 那么会执行 then 方法,并且由该 then 方法决定后续状态,并产生一个新的 Promise 对象,返回
const promise = new Promise((resolve, reject) => {
  resolve("cherry");
});

/**
 * then方法传入的回调函数可以有返回值
 * then方法本身是有返回值的,它的返回值是一个Promise对象>
 * 1)如果回调函数返回的是一个普通的值或对象
 *   那么这个值将被作为一个新的Promise的resolve的值
 * 2)如果回调函数返回的是一个Promise对象
 *   那么当前Promise的状态有返回的这个Promise对象决定
 * 3)如果回调函数返回的一个,实现thenabel的对象
 *   那么会执行then方法,并且由该then方法决定后续状态,并产生一个新的Promise对象,返回
 */

//1)
promise
  .then((res1) => {
    console.log(res1); //cherry
    return "KD";
  })
  .then((res2) => {
    console.log(res2); //KD
  });

//2)
promise
  .then((res1) => {
    console.log(res1); //cherry
    return new Promise((resolve, reject) => {
      reject("reject ERROR");
    });
  })
  .then(
    (res2) => {
      console.log(res2);
    },
    (err) => {
      console.log(err); //reject ERROR
    }
  );

//3)
promise
  .then((res1) => {
    console.log(res1); //cherry
    const obj = {
      then(reslove, reject) {
        reslove("reslove KD");
      },
    };
    return obj;
  })
  .then((res2) => {
    console.log(res2); //reslove KD
  });
catch()

catch 可以捕获 Promise 内部的异常

// catch
const promise = new Promise((resloce, reject) => {
  throw new Error("ERROR");
});

promise.then(undefined, (err) => {
  console.log(err); //ERROR
});

promise.catch((err) => {
  console.log(err); //ERROR
});

catch,有异常(拒绝 reject)就会被直接捕获

const promise = new Promise((reslove, reject) => {
  throw new Error("reject ERROR");
  // reslove("cherry");
});
promise
  .then((res) => {
    console.log(res);
    return new Promise((reslove, reject) => {
      reject("then reject error");
    });
  })
  .catch((err) => {
    console.log(err); // 1.reject ERROR , 2.then reject error
  });

catch 方法传入的回调函数的返回值问题和 then 方法同理

finally()

finally()方法返回一个Promise。在 promise 结束时,无论结果是 fulfilled 或者是 rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。

//finally
const promise = new Promise((reslove, reject) => {
  reslove("cherry");
});

promise.finally(() => {
  console.log("被执行了");
});
Promise 类方法
Promise.resolve()

参数:1)普通的值或对象,2)Promise 对象,3)thenable,同理 resolve( )

Promise.resolve(value)方法返回一个以给定值解析后的Promise 对象。如果这个值是一个 promise ,那么将返回这个 promise ;如果这个值是 thenable(即带有"then"方法),返回的 promise 会“跟随”这个 thenable 的对象,采用它的最终状态;否则返回的 promise 将以此值完成。此函数将类 promise 对象的多层嵌套展平。

const obj = {
  naem: "cherry",
};
const oPromise = Promise.resolve(obj);
//相当于
// const oPromise = new Promise((reslove, reject) => {
//   reslove(obj);
// });
oPromise.then((res) => {
  console.log(res); //{ naem: 'cherry' }
});
Promise.reject()

参数:无论传入什么值都一样,直接传入

Promise.reject()方法返回一个带有拒绝原因的Promise对象。

const oPromise = Promise.reject("reject ERROR");
//相当于
// const oPromise = new Promise((reslove, reject) => {
//   reject("reject ERROR");
// });
oPromise.catch((err) => {
  console.log(err); //reject ERROR
});
Promise.all()

Promise.all() 方法接收一个 promise 的 iterable 类型(注:Array,Map,Set 都属于 ES6 的 iterable 类型)的输入,并且只返回一个 Promise 实例, 那个输入的所有 promise 的 resolve 回调的结果是一个数组。这个Promise的 resolve 回调执行是在所有输入的 promise 的 resolve 回调都结束,或者输入的 iterable 里没有 promise 了的时候。它的 reject 回调执行是,只要任何一个输入的 promise 的 reject 回调执行或者输入不合法的 promise 就会立即抛出错误,并且 reject 的是第一个抛出的错误信息。

const promise1 = new Promise((reslove, reject) => {
  setTimeout(() => {
    reslove("promise1");
  }, 2000);
});
const promise2 = new Promise((reslove, reject) => {
  setTimeout(() => {
    reslove("promise2");
  }, 3000);
});
const promise3 = new Promise((reslove, reject) => {
  setTimeout(() => {
    // reslove("promise3");
    reject("promise3 ERROR");
  }, 1500);
});
//Promise.all([]) 等待所有的promise有结果后返回一个新的包含所有的结果的Promise对象
//当,所有传入的promise中,存在一个rejectd状态的Promise,那么这个promise的rejected状态
// 将作为返回的新的Promise的reject的参数;
const allPromise = Promise.all([promise1, promise2, promise3, "cherry"]);
allPromise.then(
  (res) => {
    console.log(res); //[ 'promise1', 'promise2', 'promise3', 'cherry' ]
  },
  (err) => {
    console.log(err); //promise3 ERROR
  }
);
Promise.allSettled()

该 Promise.allSettled()方法返回一个在所有给定的 promise 都已经fulfilledrejected后的 promise,并带有一个对象数组,每个对象表示对应的 promise 结果。

当您有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise的结果时,通常使用它。

相比之下,Promise.all() 更适合彼此相互依赖或者在其中任何一个reject时立即结束。

const promise1 = new Promise((reslove, reject) => {
  setTimeout(() => {
    reslove("promise1");
  }, 2000);
});
const promise2 = new Promise((reslove, reject) => {
  setTimeout(() => {
    reslove("promise2");
  }, 3000);
});
const promise3 = new Promise((reslove, reject) => {
  setTimeout(() => {
    // reslove("promise3");
    reject("promise3 ERROR");
  }, 1500);
});

const allSettledPromise = Promise.allSettled([
  promise1,
  promise2,
  promise3,
  "cherry",
]);

allSettledPromise.then((res) => {
  console.log(res);
});
// [
//   { status: "fulfilled", value: "promise1" },
//   { status: "fulfilled", value: "promise2" },
//   { status: "rejected", reason: "promise3 ERROR" },
//   { status: "fulfilled", value: "cherry" },
// ];
Promise.race()

Promise.race(iterable)方法返回一个 promise,一旦迭代器中的出现某个 promise 已兑现或拒绝,返回的 promise 就会解决或拒绝。

const racePromise = Promise.race([promise1, promise2, promise3, "cherry"]);
//谁先有结果,就返回谁的状态
racePromise.then(
  (res) => {
    console.log(res); //cherry
  },
  (err) => {
    console.log(err); //promise3 ERROR  参数:([promise1, promise2, promise3])
  }
);
Promise.any()

Promise.any()接收一个 Promise 可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise 。如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promiseAggregateError类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起。本质上,这个方法和Promise.all()是相反的。

const anyPromise = Promise.any([promise1, promise2, promise3]);
anyPromise.then(
  (res) => {
    console.log(res); //promise1
  },
  (err) => {
    console.log(err); //所有的promises都失败/拒绝
  }
);
Promise的实现
//promise的设计模式的实现
const Promise_status_Pending = "pending";
const Promise_status_Fulfilled = "fulfilled";
const Promise_status_Rejected = "rejected";
class CherryPromise {
  constructor(executor) {
    //promise的状态管理
    this.status = Promise_status_Pending;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledCallBacks = [];
    this.onRejectCallBacks = [];

    //resolve
    const resolve = (value) => {
      if (this.status === Promise_status_Pending) {
        this.status = Promise_status_Fulfilled;
        //queueMicrotask将代码传入微任务队列
        this.value = value;
        queueMicrotask(() => {
          //将处理onFulfilled回调函数加入onFulfilledCallBacks队列执行
          !!this.onFulfilledCallBacks &&
            this.onFulfilledCallBacks.forEach((fn) => {
              fn(this.value);
            });
        });
      }
    };
    //reject
    const reject = (reason) => {
      if (this.status === Promise_status_Pending) {
        this.status = Promise_status_Rejected;
        this.reason = reason;
        queueMicrotask(() => {
          !!this.onRejectCallBacks &&
            this.onRejectCallBacks.forEach((fn) => {
              fn(this.reason);
            });
        });
      }
    };
    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }
  //then方法
  then(onFulfilled, onRejected) {
    const newPromise = new CherryPromise((resolve, reject) => {
      if (this.status === Promise_status_Fulfilled && onFulfilled) {
        try {
          const value = onFulfilled(this.value) || this.value;
          CherryPromise._resolvePromise(value, newPromise, resolve, reject);
        } catch (error) {
          reject(error);
        }
      } else if (this.status === Promise_status_Rejected && onRejected) {
        try {
          const reason = onRejected(this.reason) || this.reason;
          CherryPromise._resolvePromise(reason, newPromise, resolve, reject);
        } catch (error) {
          reject(error);
        }
      } else if (this.status === Promise_status_Pending) {
        this.onFulfilledCallBacks.push(() => {
          const value = (onFulfilled && onFulfilled(this.value)) || this.value;
          CherryPromise._resolvePromise(value, newPromise, resolve, reject);
        });
        this.onRejectCallBacks.push(() => {
          const reason = (onRejected && onRejected(this.reason)) || this.reason;
          CherryPromise._resolvePromise(reason, newPromise, resolve, reject);
        });
      }
    });
    return newPromise;
  }

  static _resolvePromise(value, promise, resolve, reject) {
    if (promise === value) {
      return reject(
        new TypeError("Chaining cycle detected for promise #<promise>")
      );
    }
    try {
      if (value instanceof CherryPromise) {
        return value.then(
          (res) => {
            resolve(res);
          },
          (err) => {
            resolve(err);
          }
        );
      }
      if (typeof value === "object" && value instanceof Object && value.then) {
        return value.then(
          (res) => {
            resolve(res);
          },
          (err) => {
            resolve(err);
          }
        );
      }
    } catch (error) {
      return reject(error);
    }
    return resolve(value);
  }
  //catch
  catch(onRejected) {
    return CherryPromise.then(undefined, onRejected);
  }

  //finally
  finally(onfinally) {
    return this.then(
      (value) => CherryPromise.resolve(onfinally()).then(() => value),
      (reason) => CherryPromise.reject(onfinally()).then(() => reason)
    );
  }
  //类的静态方法
  //resolve
  static resolve(value) {
    return new CherryPromise((resolve, reject) => {
      CherryPromise._resolvePromise(value, null, resolve, reject);
    });
  }
  //reject
  static reject(reason) {
    return new CherryPromise((resolve, reject) => {
      reject(reason);
    });
  }

  //将不是promise对象转化成Promise
  static _transformPromise(values) {
    const newPromiseArray = [...values];
    for (let i = 0; i < values.length; i++) {
      if (!(values[i] instanceof CherryPromise)) {
        const newPromise = CherryPromise.resolve(values[i]);
        newPromiseArray.splice(i, 1, newPromise);
      }
    }
    return newPromiseArray;
  }
  //all
  static all(values) {
    values = CherryPromise._transformPromise(values);
    return new CherryPromise((resolve, reject) => {
      let temp = 0;
      let resolveResult = [];
      for (let i = 0; i < values.length; i++) {
        implementPromise(i, values[i]);
      }
      function implementPromise(index, promise) {
        promise.then(
          (res) => {
            resolveResult[index] = res;
            temp++;
            temp === values.length && resolve(resolveResult);
          },
          (err) => {
            reject(err);
          }
        );
      }
    });
  }

  //race
  static race(values) {
    values = CherryPromise._transformPromise(values);
    return new CherryPromise((resolve, reject) => {
      for (let i = 0; i < values.length; i++) {
        values[i].then(
          (res) => {
            resolve(res);
          },
          (err) => {
            reject(err);
          }
        );
      }
    });
  }

  static any(values) {
    values = CherryPromise._transformPromise(values);
    return new CherryPromise((resolve, reject) => {
      let state = 0;
      for (let i = 0; i < values.length; i++) {
        values[i].then(
          (res) => {
            resolve(res);
          },
          (err) => {
            state++;
            if (state === values.length) {
              reject("All promises were rejected");
            }
          }
        );
      }
    });
  }

  static allSettled(values) {
    values = CherryPromise._transformPromise(values);
    return new CherryPromise((resolve, reject) => {
      let resultArray = [];
      let temp = 0;
      for (let i = 0; i < values.length; i++) {
        values[i].then(
          (res) => {
            resultArray[i] = {
              status: Promise_status_Fulfilled,
              value: res,
            };
            temp++;
            temp === values.length && resolve(resultArray);
          },
          (err) => {
            resultArray[i] = {
              status: Promise_status_Rejected,
              reason: err,
            };
            temp++;
            temp === values.length && resolve(resultArray);
          }
        );
      }
    });
  }
}

});
return newPromise;

}

static _resolvePromise(value, promise, resolve, reject) {
if (promise === value) {
return reject(
new TypeError(“Chaining cycle detected for promise #”)
);
}
try {
if (value instanceof CherryPromise) {
return value.then(
(res) => {
resolve(res);
},
(err) => {
resolve(err);
}
);
}
if (typeof value === “object” && value instanceof Object && value.then) {
return value.then(
(res) => {
resolve(res);
},
(err) => {
resolve(err);
}
);
}
} catch (error) {
return reject(error);
}
return resolve(value);
}
//catch
catch(onRejected) {
return CherryPromise.then(undefined, onRejected);
}

//finally
finally(onfinally) {
return this.then(
(value) => CherryPromise.resolve(onfinally()).then(() => value),
(reason) => CherryPromise.reject(onfinally()).then(() => reason)
);
}
//类的静态方法
//resolve
static resolve(value) {
return new CherryPromise((resolve, reject) => {
CherryPromise._resolvePromise(value, null, resolve, reject);
});
}
//reject
static reject(reason) {
return new CherryPromise((resolve, reject) => {
reject(reason);
});
}

//将不是promise对象转化成Promise
static _transformPromise(values) {
const newPromiseArray = […values];
for (let i = 0; i < values.length; i++) {
if (!(values[i] instanceof CherryPromise)) {
const newPromise = CherryPromise.resolve(values[i]);
newPromiseArray.splice(i, 1, newPromise);
}
}
return newPromiseArray;
}
//all
static all(values) {
values = CherryPromise._transformPromise(values);
return new CherryPromise((resolve, reject) => {
let temp = 0;
let resolveResult = [];
for (let i = 0; i < values.length; i++) {
implementPromise(i, values[i]);
}
function implementPromise(index, promise) {
promise.then(
(res) => {
resolveResult[index] = res;
temp++;
temp === values.length && resolve(resolveResult);
},
(err) => {
reject(err);
}
);
}
});
}

//race
static race(values) {
values = CherryPromise._transformPromise(values);
return new CherryPromise((resolve, reject) => {
for (let i = 0; i < values.length; i++) {
values[i].then(
(res) => {
resolve(res);
},
(err) => {
reject(err);
}
);
}
});
}

static any(values) {
values = CherryPromise._transformPromise(values);
return new CherryPromise((resolve, reject) => {
let state = 0;
for (let i = 0; i < values.length; i++) {
values[i].then(
(res) => {
resolve(res);
},
(err) => {
state++;
if (state === values.length) {
reject(“All promises were rejected”);
}
}
);
}
});
}

static allSettled(values) {
values = CherryPromise._transformPromise(values);
return new CherryPromise((resolve, reject) => {
let resultArray = [];
let temp = 0;
for (let i = 0; i < values.length; i++) {
values[i].then(
(res) => {
resultArray[i] = {
status: Promise_status_Fulfilled,
value: res,
};
temp++;
temp === values.length && resolve(resultArray);
},
(err) => {
resultArray[i] = {
status: Promise_status_Rejected,
reason: err,
};
temp++;
temp === values.length && resolve(resultArray);
}
);
}
});
}
}


  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值