GithubHelp home page GithubHelp logo

xieyezi / website-2022 Goto Github PK

View Code? Open in Web Editor NEW
14.0 3.0 8.0 1.81 MB

my personal website 2022.

Home Page: https://xieyezi.github.io/

Shell 23.19% JavaScript 76.81%
blog vuepress vuepress-doc personal-blog

website-2022's Introduction

home heroImage heroText tagline actionText actionLink preactionText preactionLink
true
/logo.png
xieyezi
Hi,我是写夜子
🌈 Enter 🌈
/front-end/
✨ Project ✨

website-2022's People

Contributors

xieyezi avatar

Stargazers

Cat_Key avatar Liang avatar 小吴 avatar 剧终 avatar  avatar  avatar yzhou avatar  avatar  avatar JinmingYang avatar guangtouqiang avatar Dante avatar Yime Yan (imgradeone) avatar

Watchers

James Cloos avatar  avatar  avatar

website-2022's Issues

typescript练习-第一个元素

description:

实现一个通用First,它接受一个数组T并返回它的第一个元素的类型。

answer:

type First<T extends any[]> = T[0] extends T[number] ? T[0] :never

example:

type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]

type head1 = First<arr1> // expected to be 'a'
type head2 = First<arr2> // expected to be 3

TypeScript 练习

Partial

将将泛型中全部属性变为可选的属性

type Partial<T> = {
    [P in keyof T]?: T[P]
}

使用示例:

type People = {
    name: string
    age: number
}

type PartOfPeople = Partial<People>

Required

将将泛型中全部属性变为必选的属性

type Required<T> = {
    [P in keyof T]-?: T[P]
}

使用示例:

type People = {
    name?: string
    age: number
}

type PartOfPeople = Required<People>

Record<K,T>

此工具的作用是将 K 中所有属性值转化为 T 类型,我们常用它来申明一个普通 object 对象。

type Record<K extends keyof string | number | symbol, T> = {
    [key in K]: T
}

使用示例:

type PersonKey = string

type Key = 'name' | 'sex'

type Person = Record<Key,PersonKey>

const person: Person = {
  name: 'xieyezi',
  sex: 'man'
}

Pick<T, K>

此工具的作用是将 T 类型中的 K 键列表提取出来,生成新的子键值对类型。

type Pick<T, K extends keyof T> = {
    [P in K]: T[P]
}

使用示例:

type People = {
  age: number
  name: string
  sex: string
  birthdat: string
}

type PartPeople = Pick<People, "age" | "name">;

const partpeople: PartPeople = {
  name: "xieyezi",
  age: 18
}

Exclude<T, U>

此工具是在 T 类型中,去除 T 类型和 U 类型的交集,返回剩余的部分。

type Exclude<T, U> = T extends U ? never: T

使用示例:

type T1 = Exclude<"a" | "b" | "c", "a" | "b">;   // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number

react this.state 到底是异步还是同步?

setState是同步还是异步?

基于当前最新稳定版本v16.14.0进行分析

此处同步异步的定义

我们知道Promise.then(),setTimeout是异步执行. 从js执行来说, setState肯定是同步执行.

所以这里讨论的同步和异步并不是指setState是否异步执行, 而是指调用setState之后this.state能否立即更新.

分析

  1. 众所周知调用setState({item: 'new xxx'})之后, 会将传入setState的参数包装成一个update对象并添加到updateQueue队列中.
  2. 之后updateQueue队列在什么时机被合并到this.state中才是本题目的关键. 因为合并之后this.state必然就已经更新了.
  3. state的合并是在fiber构建循环中进行的, 而fiber构建循环必然是在触发scheduler调度之后进行. 关于这一点的详细论述可以参考react两大工作循环.

到这里问题转化为调用setState之后, 是否立即触发scheduler调度?

  • 如果立即进行scheduler调度, 那么this.state必然能同步获取.
  • 反之, 如果异步进行scheduler调度, 那么this.state不能同步获取.
  1. 每次调用setState都会进行一次scheduler调度(可以参考React 调度机制).
    在最新源码v16.14.0中体现为调用ensureRootIsScheduled. 在该源码中, 可以得到回答本题目的最佳答案, 核心逻辑如下:
 if (
      (executionContext & LegacyUnbatchedContext) !== NoContext &&
      (executionContext & (RenderContext | CommitContext)) === NoContext
    ) {
       // .... 省略部分本次讨论不会涉及的代码
    } else {
      ensureRootIsScheduled(root, eventTime); // 触发scheduler调度(调度是异步的) , 所以该函数不会立即触发render.
      if (executionContext === NoContext) {  // 当执行上下文为0时, 会刷新同步队列
         // .... 省略部分本次讨论不会涉及的代码

        // 这里是关键,  执行同步回调队列. 有兴趣的同学可以继续在源码中查看, 可以得到结论:
        // if分支之外的ensureRootIsScheduled(root, eventTime)和此处的flushSyncCallbackQueue()
        // 最终都是调用performSyncWorkOnRoot进行fiber树的循环构建
        flushSyncCallbackQueue(); 
      }
    }

结论

  1. 如楼上 @taichiyi 所述, setState是同步和异步最关键的因素是react内部的执行上下文executionContext的状态.
  • executionContext为空时, 表现为同步.
  • 反之executionContext不为空, 表现为异步.
  1. executionContext何时为空?

