Skip to content

原生

构造函数与类的理解

  • 构造函数

实例对象 (stu)__proto__ 指向 构造函数 (Student)prototypeprototype 是一个对象 ,有 { constructor, [[prototype]]} 两个属性,我们要想实现原型方法共享,只需要把 [[prototype]] 指向 Person.prototype

js
function Person(name, age) {
  this.name = name;
  this.age = age;
}

function Student(name, age, score) {
  Person.call(this, name, age);
  this.score = score;
}
// 1
// Student.prototype = Object.create(Person.prototype);
// Student.prototype.constructor = Student // 让 constructor 保持不变

// 2 推荐这种写法(ES6 类中 constructor 是不可枚举的)
Student.prototype = Object.create(Person.prototype, {
  constructor: { value: Student }
});
// 3  等同于 Student.prototype.__proto__ = Person.prototype
// Object.setPrototypeOf(Student.prototype, Person.prototype);

// ES6 中静态属性和方法也会继承 ===>  Student.__proto__ = Person
Object.setPrototypeOf(Student, Person);
  • class 类
js
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

class Student extends Person {
  constructor(name, age, score) {
    super(name, age);
    this.score = score;
  }
}

const stu = new Student('HJ', 30, 88);
console.log(stu.__proto__ === Student.prototype); // true
console.log(Student.prototype.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true

console.log(Student.__proto__ === Person); // true
console.log(Person.__proto__ === Function.prototype); // true
console.log(Function.prototype === Function.__proto__); // true
console.log(Function.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true

/**
 * 结论一: 所有的实例对象的隐式原型(stu.__proto__)都指向构造该对象的 构造函数的原型(Student.prototype)
 * 结论二: 构造函数的隐式原型(Person.__proto__) 都指向 Function.prototype =>   构造函数 = new Function()
 *          Fumber String Boolean Array Object Function Symbol Map Set EventTarget 的 __proto__ 都指向 Function.prototype
 *          prototype.__proto 都指向 Object.prototype, Object.prototype.__proto__ = null   (万物皆对象)
 * 结论三: Function 的隐式原型 与 显式原型 是相同的   => Function.__proto__ === Function.prototype (鸡生蛋还是蛋生鸡?)
 */
  • DOM 继承相关
js
console.log(HTMLDivElement.prototype.__proto__ === HTMLElement.prototype); // HTMLElement.prototype.innerText
console.log(HTMLElement.prototype.__proto__ === Element.prototype); // Element.prototype.innerHTML
console.log(Element.prototype.__proto__ === Node.prototype); // Node.prototype.textContent 元素属于节点
console.log(Node.prototype.__proto__ === EventTarget.prototype);
console.log(EventTarget.prototype.__proto__ === Object.prototype);

console.log(HTMLDivElement.__proto__ === HTMLElement);
console.log(HTMLElement.__proto__ === Element);
console.log(Element.__proto__ === Node);
console.log(Node.__proto__ === EventTarget);
console.log(EventTarget.__proto__ === Function.prototype);
console.log(Function.prototype.__proto__ === Object.prototype);
  • 构造函数与类的总结
js
class Person {
  // 私有属性 必须在内的内部先声明  实例对象不能读取 不能解构
  #age;
  constructor(name, age) {
    this.name = name;
    this.#age = age;
    // this.#gender = 'male';  // 报错
  }

  // get 函数会在实例自身 与 原型(Person.prototype)上都存在
  get getInfo1() {
    return this.#age;
  }
  // 只存在于 Person.prototype
  getInfo2() {
    return this.#age;
  }
}
js
// Function 与 Object 的关系
console.log(Object.getPrototypeOf(Function) === Function.prototype); // Function 的 隐式原型 和 显式原型是一样的
console.log(Object.getPrototypeOf(Function.prototype) === Object.prototype); //Function.prototype 是 Object 的实例
console.log(Object.getPrototypeOf(Object) === Function.prototype); // Object 是 Function 的实例
  • 关于 typescript class 编绎 ES5 解析

image-20201011214324302

js
let obj1 = { name: 'zhangsan', gender: '', age: 24 };
let objProxy = new Proxy(obj1, {
  get: function (target, key, receiver) {
    console.log(target); // obj1
    console.log(key); // obj1 key
    console.log(receiver); // objProxy
    // Reflect.has(target,key) 相当于 key in target
    if (Reflect.has(target, key)) {
      return target[key];
    }
    throw new ReferenceError(`该对象上不存在${key}属性`);
  },
  set: function (target, key, value, receiver) {
    if (typeof value === 'string') {
      value = value.trim();
    }
    target[key] = value;
  }
});
console.log(objProxy.name);
console.log(objProxy.sex); // 报错objProxy.sex = ' 男 '
console.log(objProxy.sex);
console.log(obj1);
console.log(objProxy);

function Person(name, age) {
  this.name = name;
  this.age = age;
}
function create(constructor, params) {
  // 1.创建一个空对象,作为将要返回的对象实例
  var obj = {};
  let args = [].slice.call(arguments);
  args.splice(0, 1);
  // 2. 将空对象的原型指向构造函数的原型对象
  Object.setPrototypeOf(obj, constructor.prototype);
  // 3.将空对象赋值给构造函数内部的this关键字,并开始执行构造函数的内部代码
  constructor.apply(obj, args);
  return obj;
}
let obj = create(Person, 'hou', 28);
console.log(obj);
console.log(Object.getPrototypeOf(obj) === Person.prototype);

Object 静态方法及属性深入理解

  • 访问器
js
const obj = {
  log: ['a', 'b', 'c'],
  get Last() {
    if (this.log.length === 0) {
      return undefined;
    }
    return this.log[this.log.length - 1];
  }
};
console.dir(obj);
console.log(obj.Last); // c
js
const language = {
  set current(name) {
    this.log.push(name);
  },
  log: []
};
language.current = 'CN';
language.current = 'EN';
console.log(language); // { log: [ 'CN', 'EN' ] }
console.log(language.current); // undefined

var o = { a: 0 };
Object.defineProperty(o, 'b', {
  set: function (x) {
    this.a = x / 2;
  }
});
console.dir(o);
o.b = 20;
console.log(o.a); // 10

Object.assign(target,...source)

js
let obj1 = { a: 1, b: 2 };
let obj2 = Object.assign({}, obj1);
obj1.a = 2;
console.dir(obj1); // { a: 2, b: 2 }
console.dir(obj2); // { a: 1, b: 2 } // 返回目标对象的属性值不会改变

const o1 = { a: 1 };
const o2 = { b: 2 };
const o3 = { c: 3 };
const obj = Object.assign(o1, o2, o3);
console.log(obj); // {a: 1, b: 2, c: 3}
console.log(o1); // {a: 1, b: 2, c: 3}  目标对象也会改变
console.log(o2); // {b: 2}
console.log(o3); // {c: 3}

const o1 = { a: 1, b: 1, c: 1 };
const o2 = { b: 2, c: 2 };
const o3 = { c: 3 };

const obj = Object.assign({}, o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }  属性被后续参数中具有相同属性的其他对象覆盖。

// 继承属性 和不可枚举属性 是不能拷贝的
const obj = Object.create(
  { foo: 1 },
  {
    //  foo 是继承属性 (obj.__proto__.foo)
    bar: { value: 1 },
    baz: { value: 3, enumerable: true } // 不可枚举
  }
);
console.log(obj); // {baz: 3, bar: 1}  foo 是继承属性,不会打印出来
const copy = Object.assign({}, obj);
console.log(copy); // {baz: 3}

Object.create(proto,properties)

js
var o = {};
const j = Object.create(o, {
  firstname: { value: 'hou', enumerable: true }
  // configurable enumerable writable 默认为false
});
console.log(j.__proto__ == o); //说明 j(实例对象) 的原型对象(j的构造函数的prototype)是 o
console.log(o);
console.log(j);
特殊情况;
const obj = Object.create(
  {},
  {
    firstname: { value: 'hou', enumerable: true }
  }
);
此时;
console.log(j.__proto__ == {}); // false
console.log(j.__proto__.__proto__ == {}.__proto__); // true 因为 {} 是 new Object()是实例对象,每一个实例对象是不相等的, 但是它们的原型对象是相同的
new Object() == new Object(); // false 构造函数的实例对象是不相等的
new Object().__proto__ == new Object().__proto__; // true
// ps: 如果想让它们相等  可以提前以字面量的方式赋值  var a = {}

Object.defineProperty(obj,prop,descriptor)

value 与 writable 不能和 get 和 set 同时出现,即设置了 value 或 writable,不能出现 get 或 set,反之亦然。

js
let info = { name: 'hou', age: 28 };
Object.defineProperty(info, 'sex', {
  value: '',
  configurable: false, // 可删除
  enumerable: false, // 可枚举 为false 属性在chrome中看起来是浅色的
  writable: false // 可重新赋值
});
console.log(info); // {name: "houjian", age: 28, sex: "男"}

function Archiver() {
  var temperature = null;
  var archiver = [];
  var b = Object.defineProperty(this, 'temperature', {
    get: function () {
      console.log('get!');
      return temperature;
    },
    set: function (value) {
      temperature = value;
      archiver.push({ val: temperature });
    }
  });
  this.getArchiver = function () {
    return archiver;
  };
}
// 数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。存取描述符是由 getter 函数和 setter 函数所描述的属性。一个描述符只能是这两者其中之一;不能同时是两者。
var arc = new Archiver();
console.dir(arc);
console.log(arc.temperature); // null
arc.temperature = 11;
arc.temperature = 13;
console.log(arc.temperature); // 13

console.log(arc.getArchiver()); // [ { val: 11 }, { val: 13 } ]

Object.entries(obj)返回自身可枚举的键值对数组

js
const arr1 = ['a', 'b', 'c'];
const obj = { a: 1, b: 2, c: 3 };

console.log(Object.entries(obj)); // [['a', 5], ['b', 7], ['c', 9]];
console.log(Object.entries(arr1)); // [['0', 'a'], ['1', 'b'], ['2', 'c']];

console.log(Object.entries(obj).entries()); // Array Iterator
console.log(Object.entries(arr1).entries()); // Array Iterator

for (const [key, value] of Object.entries(obj)) {
  // 可迭代键值对二维数组
  console.log(key, value);
}

for (const [key, value] of arr1.entries()) {
  // Array Iterator 对象
  console.log(key, value);
}

console.log(new Map(Object.entries(obj))); // map 对象
console.log(new Map(Object.entries(arr1))); // map 对象

console.log(Object.fromEntries(new Map(Object.entries(obj)))); // { a: 5, b: 7, c: 9 }
console.log(Object.fromEntries(new Map(Object.entries(arr1)))); // { 0: 'a', 1: 'b', 2: 'c' }

Object.freeze(obj)冻结

Object.isFrozen(obj)判断是否被冻结

js
var obj = {
  prop: function () {},
  foo: 'bar',
  name: {}
};
obj.foo = 'baz';
obj.lump = '123';
Object.freeze(obj);

console.log(obj);
obj.foo = 'bay'; // 未更改属性值
console.log(Object.isFrozen(obj)); // true
obj.name.a = 213; //修改了  未被深冻结

function deepFreeze(obj) {
  let propNames = Object.getOwnPropertyNames(obj);
  propNames.forEach(name => {
    var prop = obj[name];
    if (typeof prop == 'object' && prop != null) {
      deepFreeze(prop);
    }
  });
  return Object.freeze(obj);
}
deepFreeze(obj);
obj.name.a = 123; // 未修改  还是213
console.log(obj);
console.log(Object.isFrozen(obj)); // true

DOM 节点

html
<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
  <li>5</li>
</ul>
<script>
  const ul = document.getElementsByTagName('ul')[0];
  console.log(ul.children);
  console.log(ul.firstElementChild);
  console.log(ul.lastElementChild);
  console.log(ul.children[2].previousElementSibling); // li2
  console.log(ul.children[2].nextElementSibling); // li4
  console.log('-----------');
  console.log(ul.childNodes);
  console.log(ul.firstChild);
  console.log(ul.lastChild);
  console.log(ul.childNodes[2].previousSibling === ul.childNodes[2].previousElementSibling);
</script>

事件流

addEventListener

js
target.addEventListener(type, listener, options);
{ capture: false, once: false, passive: false } // 默认值
capture 事件传播方式 与只传一个参数一样
once 只能被点击一次
passivetrue时,表示阻止e.preventDefault
// 1 捕获阶段 2 目标阶段 3 冒泡阶段 e.eventPhase => number
js
// 执行顺序 =》  捕获 --- 目标 ---冒泡
span.addEventListener(
  'click',
  e => {
    console.log('我是重孙子');
  },
  true
);
span.addEventListener(
  'click',
  e => {
    console.log('我是重孙子 冒泡');
  },
  false
);
grandson.addEventListener(
  'click',
  e => {
    console.log('我是孙子');
  },
  true
);
son.addEventListener(
  'click',
  e => {
    console.log('我是儿子');
  },
  true
);
parent.addEventListener(
  'click',
  e => {
    console.log('我是父亲');
  },
  true
);
document.addEventListener(
  'click',
  e => {
    console.log('我是documnet');
  },
  false
);
//  当点击 span 时 按照顺序 为true的先执行 输出结果为 父亲 儿子 孙子 重孙子 重孙子冒泡 document

break 跳出当前循环 continue 跳过当前循环,开始下一轮

正则相关

字符串实例方法

  • String.prototype.replace
js
// $$ => 美元符号 $ => 匹配子串前面的文本` $' => 匹配子串后面的文本 $& => 匹配子串 $n => 捕获组 $<name> => 命名捕获组
const a = 'abc'.replace('b', "[$'-$&-$`]");
// a[c-b-a]c
console.log(a);

var prices = { p1: '$1.99', p2: '$9.99', p3: '$5.00' };
var template = '<span id="p1"></span>' + '<span id="p2"></span>' + '<span id="p3"></span>';
const reg1 = /(<span\sid=")(?<id>.*?)(">)(<\/span>)/g;
// match 匹配的子串 pn => 匹配捕获组 offst => index string => 原字符串 namedCaptrueGroup => 命名捕获组
const d = template.replace(
  reg1,
  function (match, p1, p2, p3, p4, offset, string, namedCaptureGroup) {
    // console.log(arguments);
    return p1 + p2 + p3 + prices[namedCaptureGroup.id] + p4;
  }
);
// <span id="p1">$1.99</span><span id="p2">$9.99</span><span id="p3">$5.00</span>
console.log(d);
  • String.prototype.split
js
const a = 'a,  b, c, d'.split(/,\s*/);
// ["a", "b", "c", "d"]
console.log(a);

const b = 'a,  b, c, d'.split(/,\s*/, 2);
// ["a", "b"]
console.log(b);

const c = 'aaa*a*'.split(/a*/);
// ["", "*", "*"]  此外默认是贪婪匹配, a* 会默认尽可能多的匹配 a
console.log(c);

const d = 'aaa**a*'.split(/a*/);
// ["", "*", "*", "*"]
console.log(d);

const e = 'aaa*a*'.split(/(a*)/);
// ["", "aaa", "*", "a", "*"]
console.log(e);

断言

js
// 此字符串匹配的是 => 我是你我是你
const reg1 = /(我是你)(?=我是)\1/;

// 此字符串匹配的是 => 我是你我是我是你
const reg2 = /(我是你)(?=我是\1)/;
  • 正则处理数字
typescript
function formatCurrency(num) {
  num = typeof num === 'number' ? num : parseFloat(num);
  const [l, r] = num.toFixed(2).split('.');

  // const reg = /\B(?=(\d{3})+$)/g;
  // const reg = /\B(?=(\d{3})+(?!\d))/g;
  // const reg = /\d(?=(\d{3})+$)/g; // 需要替换为 $&,
  // return l.replace(reg, ',') + '.' + r;

  // let endIndex = l.length % 3;
  // const arr = [];
  // arr.push(l.substring(0, endIndex));
  // while (endIndex < l.length) {
  //   arr.push(l.substring(endIndex, (endIndex += 3)));
  // }
  // return arr.join(',') + '.' + r;

  const [left, right] = num.toString().split('.');
  const arr = left.split('');
  for (let i = arr.length - 3; i > 0; i -= 3) arr.splice(i, 0, ',');
  return arr.join('') + (right ? `.${right}` : '');

  const reverseLArr = l.split('').reverse();
  let t = '';
  for (let i = 0; i < reverseLArr.length; i++) {
    t += reverseLArr[i] + ((i + 1) % 3 === 0 && i + 1 !== reverseLArr.length ? ',' : '');
  }
  return t.split('').reverse().join('') + '.' + r;
}
js
// Lookahead assertion => reg(?=exp) => 即reg后面的内容是exp的
let str1 = '后盾人不断分享视频教程,学习后盾人教程提升编程能力。';
let reg1 = /后盾人(?=教程)/;
str1 = str1.replace(reg1, v => 'HJ');
console.log(str1); // 后盾人不断分享视频教程,学习HJ教程提升编程能力。

// Lookbehind assertion => (?<=exp)reg => 即reg前面内容是exp的
let str2 = '后盾人不断分享视频教程,学习后盾人教程提升编程能力。';
let reg2 = /(?<=学习)后盾人/;
str2 = str2.replace(reg2, v => 'HJ');
console.log(str2); // 后盾人不断分享视频教程,学习HJ教程提升编程能力。

// Negative lookahead assertion => reg(?!exp) => 即reg后面内容不是exp的
let str3 = '后盾人不断分享视频教程,学习后盾人教程提升编程能力。';
let reg3 = /后盾人(?!不断)/g;
str3 = str3.replace(reg3, v => v + '系列书箱');
console.log(str3); // 后盾人不断分享视频教程,学习后盾人系列书箱教程提升编程能力。

// Negative lookbehind assertion => (?<!exp)reg => 即reg前面内容不是exp的
let str4 = '后盾人不断分享视频教程,学习后盾人教程提升编程能力。';
let reg4 = /(?<!视频)教程/;
str4 = str4.replace(reg4, v => 'HJ');
console.log(str4); // 后盾人不断分享视频教程,学习后盾人HJ提升编程能力。

// 非捕获取 (?:\d)

Iterator

自定义 Iterator

js
let arr = [1, 2, 3];
function makeIterator(arr) {
  let nextIndex = 0;
  return {
    next() {
      return nextIndex < arr.length
        ? { value: arr[nextIndex++], done: false }
        : { value: undefined, done: true };
    }
  };
}
let it = makeIterator(arr);
console.log(it.next()); // {value: 1, done: false}
console.log(it.next()); // {value: 2, done: false}
console.log(it.next()); // {value: 3, done: false}
console.log(it.next()); // {value: undefined, done: true}

在构造函数的原型上部署 Symbol.iterator 方法,调用该方法会返回遍历器对象 iterator,调用该对象的 next 方法,在返回一个值的同时,自动将内部指针移到下一个实例,类似与数组的 iterator 实现

js
function Obj(value) {
  this.value = value;
  this.next = null;
}

Obj.prototype[Symbol.iterator] = function () {
  let iterator = { next: next };
  let current = this;
  function next() {
    if (current) {
      // 这里的 this 是迭代器 it
      let value = current.value;
      current = current.next;
      return { done: false, value: value };
    } else {
      return { done: true, value: undefined };
    }
  }
  return iterator;
};
let one = new Obj(1);
let two = new Obj(2);
let three = new Obj(3);
one.next = two;
two.next = three;
let it = one[Symbol.iterator]();
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
for (const oneElement of one) {
  console.log(oneElement);
}

对象添加 iterator 接口

js
let obj = {
  data: ['hello', 'world'],
  [Symbol.iterator]() {
    const self = this;
    let index = 0;
    return {
      next() {
        if (index < self.data.length) {
          return { value: self.data[index++], done: false };
        } else {
          return { value: undefined, done: true };
        }
      }
    };
  }
};
let it = obj[Symbol.iterator]();

用 while 来遍历

js
let iterable = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3,
  [Symbol.iterator]: [][Symbol.iterator]
};
let it = iterable[Symbol.iterator]();
let result = it.next();
// 死循环 输出a
// while (!result.done) {
//     console.log(result.value)
// }
while (!result.done) {
  let x = result.value;
  console.log(x); // 依次输出abc
  result = it.next();
}

let str = new String('hi');
console.log([...str]);

str[Symbol.iterator] = function () {
  return {
    _first: true,
    next() {
      // this 是 {_first: true, next: ƒ}
      if (this._first) {
        this._first = false;
        return { value: 'Bye', done: false };
      } else {
        return { value: undefined, done: true };
      }
    }
  };
};
console.log([...str]); // ['Bye']
console.log(str); // hi

Generator

js
// yield* 表达式用来在一个Generator 表达式中执行另一个Generator表达式
let arr1 = [1, [[2, 3], 4], 5, 6];
let flat = function* (a) {
  for (let i = 0; i < a.length; i++) {
    if (typeof a[i] !== 'number') {
      yield* flat(a[i]);
    } else {
      yield a[i];
    }
  }
};

for (let v of flat(arr1)) {
  console.log(v);
}
js
let myIterator = {};
myIterator[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};
console.log([...myIterator]);
let g1 = myIterator[Symbol.iterator]();
// 迭代器调用迭代器函数 等于 迭代器本身
console.log(g1[Symbol.iterator]() == g1);

function* gen() {}
let g = gen();
console.log(g[Symbol.iterator]() === g);

// 下面两个输出一样
let arr = [1, 2, 3, 4, 5, 6];
for (const number of arr[Symbol.iterator]()) {
  console.log(number);
}
for (const number of arr) {
  console.log(number);
}
js
// next(arg)是传给yield表达式的,而不是被赋值给变量的
// 括号很重要 如果不加结果不一样
function* f(i) {
  let a = (yield i) + 30;
  return a + i;
}
let it = f(6);
console.log(it.next()); // {value: 6,done: false} i = 6
console.log(it.next(20)); // {value: 56,done: true} a = 50
// (yield i) = 20 ,a = 50

function* f(i) {
  let a = yield i + 30;
  return a + i;
}

let it = f(6);
console.log(it.next()); // {value: 36,done: false} i = 6
console.log(it.next(20)); // {value: 26,done: true} a = 20
//  (yield i +30) =20 , a= 20

/*
 * yield 表达式总是无返回值 也就是 (yield i)=undefined,  所以reset 是undefined
 * 每当运行到yield表达式时 reset的值 总是undefined
 * 当next 方法带一个参数true时,变量reset就被重置为这个参数
 * next 方法的参数表示上一个(yield)表达式的返回值,所以第一次使用next方法参数是无效的
 * */
function* f1() {
  for (let i = 0; true; i++) {
    let reset = yield i;
    if (reset) {
      i = -1;
    }
  }
}
var g = f1();
console.log(g.next()); //{value: 0, done: false}
console.log(g.next()); //{value: 1, done: false}
console.log(g.next(true)); //{value: 0, done: false}
console.log(g.next()); //{value: 1, done: false}
console.log(g.next()); //{value: 2, done: false}

function* foo(x) {
  var y = 2 * (yield x + 1);
  var z = yield y / 3;
  return x + y + z;
}
// yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

var a = foo(5);
console.log(a.next()); // {value: 6, done: false}
console.log(a.next()); // {value: NaN, done: false}
console.log(a.next()); // {value: NaN, done: true}

var b = foo(5);
console.log(b.next()); // {value: 6, done: false}  x=5
console.log(b.next(12)); // { value: 8, done: false}  y=24 (yield (x+1))=12
console.log(b.next(13)); // { value: 42, done: true} z=13 (yield (y /3)) =13
js
// 很难理解的一个generator函数
function wrapper(generatorFunction) {
  return function (...args) {
    let generatorObject = generatorFunction(...args);
    // 先调用一下next
    generatorObject.next();
    return generatorObject;
  };
}

const wrapped = wrapper(function* () {
  console.log(`First input: ${yield}`);
  return 'Done';
});

console.log(wrapped); // function(...args){....}
console.log(wrapped()); // generatorObject
// 这里是第二次调用
wrapped().next('hello');

Proxy

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

js
let obj1 = {
  name: 'zhangsan',
  gender: '',
  age: 24
};
let objProxy = new Proxy(obj1, {
  get: function (target, key, receiver) {
    // console.log(target) // obj1
    console.log(key); // obj1 key
    console.log(receiver); // objProxy
    // Reflect.has(target,key) 相当于 key in target
    if (Reflect.has(target, key)) {
      return target[key];
    }
    throw new ReferenceError(`该对象上不存在${key}属性`);
  },
  set: function (target, key, value, receiver) {
    if (typeof value === 'string') {
      value = value.trim();
    }
    target[key] = value;
  }
});
console.log(objProxy.name);
// console.log(objProxy.sex) // 报错
objProxy.sex = '';
console.log(objProxy.sex);
console.log(obj1);
console.log(objProxy);

Promise

Promises/A+

手写 Promise
js
const STATE = {
  PENDING: 'pending',
  FULFILLED: 'fulfilled',
  REJECTED: 'rejected'
};

const styles = ['font: 20px "Fira Code"', 'color: #9A1663'];
let id = 0;
class MyPromise {
  id = id++;
  state = STATE.PENDING;
  value = null;
  reason = null;
  onFulfilledCallbacks = [];
  onRejectedCallbacks = [];

  constructor(executor) {
    try {
      executor(this.resolve, this.reject);
    } catch (error) {
      this.reject(error);
    }
  }

  resolve = value => {
    if (this.state === STATE.PENDING) {
      this.state = STATE.FULFILLED;
      this.value = value;
      // console.log('resolve=====> ', value);
      while (this.onFulfilledCallbacks.length) {
        this.onFulfilledCallbacks.shift()(value);
      }
    }
  };

  reject = reason => {
    if (this.state === STATE.PENDING) {
      this.state = STATE.REJECTED;
      this.reason = reason;
      // console.log('reject=====> ', reason);
      while (this.onRejectedCallbacks.length) {
        this.onRejectedCallbacks.shift()(reason);
      }
    }
  };

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : reason => {
            throw reason;
          };

    // const thenLog = `${this.state}${this.value || this.reason || ''}`;
    // console.log(`%cthen=====>\t${thenLog} `, styles.join(';'));
    const promise2 = new MyPromise((resolve, reject) => {
      const fulfilledMicrotask = () => {
        queueMicrotask(() => {
          try {
            const x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      };
      const rejectedMicrostask = () => {
        queueMicrotask(() => {
          try {
            const x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      };
      if (this.state === STATE.FULFILLED) {
        fulfilledMicrotask();
      } else if (this.state === STATE.REJECTED) {
        rejectedMicrostask();
      } else if (this.state === STATE.PENDING) {
        this.onFulfilledCallbacks.push(fulfilledMicrotask);
        this.onRejectedCallbacks.push(rejectedMicrostask);
      }
    });
    return promise2;
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }

  static resolve(value) {
    if (value instanceof MyPromise) return value;
    return new MyPromise(resolve => {
      // 可能是 thenable 对象
      resolvePromise(null, value, resolve, this.reject)
    });
  }

  static reject(reason) {
    return new MyPromise((resolve, reject) => {
      reject(reason);
    });
  }
}

function resolvePromise(promise, x, resolve, reject) {
  // console.log('resolvePromise=====> ', promise, x);
  if (promise === x) {
    // then方法需要异步,需要避免返回自己,循环引用
    // const { name, message } = new TypeError('Chaining cycle detected for promise #<MyPromise>');
    // return reject(`${name}: ${message}`);
    return reject(new TypeError('Chaining cycle detected for promise #<MyPromise>'));
  }

  if (['object', 'function'].includes(typeof x) && x !== null) {
    let then, called;
    try {
      // thenable get劫持
      then = x.then;
      if (typeof then === 'function') {
        then.call(
          x,
          y => {
            // 避免 thenable 多次调用
            if (called) return;
            called = true;
            console.log('onFulfilled=====> ', called, x);
            // y 可能还是 promise ,需要递归调用,解析出最终值
            resolvePromise(promise, y, resolve, reject);
          },
          r => {
            if (called) return;
            called = true;
            console.log('onRejected=====> ', called, x);
            reject(r);
          }
        );
      } else {
        resolve(x);
      }
    } catch (error) {
      /** 为什么要判断 called ?
       * 假设自定义 thenable 传递三个函数 y(resolvePromise) r(rejectPromise) z(anotherFn)
       * 如果调用 z 后,再调用 y || r 会被下一个 then 捕获, y || r 不会执行,自然没有问题
       * 如果调用 y || r 后调用了 z, y || r 执行后会被下一个then捕获,z也会执行报错也会 catch 被捕获,开始执行 reject
       * 当然上述只是优化逻辑,不限定对于结果没有任何影响(只为跑通测试) 因为 resolve || reject 以作出判断
       */
      // called 不用赋值,捕获错误后,后续代码不会再执行
      console.log('catch=====> ', called, error);
      if (called) return;
      reject(error);
    }
  } else {
    resolve(x);
  }
}

// 测试相关 promises-aplus-tests
MyPromise.deferred = function () {
  const result = {};
  result.promise = new MyPromise(function (resolve, reject) {
    result.resolve = resolve;
    result.reject = reject;
  });
  return result;
};

module.exports = MyPromise;
  • 场景考虑
js
const promise = new MyPromise((resolve, reject) => {
  resolve('success1');
  // reject('error1');
  // throw new Error('error1');
  // setTimeout(resolve, 0, 'success1');
});

const promise2 = promise.then(
  value => {
    console.log('then1 value=====> ', value);
    // return value + 2;
    // return promise2;
    // return null;
    // return new MyPromise(resolve => resolve(value + 2));
    // return { name: 'HJ' };
    // return new MyPromise((resolve, reject) => {
    //   resolve(
    //     new MyPromise((resolve, reject) => {
    //       resolve(value + '2');
    //     })
    //   );
    // });

    return new MyPromise((resolve, reject) => {
      resolve({
        name: 'HJ',
        get then() {
          // throw  'error'
          return (resolvePromise, rejectPromise, anotherFn) => {
            // throw 'error';
            // resolvePromise(value + 2);
            // resolvePromise(value + 3);
            // rejectPromise('error1');
            // anotherFn(123456);
            // setTimeout(() => {
            //   throw 'dsdfasd'; // 这里 throw 不会被promise 捕获
            //   resolvePromise(value + 2);
            // }, 5000);

            // 以下调用 rejectPromise ,返回 promise 实例 ===> 调用 reject 不会进行递归调用
            resolvePromise(
              new MyPromise((resolve, reject) => {
                // throw 'dsafdsfasd';
                setTimeout(reject, 1000, 456);
              })
            );
          };
        }
      });
    });
  },
  reason => {
    console.log('then1 reason=====> ', reason);
  }
);

// promise2.then(
//   value => {
//     console.log('then2 value=====> ', value);
//   },
//   reason => {
//     console.log('then2 reason=====> ', reason);
//   }
// );

promise2.catch(reason => {
  console.log(reason);
});

npm 相关理解

  • npm init

image-20221125151027144

sh
npm init ===> npm create
npx ===> npm exec

npm init vue ===> npm create vue ===> npm exec create-vue ===> npm x create-vue ===> npx create-vue
npm init vite ===> npm create vite ===> npm exec create-vite

@scope 就是可能这个包为 例如 npm init @config/vue 会去找 create-vue config 的最后一个版本 @config/create-vue@latest

npm init <@scope> (same as `npx <@scope>/create`) ===> npm init vue || npx vue/create
npm init [<@scope>/]<name> (same as `npx [<@scope>/]create-<name>`) ===> npm init vue/vue || npx vue/create-vue
  • npx
sh
npx tsc --version // 首先当前项目 node_modules  => 全局 => 网络 => 网络如果找不到 可以通过 -p 指定包来执行
npx -p typescript tsc --version
  • workspaces
sh
npm init -y -w packages/b --workspace=packages/b  # workspaces: ['packages/a', 'packages/b']
# npm init -y -w=./packages/a -w=./packages/b
npm init -y -w a -w b  # workspaces: { packages: ['a', 'b'] } 同上一样,只不过项目在当前根项目 a b c 而非上面的packages/a
npm init vite ./ -w packages/a # 以 packages/a作为项目,并初始化

npm init -y monorepo-demo
npm init -y -w ./packages/backend
npm i express -S # 在根中 安装express
npm i lodash -w a # 在子项目a 中安装lodash
npm i whistle -w b # 在子项目b 中安装whistle
npm run test -w a # 在当前工作区 运行子项目a中的命令 等同于 cd packages/a && npm run test


npm run test --workspace=a --workspace=b #在当前工作区同时运行子项目a b 中的命令 npm run start -w=a -w=b
npm run test --workspaces #  简写当前工作区同时运行子项目a b  npm run test -ws 当某个子项目没有 test 会对当前子项目报错提示 可通过npm run start -ws --if-present 进行跳过
  • 常用全局命令
sh
npm outdated -g # 检查全局包是否有更新
npm list -g --depth 0 # 查看全局安装包`
npm update package -g # 更新全局包
  • 常用技巧
sh
npm config --global --list # 获取全局配置
git config --global --unset http.proxy # 配置全局代理 --local 也行,注意影响他人
git config --global --unset http.sslverify # 禁止 ssl 校验

DOM

js
ul.childNodes; // 所有子节点
ul.children; // 所有子元素
ul.firstChild; // 第一个子节点
ul.firstElementChild; // 第一个子元素
ul.lastChild; // 最后一个子节点
ul.lastElementChild; // 最后一个子元素
li.previousSibling; // 前一个子节点
li.previousElementSibling; // 前一个子元素
li.nextSibling; // 后一个子节点
li.nextElementSibling; // 后一个子元素

jsonp

js
function jsonp(options) {
  const script = document.createElement('script');
  const fnName = 'myJsonp' + Math.random().toString().replace('.', '');
  let params = '';
  for (const attr in options.data) {
    params += '&' + attr + '=' + options.data[attr];
  }
  window[fnName] = options.success;
  script.src = options.url + '?callback=' + fnName + params;
  document.body.appendChild(script);
  script.onload = function () {
    document.body.removeChild(script);
  };
}

原型的深入理解

js
// 局部变量变全局
(function (win) {
  var num = 10;
  win.num = num;
  console.log('哈哈');
})(window);
console.log(num);

// 给 window 添加方法
(function (win) {
  function Random() {}
  Random.prototype.getRandom = function () {
    return Math.floor(Math.random() * 5);
  };
  // 构造函数传给 window 此时Random 成为内置方法,相当于 Array
  win.Random = Random;
})(window);
var num = new Random();
console.log(num.getRandom());
console.log(window);

(function (win) {
  function Random() {}
  Random.prototype.getRandom = function () {
    return Math.floor(Math.random() * 5);
  };
  // 实例对象传给 window
  win.Random = new Random();
})(window);
var num = Random;
console.log(num.getRandom());
console.log(window);
  • 随机小方块
js
// 产生随机数
(function (win) {
  function Random() {}
  Random.prototype.getRandom = function (min, max) {
    return Math.floor(Math.random() * (max - min) + min);
  };
  win.Random = new Random();
})(window);

// 创建小方块
(function () {
  var map = document.querySelector('.map');
  function Food(width, height, color) {
    this.width = width;
    this.height = height;
    this.color = color;
    this.x = 0;
    this.y = 0;
    this.element = document.createElement('div');
  }
  Food.prototype.init = function (map) {
    var div = this.element;
    div.style.position = 'absolute';
    div.style.width = this.width + 'px';
    div.style.height = this.height + 'px';
    div.style.backgroundColor = this.color;
    map.appendChild(div);
    this.render(map);
  };
  Food.prototype.render = function (map) {
    var x = Random.getRandom(0, map.offsetWidth / this.width) * this.width;
    var y = Random.getRandom(0, map.offsetHeight / this.height) * this.height;
    this.x = x;
    this.y = y;
    var div = this.element;
    div.style.left = this.x + 'px';
    div.style.top = this.y + 'px';
  };
  var Food = new Food(20, 20, 'red');
  Food.init(map);
  console.log(Food.x + '----' + Food.y);
})();

递归

js
// 普通求和
var num = 0;
function sum(n) {
  for (var i = 1; i <= n; i++) {
    num += i;
  }
  return num;
}

// 递归求和
function sumRecursion(n) {
  if (n == 1) {
    return 1;
  }
  return sumRecursion(n - 1) + n;
}

//  递归 斐波那契数列
function fib(n) {
  if (n <= 2) {
    return 1;
  }
  return fib(n - 1) + fib(n - 2);
}

module 相关

  • ESM
js
// a.js
function sum(num1, num2) {
  return num1 + num2;
}
const name = '小明';
const age = 28;
export { name, age, sum as default };
// b.js 非项目中不能省略 .js 后缀,以下两种写法都可以
import * as index from './b.js';
import sum, { name, age } from './index.js';
console.log(index);
// index.html script 标签中加上 module
<script src="./b.js" type="module"></script>;
  • commonjs

  • AMD(asynchronous module definition)

requirejs

Released under the MIT License | Copyright © 2022-present HOUJIAN