Giter VIP home page Giter VIP logo

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

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

website-2022's Issues

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

面试题复习

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>
  );
}

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

小程序引擎相关资料

小程序含义

小程序,开发者基于此开发出来的服务,在各类宿主环境(手机 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练习-第一个元素

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

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 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模式下演示并验证了上述结论.

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分支。

原型与原型链

前言

在前端这块领域,原型与原型链是每一个前端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练习-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 练习-实现 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,
}

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操作后)

继承

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 练习-元组转换为对象

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'}

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.