这个问题反过来更好理解, 什么时候executionContext不为空? 因为executionContext是react内部控制的属性, 当初次render, 合成事件触发时都会改变executionContext的值.

  1. 只要绕开react内部触发更改executionContext的逻辑, 就能保证executionContext为空, 进而实现setState为同步.
  • 可以使用异步调用如setTimeout, Promise, MessageChannel等
  • 可以监听原生事件, 注意不是合成事件(合成事件是react体系, 会更改executionContext), 在原生事件的回调函数中执行 setState 就是同步的

附加条件

以上分析都是基于legacy模式进行分析的, 众所周知react即将(可能)全面进入concurrent模式(可以参考react 启动模式). 在concurrent模式下, 这个题目可能就没有意义了, 因为从目前最新代码来看, 在concurrent模式下根本就不会判断executionContext, 所以concurrent模式下setState都为异步.

 // concurrent模式下根本没有下列代码, 所以不可能同步
if (executionContext === NoContext) { 
        flushSyncCallbackQueue(); 
      }

演示示例

对于此问题在codesandbox上面做了一个demo(详细演示示例). 在legacyconcurrent模式下演示并验证了上述结论.

原型与原型链

前言

在前端这块领域,原型与原型链是每一个前端er必须掌握的概念。我们多次在面试或者一些技术博客里面看见这个概念。由此可见,�这个玩意对于前端来说有多重要。其实它本身理解起来不难,但是很多刚入行前端的同学,看到prototype__proto__理解起来还是有点吃力,然后脑子里面就乱成一锅粥,就像我一样。但是这是很正常的事情,没什么大不了的,就像我们想要学会跑步,那么我们就必须先学会走路。任何事情都是有个过程的。所以现在就跟我一起来攻克这个难点吧。通过这篇文章你将掌握以下知识点:

  • 理解 __proto_;
  • 理解 prototype;
  • 理解javascript对象的概念;
  • 理解原型和原型链;
  • 理解javascript的概念;
  • 理解new的实现;
  • 理解instanceof的实现;
  • 理解javascript的继承;
  • 加深对javascript这门语言的理解。

这也是本篇文章的写作思路。

对象

那么我们就从对象这一概念开始说起,其实对象这一概念相信大家并不陌生。有一种说法是“javasrcript中万物皆是对象”,其实这个说法是错误的,一个很简单的例子,javasript中简单基本类型(string、boolean、number、null、undefined、symbol)本身就不是对象。其实javasript中对象主要分为函数对象普通对象。其中:

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error

这些都是函数对象,他们同时也被称为内置对象函数对象本身其实就是一个纯函数,javascript用他们来模拟。普通对象就很简单了,就是我们常见的对象:

const obj = {
    name: 'juefei',
    desc: 'cool'
}

可能说到这,你还是无法理解到底啥是函数对象,啥是普通对象,那我们就一起来看看下面的代码:

const obj1 = {};
const obj2 = new Object();
function func1() {

}
const obj3 = new func1();
const func2 = new function() {

}
const func3 = new Function()

接着我们来分别打印一下他们:

console.log(obj1);  // object
console.log(obj2);  // object
console.log(obj3);  // object
console.log(func1);  // function
console.log(func2);  // function
console.log(func3);  // function

所以可以看见,obj1obj2、,obj3是普通对象,他们都是Object的实例,而func1func2func3则都是Function的实例,称为函数对象。我们再看看:

console.log(typeof Object);  //f unction
console.log(typeof Function); // function

你是不是惊呆了,原来ObjectFunction都是 Function的实例。
所以我们得出一个结论就是:

  • 只要是Function的实例,那就是函数对象,其余则为普通对象

同样我们也可以看出,不仅 Object函数对象,就连 Function 本身也是函数对象,因为我们通过 console.log(typeof Function); 得知 FunctionFunction 的实例。是不是又开始有点绕了?没事,到这一步你就记住我们刚刚的结论就算完成目标:

  • 只要是Function的实例,那就是函数对象,其余则为普通对象

那么说到对象,我们从上面可以看出,一个对象是通过构造函数 new 出来的,这其实跟原型原型链有很大的关系,那么原型原型链到底是用来干嘛的呢?

原型

涉及到这两个概念,我们就必须先来介绍两个东西: __proto__prototype ,这两个变量可以说,在 javascript 这门语言里面随处可见,我们不管他三七二十一,我们先来看一张表:

对象类型 __proto__ prototype
普通对象
函数对象

所以,请你先记住以下结论:

  • 只有函数对象prototype 属性,普通对象 没有这个属性。

  • 函数对象普通对象 都有 __proto__这个属性。

  • prototype__proto__都是在创建一个函数或者对象会自动生成的属性。

接着我们来验证一下:

function func (){  //func称为构造函数

}
console.log( typeof func.prototype); // object
console.log(typeof func.__proto__);  // function
const obj = {}
console.log(typeof obj.__proto__) //object
console.log(typeof obj.prototype) //undefined (看见了吧,普通对象真的没有 prototype 属性)

所以就验证了我们刚刚的结论:

  • 只有函数对象prototype 属性,普通对象 没有这个属性
  • 函数对象普通对象 都有 __proto__这个属性。
  • prototype__proto__都是在创建一个函数或者对象会自动生成的属性。

你看我又重复写了一遍,我不是为了凑字数,是为了你加深记忆,这对于我们接下来的篇幅很重要。
接着我们来看看下面的代码:

console.log(obj.__proto__ === Object.prototype); // true
console.log(func.__proto__ === Function.prototype); // true

所以我们又得出如下结论:

  • 实例的 __proto__属性主动指向构造的 prototype;
  • prototype 属性被 __proto__ 属性 所指向。

