分类
ES6

JavaScript 中你应该知道的 旧习惯 与 新习惯(二)

创建对象时使用可计算属性名

旧习惯

  • 之前,如果属性名是个变量或者需要动态计算,则只能通过 对象.[变量名] 的方式去访问,且只能运行时才能确定
var name = 'answer';   
var obj = { };
obj[name]=42;
console.log(obj[name]) // 42

新习惯

var name = 'answer';   
var obj = { 
[name]:42
};
console.log(obj[name]) // 42

同名变量初始化对象时,使用简写语法

旧习惯:哪怕变量是局部变量,也要写完整key与value

function getParams(){
	let name;
	// 其他操作
	return {name:name}
}

新习惯:使用简写

function getParams(){
	let name;
	// 其他操作
	return {name}
}

使用 *Object.assign()*代替自定义的扩展方法或者显式复制所有属性

旧习惯 使用遍历key然后复制给新的对象

function mergeObjects() {
    var resObj = {};
    for(var i=0; i < arguments.length; i += 1) {
         var obj = arguments[i],
             keys = Object.keys(obj);
         for(var j=0; j < keys.length; j += 1) {
             resObj[keys[j]] = obj[keys[j]];
         }
    }
    return resObj;
}

新习惯 使用Object.assign代替

const target = { a: 1, b: 2 };

const returnedTarget = Object.assign({}, source);

console.log(returnedTarget); // {a:1,b:2}

使用属性扩展语法

旧习惯 基于已有对象创建新对象时,使用 Object.assign

const target = { a: 1, b: 2 };
const source = { b: 3, c: 4 };

const returnedTarget = Object.assign({},target, source);

console.log(returnedTarget); // {a:1,b:3,c:4}

新习惯 使用属性扩展语法

const a = { a: 1, b: 2 };
const b = { b: 3, c: 4 };

const returnedTarget = {...a,...b}; // {a: 1, b: 3, c: 4}

使用 Symbol 避免属性名冲突

旧习惯 使用可读性很差的又很长的字符串来用作属性名来避免冲突

const shapeType = {
  triangle: 'Triangle'
};

function getArea(shape, options) {
  let area = 0;
  switch (shape) {
    case shapeType.triangle:
      area = .5 * options.width * options.height;
      break;
  }
  return area;
}

getArea(shapeType.triangle, { width: 100, height: 100 });

新习惯

const shapeType = {
  triangle: Symbol()
};

function getArea(shape, options) {
  let area = 0;
  switch (shape) {
    case shapeType.triangle:
      area = .5 * options.width * options.height;
      break;
  }
  return area;
}

getArea(shapeType.triangle, { width: 100, height: 100 });

使用 Object.getPrototypeOf/setPrototypeOf 来代替 Proto

旧习惯 使用非标准的 Proto 来获取或者设置原型

新习惯 使用标准方法 Object.getPrototypeOf/setPrototypeOf 来获取或者设置原型

具体查看 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/proto

使用对象方法的简写语法来定义对象中的方法

旧习惯 key 与 value 的形式

const obj = {
    fun : function(){
        // ...
    }
}

新习惯

const obj = {
    fun(){
        // ...
    }
}

遍历的姿势-可迭代对象

旧习惯 使用for循环或者 forEach 方法 来循环

// for
for(let i = 0; i < array.length; ++i){
    console.log(array[i]);
}
// forEach
array.forEach(entry => console.log(entry));

新习惯 当不需要下标的时候,考虑for-of

let arr = [1, 2, 3, 4]
for(let item of arr) {
  console.log(item) // 1, 2, 3, 4 
}

需要下标可以考虑
let arr = [1, 2, 3, 4]
for(let [index, value] of arr.entries()) {
  arr[index] = value * 10
}
console.log(arr) // [ 10, 20, 30, 40 ]

遍历的姿势-DOM

旧习惯 为了遍历DOM集合,将DOM集合转为数组或者使用Array.prototype.forEach.call 方法

Array.prototype.slice.call(document.querySelectorAll("div")).forEach(
    (div)=>//....
)
    
Array.prototype.forEach.call(document.querySelectorAll("div"),div=>{
    // ...
})

新习惯 确保DOM集合是可迭代的,然后使用for-of