这就是prototype 属性和 __proto__ 属性的区别与联系。
这可能又有点绕了,来多看几遍这一节,多背一下我们的结论。我们继续。
那么问题来了,既然func是一个函数对象,函数对象是有 prototype 属性的,那么func.prototype.__proto__等于啥呢?
为了解决这个问题,我们来思考一下:
首先,我们看看func.prototype 是啥:

 console.log(typeof func.prototype); //object

好,我们知道了,func.prototype 是一个对象,那既然是对象,那 func.prototype 那不就是 Object的实例吗?那也就是说,func.prototype.__proto__属性肯定是指向 Object.prototype 咯!
好,我们来验证一下:

 console.log(func.prototype.__proto__ === Object.prototype); //true

看见没有,就是这样的。那看到这里,我们应该也知道当我们这创建一个构造函数的时候,javascript是如何帮我们自动生成__proto__prototype属性的。哈哈没错就是这样:

//我们手动创建func函数
function func() {}
//javascript悄悄咪咪执行以下代码:
func._proto = Function.prototype; //实例的 __proto__ 属性主动指向构造的 prototype
func.prototype = {
    constructor: func,
    __proto: Object.prototype //我们刚刚才在上面验证的,你别又忘记了
}

我还专门为你画了个图(够贴心不老铁):

原型.png

所以prototype又被称为显式原型对象,而__proto__又被称为隐式原型对象。

hi,看到这里,你是不是有种脑子开了光的感觉。哈哈,所以到现在你应该已经理解原型的概念了,如果你还不理解,那就把上述章节再看一遍。最好拿个纸笔出来跟着画一画,顺便拿出电脑把示例代码敲一敲。好,整理一下头脑,接下来我们来看看什么又是原型链

原型链

再介绍这个概念之前,我们先来看如下代码:

function Person = function(name,desc){
    this.name = name;
    this.desc = desc;
} //***1****//
Person.prototype.getName = function(){
    return this.name;
}//***2****//
Person.prototype.getDesc = function(){
    return this.desc;
}//***3****//

const obj = new Person('juefei','cool');//***4****//
console.log(obj);//***5****//
console.log(obj.getName);//***6****//

接下来我们来逐步解析一下:

  1. 创建了一个构造函数 Person,此时,Person.portotype自动创建,其中包含了 constructor__proto__两个属性;
  2. 给对象 Person.prototype 新增了一个方法 getName;
  3. 给对象 Person.prototype 新增了一个方法 getDesc;
  4. 根据构造函数 Person 新建一个实例: obj(在创建实例的时候,构造函数会自动执行);
  5. 打印实例 obj :
{
    name: 'juefei',
    desc: 'cool'
}

根据上面一节的结论,我们得出:

obj.__proto__ = Person.prototype;
  1. 执行到第6步时,由于在实例 obj 上面找不到 getName()这个方法,所以它就会自动去通过自身的 __proto__ 继续向上查找,结果找到了 Person.prototype ,接着它发现,刚好 Person.prototype 上面有getName()方法,于是找到了这个方法,它就停止了寻找。
    怎么样,是不是有一种环环相扣的感觉?他们形成一个链了,没错,这就是原型链

我们得出如下结论:
在访问一个对象(假设这个对象叫obj)的属性/方法时,若在当前的对象上面找不到,则会尝试通过obj.__proto__去寻找,而 obj.__proto__ 又指向其构造函数(假设叫objCreated)的 prototype,所以它又自动去 objCreated.prototype 的属性/方法上面去找,结果还是没找到,那么就访问 objCreated.prototype.__proto__继续往上面寻找,直到找到,则停止对原型链对寻找,若最终还是没能找到,则返回 undefined
一直沿着原型链寻找下去,直到找到 Object.prototype.__proto__,指向 null,于是返回 undefined了。

是不是自然而然就理解了。我又给你画了个图(请对照着上面👆那个图看):

原型链.png

接下来我们再来增加一些概念:

  1. 任何内置函数对象本身的 __proto__属性都指向 Function的原型对象,即: Function.prototype;
  2. 除了 Object.prototype.__proto__指向 null ,所有的内置函数对象的原型对象的 __proto__属性 ( 内置函数对象.prototype.__proto__),都指向Object

我们得出如下终极原型链的图:
原型链终极图.png

针对这个图,我最终给出我们经常看见那个原型链的图:

prototype.jpg

好好对比一下,拿出纸和笔画一画,根据上面章节的讲解,相信你很容易就能明白。

javascript中的

刚刚我们终于明白什么是 原型原型链。下面我们根据上面的概念来讲解一下javascript中的
我们知道,在面向对象的语言中,类可以被实例化多次,这个实例化是指我们可以根据构造函数去独立复制多个独立的实例,这些实例之间是独立的。但是实际上在 javascript 却不是这样的,因为它不是这种复制机制。我们不能创建一个类的多个实例,我们只能创建这个类的多个对象,因为他们都是通过原型原型链关联到同一个对象。所以在 javascript 中 ,都是通过原型原型链来实现的,它其实是一种委托方式

new的实现

了解了上面javascript中的的概念,那我们应该很容易就理解new的过程,其核心无非就是执行原型链的链接:

function myNew(Cons,...args){
    let obj = {};
    obj.__proto__ = Cons.prototype; //执行原型链接
    let res = Cons.call(obj,args);
    return typeof res === 'object' ? res : obj;
}

instanceof的实现

那么学习了原型原型链instanceof的实现肯定也很简单了,它也是通过原型原型链来实现的:

  function myInstanceof(left,right){
      let rightProto = right.prototype;
      let leftValue = left.__proto__;
      while(true){
          if(leftValue === null){
              return false;
          }
          if(leftValue === rightProto){
              return true;
          }
          leftValue = leftValue.__proto__;
      }
  }

我就不讲解过程了,因为我知道你肯定能看懂,哈哈。

javascript的继承

我们都知道继承也是通过原型原型链来实现的,那我在这里介绍两种常见的继承方式:

  1. 组合继承:
//组合式继承
//通过call继承Parent的属性,并传入参数
//将Child的原型对象指向Parent的实例,从而继承Parent的函数
 function Parent(value){
     this.val = value;
 }
 Parent.prototype.getValue = function(){
     console.log(this.val);
 }
 function Child(value){
     Parent.call(this,value);//继承Parentd的属性
 }
 Child.prototype = new Parent();
  1. 寄生组合式继承:
//寄生组合式继承
//通过call继承Parent的属性,并传入参数
//通过Object.create()继承Parent的函数
  function Parent(value){
      this.val = value;
  }
  Parent.prototype.getValue = function(){
      console.log(this.val);
  }
  function Child(value){
      //继承Parentd的属性
      Parent.call(this,value);
  }
  Child.prototype = Object.create(Parent.prototype,{
      constructor:{
          value:Child,
          writable:true,
          configurable:true,
          enumerable:false
      }
  })

总结

  1. 若 A 通过 new 创建了 B,则 B.__proto__ = A.prototype
  2. 执行B.a,若在B中找不到a,则会在B.__proto__中,也就是A.prototype中查找,若A.prototype中仍然没有,则会继续向上查找,最终,一定会找到Object.prototype,倘若还找不到,因为Object.prototype.__proto__指向null,因此会返回undefined
  3. 原型链的顶端,一定有 Object.prototype.__proto__ ——> null。

由此可见,原型原型链是如此的强大,希望看完这篇文章,你不再会对他们感到恐惧。

写完这篇已经近凌晨两点,如果你觉得这篇文章对你有些许收获,请点赞支持!!

参考资料:
<< 你不知道的javascript 上卷 >>

typescript 练习-元组转换为对象

description:

给定数组,转换为对象类型,键/值必须在给定数组中。

answer:

type TupleToObject<T extends readonly any[]> = {
  [P in T[number]]: P
}

example:

const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const

const result: TupleToObject<typeof tuple> // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}

继承

1.原型链继承

function Parent() {
  this.name = 'xieyezi';
}

Parent.prototype.getName = function() {
  console.log(this.name);
}

function Child() {

}

Child.prototype = new Parent();

var child = new Child();

console.log(child.getName()); // xieyezi

缺点:
1.引用类型的属性会被所有实例共享,例如:

function Parent () {
    this.names = ['xieyezi'];
}

function Child () {

}

Child.prototype = new Parent();

var child1 = new Child();

child1.names.push('juefei');

console.log(child1.names); // ["xieyezi", "juefei"]

var child2 = new Child();

console.log(child2.names); // ["xieyezi", "juefei"]

2.在创建Child实例时,不能向Parent传参

2.借用构造函数(经典继承)

function Parent () {
    this.names = ['xieyezi'];
}

function Child() {
  Parent.call(this);
}

var child1 = new Child();

child1.names.push('juefei');

console.log(child1.names); // ["xieyezi", "juefei"]

var child2 = new Child('babad');

console.log(child2.names); // ["xieyezi"]

优点:
1.避免了引用类型的属性被所有实例共享;
2.可以在Child中向Parent传参,例如:

function Parent (name) {
    this.name = name;
}

function Child (name) {
    Parent.call(this, name);
}

var child1 = new Child('xieyezi');

console.log(child1.name); // xieyezi

var child2 = new Child('juefei');

console.log(child2.name); // juefei

缺点:
方法都在构造函数中定义,每次创建实例都会创建一遍方法。

3.组合继承

原型链继承和组合继承双剑合璧。

function Parent(name) {
  this.name = name;
  this.colors = ['red','blue','green']
}

Parent.prototype.getName = function() {
  console.log(this.name);
}

function Child(name,age) {
  Parent.call(this,name);
  this.age = age;
}

Child.prototype = new Parent();
Child.prototype.constructor = Child;

var child1 = new Child('xieyezi', '25');

child1.colors.push('black');

console.log(child1.name); // xieyezi
console.log(child1.age); // 25
console.log(child1.colors); // ["red", "blue", "green", "black"]

var child2 = new Child('juefei', '24');

console.log(child2.name); // juefei
console.log(child2.age); // 24
console.log(child2.colors); // ["red", "blue", "green"]

优点:融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。
缺点:Parent构造函数会调用两次

4.原型式继承

function createObj(o) {
  function F(){}
  F.prototype = o;
  return new F();
}

var person = {
    name: 'kevin',
    friends: ['daisy', 'kelly']
}

var person1 = createObj(person);
var person2 = createObj(person);

person1.name = 'person1';
console.log(person2.name); // kevin

person1.firends.push('taylor');
console.log(person2.friends); // ["daisy", "kelly", "taylor"]

就是 ES5 Object.create 的模拟实现,将传入的对象作为创建的对象的原型。

缺点:
包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。

5. 寄生式继承

function createObj(o) {
  var clone = Object.create(o);
  clone.sayName = function () {
    console.log('hi');
  }
  return clone
}