for(const div of document.querySelectorAll("div"){
    // ...
}

在使用 Function.prototype.apply() 的大部分场景就可以使用可迭代对象的展开语法

旧习惯

const numbers = [5, 6, 2, 3, 7];

const max = Math.max.apply(null, numbers);

console.log(max);
// expected output: 7

const min = Math.min.apply(null, numbers);

console.log(min);
// expected output: 2

新习惯 使用可迭代对象的展开语法

const numbers = [5, 6, 2, 3, 7];

const max = Math.max([...numbers]);

console.log(max);
// expected output: 7

const min = Math.min([...numbers]);

console.log(min);
// expected output: 2

该用解构用解构

旧习惯 使用对象中的少量属性就把整个对象保存

const bird = getBird();
console.log(bird.age);
console.log(bird.name);

新习惯 使用解构赋值,不需要整个对象的时候

let {age,name} = getBird();
console.log(age);
console.log(name);

对可选项使用解构赋值

旧习惯 使用代码处理默认值

function check(options){
    let options = Object.assign({},options,{
        a:1,
        b:2,
    })
    if(options.c === undefined){
        options.c = options.a + options.b
    }
    console.log(options.c)
    // ...
}

新习惯 将默认值提出来,通过解构来直接处理

function check(
{	
    a=1,
    b=2,
    c=a+b
} = {}){
    console.log(c)
    // ...
}

check({a:2}) // 4
分类
未分类

JavaScript 中你应该知道的 旧习惯 与 新习惯(一)

使用const 或者 let 替代 var

使用const 声明你不打算改变的值,比如配置,比如对象的引用。

使用let 声明打算改变的值

缩小变量的作用域

不用在开头列出所有的变量,现在推荐使用let 或者 const 就近声明,提高代码的可维护性

用块状作用域代替匿名函数

for(var n = 0; n < 10; n++){
    (function(value){
        setTimeout(
        	function(){
                console.log(value);
            },10
        );
    })(n)
}

使用块状作用率代替匿名函数

for(let n = 0; n < 10; n++){
	setTimeout(function(){
        console.log(n);
    })
}

使用箭头函数代替各种访问this值的变通方式

为了回调中访问上下文的this,使用了这些

旧习惯

  • 使用变量,var self = this
  • 使用 bind
  • 在支持的函数中,使用 thisArg 形参

新习惯

使用箭头函数

在不涉及this,或者大多数情况下,箭头函数都可以代替普通函数,更简洁

使用参数默认值,而不是代码实现

旧习惯

function do(参数){
	if(参数 === undefined){	
		参数 = 默认值
	}
    // XXX
}

新的习惯

function do(参数 = 默认值){
	// XXX
}

使用 rest 参数替代 arguments 关键字

旧习惯

function func1(a, b, c) {
  console.log(arguments[0]);
  // expected output: 1

  console.log(arguments[1]);
  // expected output: 2

  console.log(arguments[2]);
  // expected output: 3
}

func1(1, 2, 3);

新习惯

function func1(...rest) {
  console.log(rest[0]);
  // expected output: 1

  console.log(rest[1]);
  // expected output: 2

  console.log(rest[2]);
  // expected output: 3
}

func1(1, 2, 3);

考虑尾后逗号

旧习惯

const temp = {
	a:1,
	b:2
}

新习惯

const temp = {
	a:1,
	b:2,
}

好处在哪尼,问题在修改的时候可以少敲一个逗号

使用类创建构造函数

旧习惯

// 使用传统的函数语法
var Bird = function Bird(name){
   	this.name = name;
}
Bird.prototype.talk = function talk(string){
    return this.name + 'talk:' + string;
}

var bird = new Bird("小红");

bird.talk("Hi!");
// '小红talk:Hi!

新习惯

class Bird{
	constructor(name){
        this.name = name;
    }
    talk(string){
        return this.name + 'talk:' + string;
    }
}
var bird = new Bird("小红");

bird.talk("Hi!");
// '小红talk:Hi!
分类
未分类

从element的一次PR开始

那天我正刷着B站,主页突然跳出来up程序员小山与Bug的视频,名字叫 el-table固定表头滚动时,表头不跟手抖动的问题,跟着学到了一点调试小技巧,和大家分享下。

1、Ctrl + Shift + p 可以召唤出Chrome的命令

可以在里面方便的禁用js脚本

2、github上的 #数字 可以直接在github上的PR那边直接进入

比如

https://github.com/ElemeFE/element/pull/21863

这里的21863 就是编号

同样在releases 的文档种能找到这行

指的就是这个功能的PR号,非常的方便,看别人源码也不容易大海捞针

3、如何简单排查某行代码是否会影响其他功能,如果测试写的好的话直接跑测试,那如果测试不方便或者看不出来尼,可以看下增加那段代码时候的提示,是否是为了解决bug或者新增的功能或者单纯的是提高性能。

然后来看下这个同步是怎么做的

syncPostion() {
       const { scrollLeft, scrollTop, offsetWidth, scrollWidth } = this.bodyWrapper;
       const { headerWrapper, footerWrapper, fixedBodyWrapper, rightFixedBodyWrapper } = this.$refs;
       if (headerWrapper) headerWrapper.scrollLeft = scrollLeft;
       if (footerWrapper) footerWrapper.scrollLeft = scrollLeft;
       if (fixedBodyWrapper) fixedBodyWrapper.scrollTop = scrollTop;
       if (rightFixedBodyWrapper) rightFixedBodyWrapper.scrollTop = scrollTop;
       const maxScrollLeftPosition = scrollWidth - offsetWidth - 1;
       if (scrollLeft >= maxScrollLeftPosition) {
         this.scrollPosition = 'right';
      } else if (scrollLeft === 0) {
         this.scrollPosition = 'left';
      } else {
         this.scrollPosition = 'middle';
      }
    },

其实很简单,获取当前的scrollLeft 赋值给另一个 scrollLeft 就完成了,让我们来写个简单的同步

// index.html
<div class="headerWrapper wrapper">
 <table>
   <colgroup>
     <col width="180">
     <col width="180">
     <col width="200">
     <col width="200">
   </colgroup>
   <thead>
     <tr>
       <th>凉</th>
       <th>风</th>
       <th>有</th>
       <th>信</th>
     </tr>
   </thead>
 </table>

</div>
<div class="bodyWrapper wrapper">
 <table>
   <colgroup>
     <col width="180">
     <col width="180">
     <col width="200">
     <col width="200">
   </colgroup>
   <tbody>
     <tr>
       <td>凉风有信</td>
       <td>秋月无边</td>
       <td>亏我思娇情绪好比度日如年</td>
       <td>恨天各一方难与秋娟再相见</td>
     </tr>
   </tbody>
 </table>

</div>

css

.wrapper{
 width:300px;
 overflow: auto;
}

.wrapper > table{
 width:800px;
 
}
.headerWrapper{
 overflow: hidden;
}

下面就是见证奇迹的时刻

function ready() {

 const headerWrapper = document.getElementById("headerWrapper");
 const bodyWrapper = document.getElementById("bodyWrapper");

 bodyWrapper.addEventListener('scroll', function() {
   window.requestAnimationFrame(() => {
     let top = this.scrollTop
     let left = this.scrollLeft
     headerWrapper.scrollTo(left, top);
  })
})
}

document.addEventListener("DOMContentLoaded", ready);

在线练习链接 https://jsfiddle.net/knightgao/ksbyLfjg/59/

这样就实现了简单的js联动

那更进一步,按照力扣的命名规则,刚刚那题叫联动的话,那下面这题叫联动2

要求是n个互相影响尼,比如给了一排的div,要求拖动其中一个带动所有的进行滚动

1对1 到了 1对 N

原理没变,多了一些要求

  • 避免触发遍历的时候改变自己
  • 避免scroll 事件循环触发

关于循环触发这里补充下

循环触发例子链接 https://jsfiddle.net/knightgao/5L9mkz1t/19/

当test1滚动的时候会设置同步test2的值,如果这时候 test2 scroll 事件 中设置 test1的值,就会导致循环触发,造成不必要的性能浪费

<div class="headerWrapper wrapper">
 <table>
   <colgroup>
     <col width="400">
     <col width="400">
     <col width="400">
     <col width="400">
   </colgroup>
   <thead>
     <tr>
       <th>冲动</th>
       <th>的</th>
       <th>惩</th>
       <th>罚</th>
     </tr>
   </thead>
 </table>

</div>
<div class="bodyWrapper wrapper">
 <table>
   <colgroup>
     <col width="400">
     <col width="400">
     <col width="400">
     <col width="400">
   </colgroup>
   <tbody>
     <tr>
       <td>那夜我喝醉了拉着你的手</td>
       <td>胡乱地说话</td>
       <td>只顾着自己心中压抑的想法</td>
       <td>狂乱地表达</td>
     </tr>
   </tbody>
 </table>
</div>

<div class="bodyWrapper wrapper">
 <table>
   <colgroup>
     <col width="400">
     <col width="400">
     <col width="400">
     <col width="400">
   </colgroup>
   <tbody>
     <tr>
       <td>我迷醉的眼睛已看不清你表情</td>
       <td>忘记了你当时会有怎样的反应</td>
       <td>我拉着你的手放在我手心</td>
       <td>我错误地感觉到你也没有生气</td>
     </tr>
   </tbody>
 </table>
</div>
.wrapper{
 width:600px;
 overflow: auto;
}

.wrapper > table{
 width:2000px;
 
}
.headerWrapper{
 overflow: hidden;
}
function ready() {

 const nodes = document.getElementsByClassName("wrapper");

 function initScroller(nodes) {
   let max = nodes.length
   if (!max || max === 1) return
   let limit = 0;
   nodes.forEach((ele, index) => {
     ele.addEventListener('scroll', function() {
       window.requestAnimationFrame(() => {
         if (!limit) {
           limit = max - 1;
           let top = this.scrollTop
           let left = this.scrollLeft
           for (node of nodes) {
             if (node === this) continue;
             node.scrollTo(left, top);
          }
        } else
           --limit;
      })
    }, {
       passive: true
    });
  });
}
 initScroller([...nodes]);
}

document.addEventListener("DOMContentLoaded", ready);

在线地址 https://jsfiddle.net/knightgao/getb5mpj/24/

同学们下课

分类
未分类

vue-dev-server 源码解析

vue-dev-server 介绍

vue-dev-server是尤大编写的一个小工具,功能一句话介绍

This is a proof of concept.

Imagine you can import Vue single-file components natively in your browser… without a build step.

how it works

  • Imports are requested by the browser as native ES module imports – there’s no bundling.
  • The server intercepts requests to *.vue files, compiles them on the fly, and sends them back as JavaScript.
  • For libraries that provide ES modules builds that work in browsers, just directly import them from a CDN.
  • Imports to npm packages inside .js files (package name only) are re-written on the fly to point to locally installed files. Currently, only vue is supported as a special case. Other packages will likely need to be transformed to be exposed as a native browser-targeting ES module.

我来简单翻译下就是,翻译的不好见谅

  • 没有打包,浏览器直接使用 ES module 导入
  • 服务拦截 *.vue 的文件请求,即时编译,然后返回浏览器对应的 js
  • 对于那些在浏览器提供 ES modules 构建的库来说,只需要从CDN导入
  • 对于vue文件内的 导入 会动态重写用来支持本地安装的那些库

老规矩,看库先看test,站在2022年这个角度,还是很熟悉的用法。

测试比较简单就三个文件: index.html , main.js , test.vue

接着找下 package.json ,看看命令

“bin”: { “vue-dev-server”: “./bin/vue-dev-server.js” },

“scripts”: { “test”: “cd test && node ../bin/vue-dev-server.js” }

也很简单,直接指向了 bin/vue-dev-server.js

#!/usr/bin/env node

const express = require('express')
const { vueMiddleware } = require('../middleware')

const app = express()
const root = process.cwd();

app.use(vueMiddleware())

app.use(express.static(root))

app.listen(3000, () => {
 console.log('server running at http://localhost:3000')
})

这里很好理解起了个服务

中间件 vueMiddleware

设置了 静态文件目录就当前目录

重点看下 vueMiddleware

const vueCompiler = require('@vue/component-compiler')
const fs = require('fs')
const stat = require('util').promisify(fs.stat)
const root = process.cwd()
const path = require('path')
const parseUrl = require('parseurl')
const { transformModuleImports } = require('./transformModuleImports')
const { loadPkg } = require('./loadPkg')
const { readSource } = require('./readSource')

const defaultOptions = {
 cache: true
}

const vueMiddleware = (options = defaultOptions) => {
 let cache
 let time = {}
 
 if (options.cache) {
   const LRU = require('lru-cache')

   cache = new LRU({
     max: 500,
     length: function (n, key) { return n * 2 + key.length }
  })
}

 const compiler = vueCompiler.createDefaultCompiler()

 function send(res, source, mime) {
   res.setHeader('Content-Type', mime)
   res.end(source)
}

 function injectSourceMapToBlock (block, lang) {
   const map = Base64.toBase64(
     JSON.stringify(block.map)
  )
   let mapInject

   switch (lang) {
     case 'js': mapInject = `//# sourceMappingURL=data:application/json;base64,${map}\n`; break;
     case 'css': mapInject = `/*# sourceMappingURL=data:application/json;base64,${map}*/\n`; break;
     default: break;
  }

   return {
     ...block,
     code: mapInject + block.code
  }
}

 function injectSourceMapToScript (script) {
   return injectSourceMapToBlock(script, 'js')
}

 function injectSourceMapsToStyles (styles) {
   return styles.map(style => injectSourceMapToBlock(style, 'css'))
}
 
 async function tryCache (key, checkUpdateTime = true) {
   const data = cache.get(key)

   if (checkUpdateTime) {
     const cacheUpdateTime = time[key]
     const fileUpdateTime = (await stat(path.resolve(root, key.replace(/^\//, '')))).mtime.getTime()
     if (cacheUpdateTime < fileUpdateTime) return null
  }

   return data
}

 function cacheData (key, data, updateTime) {
   const old = cache.peek(key)

   if (old != data) {
     cache.set(key, data)
     if (updateTime) time[key] = updateTime
     return true
  } else return false
}

   // 解析单文件组件
 async function bundleSFC (req) {
     // 读取文件
   const { filepath, source, updateTime } = await readSource(req)
   const descriptorResult = compiler.compileToDescriptor(filepath, source)
   const assembledResult = vueCompiler.assemble(compiler, filepath, {
     ...descriptorResult,
     script: injectSourceMapToScript(descriptorResult.script),
     styles: injectSourceMapsToStyles(descriptorResult.styles)
  })
   return { ...assembledResult, updateTime }
}

 return async (req, res, next) => {
   if (req.path.endsWith('.vue')) {      
       // 处理vue文件 bundle 后返回 javascript
     const key = parseUrl(req).pathname
     let out = await tryCache(key)

     if (!out) {
       // Bundle Single-File Component
       const result = await bundleSFC(req)
       out = result
       cacheData(key, out, result.updateTime)
    }
     
     send(res, out.code, 'application/javascript')
  } else if (req.path.endsWith('.js')) {
       // 处理javascript 文件 使用缓存 提高效率 返回 javascript
     const key = parseUrl(req).pathname
     let out = await tryCache(key)

     if (!out) {
       // transform import statements
       const result = await readSource(req)
       out = transformModuleImports(result.source)
       cacheData(key, out, result.updateTime)
    }

     send(res, out, 'application/javascript')
  } else if (req.path.startsWith('/__modules/')) {
       // 处理 modules 下的路径 尝试缓存 loadPkg 只对 vue 做了处理 返回 javascript
     const key = parseUrl(req).pathname
     const pkg = req.path.replace(/^\/__modules\//, '')

     let out = await tryCache(key, false) // Do not outdate modules
     if (!out) {
       out = (await loadPkg(pkg)).toString()
       cacheData(key, out, false) // Do not outdate modules
    }

     send(res, out, 'application/javascript')
  } else {
     next()
  }
}
}

exports.vueMiddleware = vueMiddleware

到这里基本的解析就完成了,其实做的很简单,

  • 重复利用缓存,减少io
  • 充分的封装,保持主流程的清晰,与主流程无关的都封装出去,转移出去
  • 逻辑清晰,扩展容易,后续要扩展react 也很清楚要在哪里加
分类
typescript 源码解析

从vue3源码开始学(1)

第一次写,总感觉这没准备好,那没准备好,哪有都准备好了的,干就完事

准备工作

克隆项目 git clone https://github.com/lxchuan12/vue-next-analysis.git

计划了解的

主要要啃的是 packages\shared\src 下的函数

一个个来看下

正餐

is的用法

export const isArray: (arg: any) => arg is any[] = Array.isArray
// 首先这是TS的语法 等价于
export const isArray = Array.isArray

这种写法与 : 
export const isArray: (arg: any) => boolean = Array.isArray

有什么区别吗,看下面这两个例子

function isString(test: any): test is string{
    return typeof test === "string";
}

function example(foo: any){
    if(isString(foo)){
        console.log("it is a string" + foo);
        console.log(foo.length); // string function
        // 如下代码编译时会出错,运行时也会出错,因为 foo 是 string 不存在toExponential方法
        console.log(foo.toExponential(2));
    }
    // 编译不会出错,但是运行时出错
    console.log(foo.toExponential(2));
}
example("hello world");

// --------------------------------
function isString(test: any): boolean{
    return typeof test === "string";
}

function example(foo: any){
    if(isString(foo)){
        console.log("it is a string" + foo);
        console.log(foo.length); // string function
        // foo 为 any,编译正常。但是运行时会出错,因为 foo 是 string 不存在toExponential方法
        console.log(foo.toExponential(2));
    }
}
example("hello world");

在使用类型保护时,TS 会进一步缩小变量的类型。
例子中,将类型从 any 缩小至了 string
类型保护的作用域仅仅在 if 后的块级作用域中生效

keyof

const hasOwnProperty = Object.prototype.hasOwnProperty
export const hasOwn = (
  val: object,
  key: string | symbol
): key is keyof typeof val => hasOwnProperty.call(val, key)

这里没学过ts的同学可能有点晕,这个is刚刚上面讲过了是做类型保护的,那这个 keyof typeof 又是啥 这个val又是啥

别急,跟我一个个来

首先先看这个 keyof

keyof 操作符是在 TypeScript 2.1 版本引入的,
该操作符可以用于获取某种类型的所有键,其返回类型是联合类型。
interface Person {
  name: string;
  age: number;
  location: string;
}
type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[];  // number | "length" | "push" | "concat" | ...
type K3 = keyof { [x: string]: Person };  // string | number

除了接口外,keyof 也可以用于操作类
class Person {
  name: string = "Semlinker";
}

let sname: keyof Person;
sname = "name";

keyof 操作符除了支持接口和类之外,它也支持基本数据类型
let K1: keyof boolean; // let K1: "valueOf"
let K2: keyof number; // let K2: "toString" | "toFixed" | "toExponential" | ...
let K3: keyof symbol; // let K1: "valueOf"

此外 keyof 也称为输入索引类型查询,与之相对应的是索引访问类型,也称为查找类型。
在语法上,它们看起来像属性或元素访问,但最终会被转换为类型:
type P1 = Person["name"];  // string
type P2 = Person["name" | "age"];  // string | number
type P3 = string["charAt"];  // (pos: number) => string
type P4 = string[]["push"];  // (...items: string[]) => number
type P5 = string[][0];  // string

所以简单的抓住重点返回的是key

举个简单的例子
// js
function getProperty(obj, key) {
  return obj[key];
}

// 这个转成ts怎么写尼
function getProperty(obj: object, key: string) {
  return (obj as any)[key];
}

// 不太优雅,用上keyof
function getProperty<T extends object, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

function getProperty<我是对象 extends object, 我是对象中的键 extends keyof 我是对象>(obj: 我是对象, key: 我是对象中的键) {
  return obj[key];
}

这时候聪明的同学就要问了 老师,这个extends你还没讲呀

这个 extends 要和 keyof 连在一起看,至少按我的理解是的

keyof T 是 T 类型的键集

extends是子集的意思

子类型的值一定也是父类型。比如“猫”是“动物”的子类型,一只具体的 “猫”一定也是“动物”

所以父类型是 ‘a’|’b’ 子类型只能是 <= 父类型的

extends keyof 和 in keyof 要区分看

function getProperty<T extends object, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}
// 这里的意思是K必须是T的公共属性名称,与class的那个extends 意思不太一样甚至相反

interface Person {
  age: number;
  name: string;
}

// 每一项映射成了可选的
type Optional<T> = { 
  [K in keyof T]?: T[K] 
};

const person: Optional<Person> = {
  name: "Tobias"
};

// 看上去这个 Optional<Person> 和 Person 一模一样是吧
// 我这边的理解是 这是一种映射 从必填都变成了可选


type ReadOnly<T> = { 
  readonly [K in keyof T]: T[K] 
};
const readperson: ReadOnly<Person> = {
    name: "Tobias",
    age:18
};

readperson.age = '' // Cannot assign to 'age' because it is a read-only property.

就是这样

还不明白看看这个

type 可选的<未知> = { 
  [键 in keyof 未知]?: 未知[键] 
};

type 可选的<未知> = { 
  [键 in keyof 未知]?: 未知[键] 
};

const readperson: 可选的<Person> = {
    name: "Tobias",
};

总结下

A extends keyof B

表示 A 是B 的子类型

in 是遍历的意思

Record

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

export const isObject = (val: unknown): val is Record<any, any> =>
  val !== null && typeof val === 'object'

export const isFunction = (val: unknown): val is Function =>
  typeof val === 'function'

export const isPromise = <T = any>(val: unknown): val is Promise<T> => {
  return isObject(val) && isFunction(val.then) && isFunction(val.catch)
}

又有小可爱问了:这个Record是什么意思呀

看不懂没关系下次再讲

扩展

值得注意的点

1、is 在 TS 中可以用来类型保护,缩小返回

参考链接

2、keyof , extends keyof , in keyof

泛型参考链接

分类
未分类

前端拾遗|从前端函数式编程开始

函数式编程是什么呢?是一种编程范式,比较经典的函数式语言有 Haskell ,实际上 JacaScript 本身作为一门多范式的语言也是支持函数式的。

函数式具有五个鲜明的特点:

  1. 函数是一等公民
  2. Lambda表达式
  3. 纯函数,也叫没有副作用,不影响外部变量
  4. 不修改外部状态
  5. 引用透明,只依赖于输入的参数

来看一个例子


const arguments = ['qiupu'];
const callName = name => {
  console.log(arguments[0])
}
const callRealName = function(name) {
  console.log(arguments[0])
}
callName('knight'); // qiupu
callRealName ('knight'); // knight

这就是典型的上下文透传的例子。


数组里有一些方法可以实现类似Lodash库的语句组合形式,如链式调用、函数作为参数调用,也可以做一些控制语句代替for/while等。

来看一个例子

在JSX中我们常常看到这样的用法


const menu = (
<div>
  {
    props.post.filter(item =>item.name).map(item=>{
    return <div key={item.id}>{item.name}<div>
    })
  }
</div>
)

这里常见的map、filter等就是常见的 组合子 的包装。

分类
未分类

vue3变化

vue3的变化

全局api

createApp 返回实例 替代 new Vue 使得返回实例不再共享全局配置 全局的方法也从Vue上转移到了实例上

2.x全局API 3.x实例API(app)
Vue.config app.config
Vue.config.productionTip removed
Vue.config.ignoredElements app.config.isCustomElement
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use app.use
Vue.prototype app.config.globalProperties

config.ignoredElements 就是现在 config.isCustomElement

引入此配置选项旨在支持本机自定义元素,因此重命名可以更好地传达其功能。新选项还期望一个比旧字符串/ RegExp方法具有更大灵活性的函数:

 // before
 Vue.config.ignoredElements = ['my-el', /^ion-/]
    
 // after
 const app = Vue.createApp({})
 app.config.isCustomElement = tag => tag.startsWith('ion-')

Vue.prototype 取而代之 config.globalProperties

在Vue 2中,Vue.prototype通常用于添加所有组件都可以访问的属性。 Vue 3中的等效项是config.globalProperties。在实例化应用程序内的组件时,将复制这些属性:

// before - Vue 2
Vue.prototype.$http = () => {}
// after - Vue 3
const app = Vue.createApp({})
app.config.globalProperties.$http = () => {}

一个经过转换的例子如下

const app = createApp(MyApp)

app.component('button-counter', {
  data: () => ({
    count: 0
  }),
  template: '<button @click="count++">Clicked {{ count }} times.</button>'
})

app.directive('focus', {
  mounted: el => el.focus()
})

// button-counter 组件与 focus指令 不会污染全局 只在当前实例
app.mount('#app')

Provide / Inject

// 在父级
app.provide('guide', 'Vue 3 Guide')

// 在任意层次的子级
export default {
  inject: {
    book: {
      from: 'guide'
    }
  },
  template: `<div>{{ book }}</div>`
}

Tree-shaking

因为内部都重写了,所以组件内部也完全是支持摇树优化的,带来的后果就是一些方法需要显式导入来使用

import { nextTick } from 'vue'

nextTick(() => {
  // something DOM-related
})
//before
import { shallowMount } from '@vue/test-utils'
import { MyComponent } from './MyComponent.vue'

test('an async feature', async () => {
    const wrapper = shallowMount(MyComponent)

    // execute some DOM-related tasks

    await wrapper.vm.$nextTick()

    // run your assertions
})
//after
import { shallowMount } from '@vue/test-utils'
import { MyComponent } from './MyComponent.vue'
import { nextTick } from 'vue'

test('an async feature', async () => {
    const wrapper = shallowMount(MyComponent)

    // execute some DOM-related tasks

    await nextTick()

    // run your assertions
})

####受影响的API

2.x全局API 3.x用法
Vue.nextTick 拆分
Vue.observable 替换为 Vue.reactive
Vue.version 拆分
Vue.compile 仅完整版本
Vue.set 仅在兼容版本中
Vue.delete 仅在兼容版本中

参考链接

$attrs的变化

在vue2中,classstyle会被直接设置到组件的根元素并且不会出现在 $attrs中, 当inheritAttrs:false的时候

<template>
  <label>
    <input type="text" v-bind="$attrs" />
  </label>
</template>
<script>
export default {
  inheritAttrs: false
}
</script>

当这样使用的时候

<my-component id="my-id" class="my-class"></my-component>

vue2将生成以下html

<label class="my-class">
    <input type="text" id="my-id" />
</label>

vue3中$attr包含所有属性包括class与style vue3将生成以下html

<label>
    <input type="text" id="my-id" class="my-class" />
</label>

$listeners 在vue3也被移除,现在是$attrs的一部分

自定义指令的生命周期变化

简要总结:

  • API 已重命名,以便更好地与组件生命周期保持一致
  • 自定义指令将由子组件通过 v-bind="$attrs"

对比

2.x 3.x
bind beforeMount
inserted mounted
beforeUpdate
update 已经移除 改用 updated
componentUpdated updated
beforeUnmount
unbind unmounted

###生命周期变化 TODO

###scopedSlots正式弃用 v-slot 指令自 Vue 2.6.0 起被引入,提供更好的支持 slot 和 slot-scope attribute 的 API 替代方案。 v-slot 完整的由来参见这份 RFC 。 在接下来所有的 2.x 版本中 slot slot-scopeattribute 仍会被支持, 但不会出现在 Vue 3 中。 结合具名插槽要这样写

<span>
  <slot v-bind:user="user">
    {{ user.lastName }}
  </slot>
</span>

异步组件需要显式定义

Mixin合并行为变更

当来自组件的 data() 及其 mixin 或 extends 基类被合并时,现在将浅层次执行合并:

const Mixin = {
  data() {
    return {
      user: {
        name: 'Jack',
        id: 1
      }
    }
  }
}
const CompA = {
  mixins: [Mixin],
  data() {
    return {
      user: {
        id: 2
      }
    }
  }
}

vue2中结果是

{
  user: {
    id: 2,
    name: 'Jack'
  }
}

vue3中结果是

{
  user: {
    id: 2
  }
}
分类
未分类

flex布局常用属性

写组件的时候常常遇到这种情况

设计给的一个设计图 头是固定高度的 底下是自适应的

一般情况下

div{
display:flex;
flex-direction: column;
height:100%;
}

header{
height:50px;
}

body{
height:100%;
}

对一些要求不高的已经可以实现了,但是当下面组件高度很高的时候,flex会进行缩放,导致实际的header高度与预期的不一致,这个时候显式的给header 加上这两个属性就可以让他不缩放了

header{
flex-grow:0;
flex-shrink:0;
height:50px;
}

顺带一提 flex:1 flex: 1; !== flex: 1 1 auto;

  • 第一个参数表示: flex-grow 定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大
  • 第二个参数表示: flex-shrink 定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小
  • 第三个参数表示: flex-basis 给上面两个属性分配多余空间之前, 计算项目是否有多余空间, 默认值为 auto, 即项目本身的大小

分类
未分类

下一阶段预告

接下来半年内要做的事情

1.重构一个v-viewer-vue3版本的

2.开源一个可配置页面的前后端版本

欢迎加群催更 交流技术 秋秋群号:768901972

分类
未分类

git设置导致的血案-jenkins 编译 vue项目

可以看到前面报错了两次,查看报错信息 报的是

其实代码里是有这个文件的

后来想了一下 这个文件的名字我改过 之前是 user.js 后来改成了 User.js

但是我是windows git 默认不区分大小写 这边打包的是在docker里打包的 区分大小写 所以导致了这个结果

为了避免这样的惨剧 请瞧上这个设置

git config core.ignorecase false

这样windows 下 git就区分大小写了

顺带一提 git 在windows下文件目录长度是260 git自身是4096

要想突破可以

git config –global core.longpaths true

但还是建议适当控制长度

如果你有ci/cd,或者前端方向的想法,欢迎加群交流 球球群:768901972