创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。

缺点:跟借用构造函数模式一样,每次创建对象都会创建一遍方法。

6. 寄生组合式继承

function object(o) {
  function F(){}
  F.prototype = o;
  return new F();
}

function Parent(value){
    this.val = value;
}

Parent.prototype.getValue = function(){
    console.log(this.val);
}

function Child(value){
    Parent.call(this,value);
}

Child.prototype = object(Parent.prototype)
// 上面这句等价于 `Child.prototype = Object.create(Parent.prototype)`

相比于组合继承,寄生组合式继承调用两次父构造函数的缺点。

typescript 练习-实现 Pick

type Pick<T,  K extends keyof T> = {
  [P in K]: T[P]
}

example:

interface Todo {
  title: string
  description: string
  completed: boolean
}

type TodoPreview = Pick<Todo, 'title' | 'completed'>

const todo: TodoPreview = {
    title: 'Clean room',
    completed: false,
}

小程序引擎相关资料

小程序含义

小程序,开发者基于此开发出来的服务,在各类宿主环境(手机 App、车载系统、IOT 设备等)中,可做到用户无感知安装,即点即用。

双线程模型

  • 采用 视图层(View) + 逻辑层(AppService) 的方式实现双线程模型,逻辑层作为一个单独的线程执行 js 代码,控制小程序数据的生成和处理;渲染层使用了 WebView 线程,处理页面的渲染和用户的事件交互行为。

  • 视图层和逻辑层通过系统层的 JSBridage 进行通信,逻辑层把数据变化通知到视图层,触发视图层页面更新,视图层把触发的事件通知到逻辑层进行业务处理。

WX20220120-163834@2x

WX20220122-093437@2x

  • 视图层(View)

    视图层使用 WebView 渲染,iOS 中使用自带 WKWebView,在 Android 使用 Mobile Chrome 内核的 XWeb 引擎 运行。

  • 逻辑层(AppService)

    逻辑层使用在 iOS 中使用自带的 JavaScriptCore 运行,在 Android 中使用 v8 内核 运行。

  • 在 开发工具上,小程序逻辑层的 javascript 代码是运行在 NW.js 中,视图层是由 Chromium Webview 来渲染的。

WX20220120-173919@2x

运行逻辑

  • 逻辑层

    • 逻辑层就是对开发者所暴露的Api,有App,Page,布局文件,其中的App,Page都是两个函数
    • App()函数的处理:直接创建App对象,全局唯一对象
    • Page()函数的处理:保存到Map中,不会马上构建Page对象,当导航到页面时,才会真正创建Page对象
  • 渲染层

    • 使用MVVM框架vue来渲染界面
    • 在编译期间把小程序标签转化为vue框架所支持的标签
    • 为每个小程序页面,创建对应的vue框架下Page组件,PageComponent的template就是page.vxml转译后的内容
  • 渲染层与逻辑层交互

    • 渲染层接收用户的交互事件,由统一的函数处理后,通过消息总线传递到逻辑层的Page对象,再调用对应的函数
    • 逻辑层依据用户操作,执行业务操作,修改data数据,通过消息总线传递到渲染层的组件里,San.Page组件会自动更新界面

程序结构

app配置

小程序包含一个描述整体程序的 app 和多个描述各自页面的 page:
一个小程序主体部分由三个文件组成,必须放在项目的根目录,如下:

文件 必须 作用
app.js 小程序逻辑
app.config.json 小程序公共配置
app.vcss 小程序公共样式表

页面

一个小程序页面由四个文件组成,如下:

文件 必须 作用
js 页面逻辑
json 页面配置
vcss 页面样式表
vxml 页面结构

注意:为了方便开发者减少配置项,描述页面的四个文件必须具有相同的路径与文件名

例如:

├── app.js
├── app.config.json
├── app.vcss
├── pages
│   │── index
│   │   ├── index.vxml
│   │   ├── index.js
│   │   ├── index.json
│   │   └── index.vcss
│   └── my
│       ├── my.vxml
│       └── my.js

typescript 练习-实现 Readonly

type MyReadonly<T> = {
  readonly [P in keyof T]:  T[P]
}

example:

interface Todo {
  title: string
  description: string
}

const todo: MyReadonly<Todo> = {
  title: "Hey",
  description: "foobar"
}

todo.title = "Hello" // Error: cannot reassign a readonly property
todo.description = "barFoo" // Error: cannot reassign a readonly property

typescript练习-Exclude

description:

实现内置的Exclude <T,U>
answer:

type MyExclude<T, U> = T extends U ? never: T

example:

 Expect<Equal<MyExclude<"a" | "b" | "c", "a">, Exclude<"a" | "b" | "c", "a">>>,
 Expect<Equal<MyExclude<"a" | "b" | "c", "a" | "b">, Exclude<"a" | "b" | "c", "a" | "b">>>,
 Expect<Equal<MyExclude<string | number | (() => void), Function>, Exclude<string | number | (() => void), Function>>>,

typescript练习-获取元组长度

description:

对于给定的元组,您需要创建一个通用的Length,选择元组的长度。

answer:

type Length<T extends any> = T extends {length: number} ? T['length'] : never;

example:

type tesla = ['tesla', 'model 3', 'model X', 'model Y']
type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT']

type teslaLength = Length<tesla>  // expected 4
type spaceXLength = Length<spaceX> // expected 5

typescript练习-Awaited

description:

根据Promise推断返回值。

answer:

type Awaited<T> = T extends Promise<infer R> ? R :never

example:

 type cases = [
  Expect<Equal<Awaited<X>, string>>,
  Expect<Equal<Awaited<Y>, { field: number }>>,
]

react源码解析

1、https://pomb.us/build-your-own-react/
2、https://github.com/BetaSu/just-react
一、前置知识
react 16版本的架构:
Scheduler(调度器)—— 调度任务的优先级,高优任务优先进入Reconciler
Reconciler(协调器)—— 负责找出变化的组件
Renderer(渲染器)—— 负责将变化的组件渲染到页面上
Scheduler(调度器)
主要是借鉴了requestIdleCallback API,更新工作从15版本递归变成了可以中断的循环过程。
Reconciler(协调器)
jsx转换为虚拟dom,根据标记更新虚拟dom,通知Renderer(渲染器)
Renderer(渲染器)
Renderer根据Reconciler虚拟DOM打的标记,同步执行对应的DOM操作。
整个SchedulerReconciler的工作都在内存中进行。只有当所有组件都完成Reconciler的工作,才会统一交给Renderer
jsx和虚拟dom
在组件mount时,Reconciler根据JSX描述的组件内容生成组件对应的虚拟DOM。在update时,ReconcilerJSX虚拟DOM保存的数据对比,为对比后状态有变化的虚拟DOM打上标记。
一、render阶段
react采用双缓存技术
React中最多会同时存在两棵Fiber树。当前屏幕上显示内容对应的Fiber树称为current Fiber树,正在内存中构建的Fiber树称为workInProgress Fiber树。
利用深度优先的**-在mountupdate阶段创建fiber
render 创建fiber树阶段:利用requestIdleCallback**实现可中断的递归
render阶段的工作是在内存中进行,当工作结束后会通知Renderer需要执行的DOM操作。要执行DOM操作的具体类型就保存在fiber.effectTag中:

// DOM需要插入到页面中
export const Placement = /*                */ 0b00000000000010;
// DOM需要更新
export const Update = /*                   */ 0b00000000000100;
// DOM需要插入到页面中并更新
export const PlacementAndUpdate = /*       */ 0b00000000000110;
// DOM需要删除
export const Deletion = /*                 */ 0b00000000001000;

dom操作根据effectTag的标记进行对应的操作
commit阶段无须再次遍历整棵fiber树,因为在创建effectTag的阶段,在rootFiber.firstEffect上保存了一个单向链表:effectList 将每一次的effectTag保存起来了,在commit阶段只需根据链表来进行更新即可。
二、commit阶段
commit 阶段为同步执行
生命周期钩子(比如componentDidXXX)、hook(比如useEffect)需要在commit阶段执行
Renderer工作的阶段被称为commit阶段。commit阶段可以分为三个子阶段:
• before mutation阶段(执行DOM操作前)
• mutation阶段(执行DOM操作)
• layout阶段(执行DOM操作后)

面试题复习

import "./styles.css";
import { useEffect } from "react";

// call 实现
// eslint-disable-next-line no-extend-native
Function.prototype.Mycall = function (context) {
  var context = context || window;
  context.fn = this;
  var args = [...arguments].slice(1);
  var result = context.fn(...args);
  delete context.fn;
  return result;
};

// apply 实现
// eslint-disable-next-line no-extend-native
Function.prototype.MyApply = function (context) {
  var context = context || window;
  context.fn = this;
  var args = [...arguments][1];
  var result = null;
  if (args) {
    result = context.fn(...args);
  } else {
    result = context.fn();
  }
  delete context.fn;
  return result;
};

// bind实现
// eslint-disable-next-line no-extend-native
Function.prototype.MyBind = function (context) {
  var self = this;
  let args = [...arguments].slice(1);
  return function () {
    let bindArgs = [...arguments].slice(0);
    let allArgs = args.concat(bindArgs);
    return self.apply(context, allArgs);
  };
};

// new 实现
function myNew() {
  let obj = {};
  let Cons = arguments[0];
  let args = [...arguments].slice(1);
  // console.log(Cons);
  // console.log(args);
  obj.__proto__ = Cons.prototype;
  let res = Cons.call(obj, ...args);
  return typeof res === "object" ? res : obj;
}

// 寻找数组中第k大的数
function findK(nums, k) {
  // 1.全排列,复杂度,O(nlogN)
  // nums = nums.sort((a, b) => b - a);
  // return nums[k - 1];
  // 2. 冒泡排序,复杂度,n*k
  for (let i = 0; i < k; i++) {
    for (let j = 0; j < nums.length - 1; j++) {
      if (nums[j] > nums[j + 1]) {
        [nums[j], nums[j + 1]] = [nums[j + 1], nums[j]];
      }
    }
  }
  return nums[nums.length - k];
}

//形式1:sum(1).sumof()
// 形式2:sum(1,2).sumof()
// 形式3:sum(1)(2, 3).sumof()
// 最终求得的结果,需要是各个出现的数字的和。

function sum(...lastArgs) {
  console.log("lastArgs", lastArgs);
  var callback = function (...args) {
    console.log("args", args);
    return sum(...[...lastArgs, ...args]);
  };
  callback.sumof = function () {
    return lastArgs.reduce((aggregated, number) => aggregated + number, 0);
  };
  return callback;
}


// 防抖实现 debunce
function debunce(fn,delay){
  let timer; 
  return function(...args) {
    if(timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      fn.apply(this,args)
    }, delay);
  }
}

// 节流实现
function throotle(fn,delay){
  let timer
  return function(...args) {
    if(!timer) {
      timer = setTimeout(()=>{
        timer = null
        fn.apply(this,args)
      },delay)
    }
  }
}

export default function App() {
  let foo = {
    value: 1
  };

  function bar(name, age) {
    console.log(name);
    console.log(age);
    console.log(this.value);
  }

  function Person(name, age) {
    this.name = name;
    this.age = age;
  }
  Person.prototype.sayName = function () {
    console.log(this.name + "hhh");
  };

  useEffect(() => {
    // bar.Mycall(foo, "xieyezi", 25);
    // bar.MyApply(foo, ["xieyezi", 25]);
    // let bindRes = bar.bind(foo, "xieyezi");
    // bindRes(19);
    // let a = myNew(Person, "xieyezi", 25);
    // console.log(a.sayName());
    // let k = findK([3, 2, 1, 5, 6, 4], 2);
    // console.log(k);
    let total = sum(1)(2, 3).sumof();
    console.log(total);
  }, []);

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

javascript 的一些技巧

1.原型链继承

function Parent() {
  this.name = 'xieyezi';
}

Parent.prototype.getName = function() {
  console.log(this.name);
}

function Child() {

}

Child.prototype = new Parent();

var child = new Child();

console.log(child.getName()); // xieyezi

缺点:
1.引用类型的属性会被所有实例共享,例如:

function Parent () {
    this.names = ['xieyezi'];
}

function Child () {

}

Child.prototype = new Parent();

var child1 = new Child();

child1.names.push('juefei');

console.log(child1.names); // ["xieyezi", "juefei"]

var child2 = new Child();

console.log(child2.names); // ["xieyezi", "juefei"]

2.在创建Child实例时,不能向Parent传参

2.借用构造函数(经典继承)

function Parent () {
    this.names = ['xieyezi'];
}

function Child() {
  Parent.call(this);
}

var child1 = new Child();

child1.names.push('juefei');

console.log(child1.names); // ["xieyezi", "juefei"]

var child2 = new Child('babad');

console.log(child2.names); // ["xieyezi"]

优点:
1.避免了引用类型的属性被所有实例共享;
2.可以在Child中向Parent传参,例如:

function Parent (name) {
    this.name = name;
}

function Child (name) {
    Parent.call(this, name);
}

var child1 = new Child('xieyezi');

console.log(child1.name); // xieyezi

var child2 = new Child('juefei');

console.log(child2.name); // juefei

缺点:
方法都在构造函数中定义,每次创建实例都会创建一遍方法。

3.组合继承

原型链继承和组合继承双剑合璧。

function Parent(name) {
  this.name = name;
  this.colors = ['red','blue','green']
}

Parent.prototype.getName = function() {
  console.log(this.name);
}

function Child(name,age) {
  Parent.call(this,name);
  this.age = age;
}

Child.prototype = new Parent();
Child.prototype.constructor = Child;

var child1 = new Child('xieyezi', '25');

child1.colors.push('black');

console.log(child1.name); // xieyezi
console.log(child1.age); // 25
console.log(child1.colors); // ["red", "blue", "green", "black"]

var child2 = new Child('juefei', '24');

console.log(child2.name); // juefei
console.log(child2.age); // 24
console.log(child2.colors); // ["red", "blue", "green"]

优点:融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。
缺点:Parent构造函数会调用两次

4.原型式继承

function createObj(o) {
  function F(){}
  F.prototype = o;
  return new F();
}

var person = {
    name: 'kevin',
    friends: ['daisy', 'kelly']
}

var person1 = createObj(person);
var person2 = createObj(person);

person1.name = 'person1';
console.log(person2.name); // kevin

person1.firends.push('taylor');
console.log(person2.friends); // ["daisy", "kelly", "taylor"]

就是 ES5 Object.create 的模拟实现,将传入的对象作为创建的对象的原型。

缺点:
包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。

5. 寄生式继承

function createObj(o) {
  var clone = Object.create(o);
  clone.sayName = function () {
    console.log('hi');
  }
  return clone
}

创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。

缺点:跟借用构造函数模式一样,每次创建对象都会创建一遍方法。

6. 寄生组合式继承

function object(o) {
  function F(){}
  F.prototype = o;
  return new F();
}

function Parent(value){
    this.val = value;
}

Parent.prototype.getValue = function(){
    console.log(this.val);
}

function Child(value){
    Parent.call(this,value);
}

Child.prototype = object(Parent.prototype)
// 上面这句等价于 `Child.prototype = Object.create(Parent.prototype)`

相比于组合继承,寄生组合式继承调用两次父构造函数的缺点。

git-flow流程解析

Gitflow是一个基于feature分支管理的版本发布方案。它是由荷兰程序猿Vincent Driessen设计研发,开源项目地址gitflow-avh。
大致流程是:

  • 不同feature在不同feature分支上单独开发(或测试)。
  • 确定版本号和此版本将要发布的功能后,将相应featustre分支统一向develop分支合并,然后拉出新的release预发布分支。
  • release分支作为持续集成关注的分支,修复bug。
  • 待release分支测试验收通过后,统一向master分支和develop分支合并,并在master分支打tag。
  • 根据tag发布apk版本。

若线上发现严重bug,需走hotfix流程。

  • 基于master分支拉出hotfix分支修复线上问题。
  • bug修复完成统一向master和develop分支合并。
  • master分支打上新的tag,发布新版本。

下面将介绍如何使用Gitflow命令完成上述版本发布,一条Gitflow指令可能对应了一系列git命令,为的是规范化开发流程,提高代码管理效率。

Mac平台安装

brew install git-flow

brew表示Homebrew,它是mac平台常用的包管理器,没有安装则需先安装,安装可参考Mac OS下brew的安装和使用。

初始化

先将远程仓库克隆到本地。

git clone <project_url>

各种初始化Gitflow配置。

git flow init

命令行会提示你是否修改Gitflow提供的默认分支前缀。不同场景的分支前缀不同,默认情况下分支前缀是这样的:

场景 分支前缀
新功能 feature
预发布 release
热修复 hotfix
打tag

分支前缀的作用是区分不同分支的使用场景,同时当你使用Gitflow命令时就不需手动添加分支前缀了,Gitflow会帮你加上。
比如开发新功能需创建一个feature分支,名为gitworkflow,使用下面的代码将会创建一个名为feature/gitworkflow本地分支。

git flow feature start gitworkflow

通常情况下不需要修改默认的命名前缀,只需加上-d就可跳过修改命名阶段。

git flow init -d

行为/Action

通常来说,一种场景的完整生命周期应至少包含以下几种行为:

  • start 开始开发
  • publish 发布到远程分支
  • finish 完成开发、合并到主分支

我们首先以feature场景为例,看看如何完成工作流。

feature流程

start

新功能开始开发前,需准备好开发分支。

git flow feature start <feature_name>

执行上面的命令将会在本地创建名为<feature_name>的分支,并切换到该分支,而且不论当前所处哪个分支都是基于develop分支创建,相当于执行了下面的git的命令。

git checkout -b feature/<feature_name> develop

需要注意基于的是本地的develop分支,执行此命令前一般需要拉取最新的远程代码。

publish

在本地开发完成新功能并commit后,需要将本地代码push到远程仓库。

git flow feature publish <feature_name>

这行指令做了三件事。

  • 创建一个名为feature/<feature_name>的远程分支。
  • 本地分支track上述远程分支。
  • 如果本地有未push代码,则执行push。

转换成git命令就是下面的样子:

git push origin feature/<feature_name>
git push --set-upstream origin feature/<feature_name>
git push origin

注意:如果已经执行过publish后又有新的代码需push,再次执行将报错,因为它始终会试图创建一个远程分支。此时需执行正常的push命令git push origin。

finish

当功能开发完毕后就将进入测试阶段,此时需将一个或多个feature分支统一合并到develop分支。

git flow feature finish <feature_name>

这行指令也做了三件事。

  • 切换到develop分支。
  • 合并代码到develop分支
  • 删除本地feature/<feature_name>分支。
    等价于执行下面的git命令:
git checkout develop
git merge feature/<feature_name>
git branch -d feature/<feature_name>

如果merge时发生了冲突,则在第二步merge时终止流程,即不会再删除本地分支。但当前已处于develop分支,待本地冲突解决并commit后,重新执行git flow feature finish <feature_name>即可完成finish流程。
细心的同学可以已经发现finish还有两件事没做。

  • develop分支代码还未push。
  • 未删除远程分支feature/<feature_name>。

也就是还需执行

git push origin develop
git push origin :feature/<feature_name>

release流程

当新功能开发完毕,将进入测试阶段,此时需要基于develop分支拉出release分支进行集成测试,也有将release场景作为预发布环境进行测试的,即feature场景已完成常规测试,在这种情况下,一般而言release只有少数改动。在这里我们先不讨论项目流程问题。
使用start指令开启一个release场景,通常以版本号命令,我们以v2.0为例:

git flow release start v2.0

此命令会基于本地的develop分支创建一个release/v2.0分支,并切换到这个分支上。
为了让其他协同人员也能看到此分支,需要将其发布出去。

git flow release publish v2.0

以上和feature场景十分类似。
待测试通过需要发布正式版:

git flow release finish v2.0

这一步做的动作有点多,大致是:

  • git fetch
  • release/v2.0分支代码向master合并。
  • 生成名为v2.0的tag。
  • release/v2.0分支代码向develop合并。
  • 删除本地release/v2.0分支。
  • 切换回develop分支。

如果merge产生冲突不会终止流程,只是不会将本地的release分支删除,待解决完冲突后需再次执行finish指令。
另外需要注意的是,如果本地还有未finish的release分支,将不允许使用start指令开启新的release分支,这一点是对并行发布的一个限制。
release finish只是完成了本地代码的一系列操作,还需要同步到远程仓库。

git push origin develop
git push origin master
git push origin v2.0

或者使用下面的命令推送所有的分支和tag。

git push origin --all
git push origin --tags

hotfix 流程

当tag打完,也表示正式版本发布出去了,如果此时在线上发现了严重的bug,需要进行紧急修复。
此时我们假设版本号变为v2.0-patch。

git flow hotfix start v2.0-patch

这将创建一个hotfix/v2.0本地分支并切换到该分支。 hotfix没有publish指令,认为hotfix应该是个小范围改动,不需要其他协同人员参与。
待本地修改结束commit后,执行finish指令。

git flow hotfix finish v2.0-patch

按照Gitflow工作流,它会执行下面的任务,与release基本一致。

  • git fetch
  • hotfix/v2.0-patch分支代码向master合并。
  • 生成名为v2.0-patch的tag。
  • hotfix/v2.0-patch分支代码向develop合并。
  • 删除本地hotfix/v2.0-patch分支。
  • 切换回develop分支。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.