yanyue404/blog

开发技巧挑战 100 楼

Opened this issue · 23 comments

001. JS 剔除对象中不需要的属性 + 惰性载入函数

剔除对象中不需要的属性

var original_params = {
  username: "Rainbow",
  age: "25",
  pwd: "123456",
  girlFriend: true,
  friend: ["heizi", "niu"]
};

提问

获取 usernamepwdobj

// 方法1: delete object[key]

let result = original_params;
delete result.age;
delete result.girlFriend;
delete result.friend;
// 方法2:使用解构删除不必要属性

const { friend, age, girlFriend, ...result2 } = original_params; //  Rest element must be last element

惰性载入函数

在某个场景下我们的函数中有判断语句,这个判断依据在整个项目运行期间一般不会变化,所以判断分支在整个项目运行期间只会运行某个特定分支,那么就可以考虑惰性载入函数

function foo() {
  if (a !== b) {
    console.log("aaa");
  } else {
    console.log("bbb");
  }
}

// 优化后
function foo() {
  if (a != b) {
    foo = function() {
      console.log("aaa");
    };
  } else {
    foo = function() {
      console.log("bbb");
    };
  }
  return foo();
}

第一次运行之后就会覆写这个方法,下一次再运行的时候就不会执行判断了。当然现在只有一个判断,如果判断很多,分支比较复杂,那么节约的资源还是可观的。

举一反三,既然在函数内部定义与原函数同名函数导出可以覆写原函数,在仅需让函数方法执行一次的场景也同样使用,将原函数覆写为空函数就可以了

参考

002. JS-根据条件给对象插入属性

对一个原始类型的值执行扩展运算符,它总会返回一个空对象。比如{ ...true }相当于{}。当然字符串除外,因为字符串拥有某些数组的特性。

那这个奇技淫巧的原理是什么呢?

其实是因为&&运算符的优先级比...高,所以{ ...name && { name } }相当于{ ...(name && { name }) },这样一转化就好理解了。

扩展运算符的优先级很低,仅高于,

// 通常我们都这么写

const profile = { age: 18, gender: "男" };

if (name) {
  profile.name = name;
}

// if 可以简化一下

if (name) profile.name = name;

// 你不能这样写,因为逻辑与运算符后面只能跟表达式,不能跟语句

name && profile.name = name;

// 现在你可以这样写

const profile = { age: 18, gender: "男", ...(name && { name }) };

参考

003. Node.js 的 Error-First Callbacks

  • 1.回调函数的第一个参数保留给一个错误 error 对象,如果有错误发生,错误将通过第一个参数 err 返回。
  • 2.回调函数的第二个参数为成功响应的数据保留,如果没有错误发生,err 将被设置为 null, 成功的数据将从第二个参数返回。
Page({
  // 获取用户可以领取的优惠券
  getMyCoupons(callback) {
    fetch._post(API.get_coupons, {}).then(res => {
      let err, result;
      if (res.response_data.error_code) {
        err = res.response_data;
      } else {
        result = res.response_data.lists;
      }
      typeof callback === "function" ? callback(err, result) : "";
    });
  }
});

004. 数组,对象映射优化 if... else...

  • switch-case
function getWeekDay() {
  let day = new Date().getDay();
  switch (day) {
    case 0:
      return "天";
    case 1:
      return "一";
    case 2:
      return "二";
    case 3:
      return "三";
    case 4:
      return "四";
    case 5:
      return "五";
    case 6:
      return "六";
  }
}
console.log(`今天是星期` + getWeekDay());
  • 数组映射
function getWeekDay() {
  let day = new Date().getDay();
  return ["天", "一", "二", "三", "四", "五", "六"][day];
}
console.log(`今天是星期` + getWeekDay());
  • 对象映射
function resolveWeekday() {
  let string = "今天是星期",
    date = new Date().getDay(),
    dateObj = {
      0: ["天", "休"],
      1: ["一", "工"],
      2: ["二", "工"],
      3: ["三", "工"],
      4: ["四", "工"],
      5: ["五", "工"],
      6: ["六", "休"]
    };
  dayType = {
    : function() {
      // some code
      console.log(string + "为休息日");
    },
    : function() {
      // some code
      console.log(string + "为工作日");
    }
  };
  return {
    string: string + dateObj[date][0],
    method: dayType[dateObj[date][1]]
  };
}
resolveWeekday().method();

参考

005. 从数组循环的 forEach 方法中删除元素

let data = [
  {
    name: "蔬菜类",
    thumb_img: "",
    price: 30,
    desc: "茄子",
    goods_id: "4",
    num: 1
  },
  {
    name: "越南进口红心火龙果 4个装",
    thumb_img: "",
    price: 0.01,
    goods_id: "7",
    num: 1
  }
];
let ids = ["4", "7"];
  • 方法一:反向删除 for while
let index = data.length - 1;
while (index >= 0) {
  if (ids.includes(data[index]["goods_id"])) {
    data.splice(index, 1);
  }
  index--;
}
for (var i = data.length - 1; i >= 0; i--) {
  if (ids.includes(data[i]["goods_id"])) {
    data.splice(i, 1);
  }
}
  • 方法二:浅拷贝数组:倒序修正 index 指向
// 使用 slice 浅拷贝 object 是真实的
data.slice().forEach((v, index, object) => {
  ids.includes(v["goods_id"]) ? data.splice(object.length - 1 - index, 1) : "";
});
  • 双重 forEach
data.slice().forEach((v, index, object) => {
  ids.forEach(m => {
    v.goods_id === m ? data.splice(object - 1 - index, 1) : "";
  });
});

参考

006. 优化表单验证逻辑判断

<script src="https://cdn.bootcss.com/vue/2.5.16/vue.min.js"></script>
<div id="app">
  <input type="text" v-model="username" placeholder="请输入你的用户名" />
  <input type="text" v-model="phone" placeholder="请输入你的手机号" />
  <input type="text" v-model="pwd" placeholder="请输入你的密码" />
  <button @click="submit">提交</button>
</div>
  • 方法一:数组遍历判断
var vm = new Vue({
  el: "#app",
  data: {
    username: "",
    phone: "",
    pwd: ""
  },
  methods: {
    submit: function() {
      const { username, phone, pwd } = this;
      let valids = [
        {
          valid: username,
          err: "请输入你的用户名"
        },
        {
          valid: phone,
          err: "请输入你的手机号码"
        },
        {
          valid: /^1[3|4|5|6|7|8][0-9]{9}$/.test(phone),
          err: "手机号码格式有误"
        },
        { valid: pwd, err: "请输入你的密码" }
      ];
      for (let val of valids) {
        if (!val.valid) {
          alert(val.err);
          return;
        }
      }
      alert("提交成功");
    }
  }
});
  • 方法二:Validator Class 公共类封装
class Validator {
  constructor(conditions) {
    setTimeout(() => {
      for (let item of conditions) {
        const res = item.fn();
        if (!res) {
          alert(item.err);
          return;
        }
      }
      this.callback();
    }, 0);
  }
  then(callback = () => {}) {
    this.callback = callback;
  }
}
var vm = new Vue({
  el: "#app",
  data: {
    username: "",
    phone: "",
    pwd: ""
  },
  methods: {
    submit: function() {
      const { username, phone, pwd } = this;
      let rules = [
        {
          fn: _ => username,
          err: "请输入你的用户名"
        },
        {
          fn: _ => phone,
          err: "请输入你的手机号码"
        },
        {
          fn: _ => /^1[3|4|5|6|7|8][0-9]{9}$/.test(phone),
          err: "手机号码格式有误"
        },
        { fn: _ => pwd, err: "请输入你的密码" }
      ];
      new Validator(rules).then(() => {
        alert("提交成功");
      });
    }
  }
});

参考

007. JS 的加减乘除运算

将浮点数 toString 后记录小数位的长度,然后将小数点抹掉,完整的代码如下:

Math.pow() 函数返回基数(base 例:10)的指数(exponent 例:2)次幂,Math.pow(10,2) = 100

/**
 * 加法
 * @param arg1
 * @param arg2
 * @returns
 **/
function accAdd(arg1, arg2) {
  var r1, r2, m;
  try {
    r1 = arg1.toString().split(".")[1].length;
  } catch (e) {
    r1 = 0;
  }
  try {
    r2 = arg2.toString().split(".")[1].length;
  } catch (e) {
    r2 = 0;
  }
  m = Math.pow(10, Math.max(r1, r2));
  return (arg1 * m + arg2 * m) / m;
}

/**
 * 减法
 * @param arg1
 * @param arg2
 * @returns
 */
function accSub(arg1, arg2) {
  var r1, r2, m, n;
  try {
    r1 = arg1.toString().split(".")[1].length;
  } catch (e) {
    r1 = 0;
  }
  try {
    r2 = arg2.toString().split(".")[1].length;
  } catch (e) {
    r2 = 0;
  }
  m = Math.pow(10, Math.max(r1, r2));
  //动态控制精度长度
  n = r1 >= r2 ? r1 : r2;
  return ((arg1 * m - arg2 * m) / m).toFixed(n);
}

function accMul(arg1, arg2) {
  var m = 0,
    s1 = arg1.toString(),
    s2 = arg2.toString();
  try {
    m += s1.split(".")[1].length;
  } catch (e) {}
  try {
    m += s2.split(".")[1].length;
  } catch (e) {}
  return (
    (Number(s1.replace(".", "")) * Number(s2.replace(".", ""))) /
    Math.pow(10, m)
  );
}

function accDiv(arg1, arg2) {
  var t1 = 0,
    t2 = 0,
    r1,
    r2;
  try {
    t1 = arg1.toString().split(".")[1].length;
  } catch (e) {}
  try {
    t2 = arg2.toString().split(".")[1].length;
  } catch (e) {}
  with (Math) {
    r1 = Number(arg1.toString().replace(".", ""));
    r2 = Number(arg2.toString().replace(".", ""));
    return (r1 / r2) * pow(10, t2 - t1);
  }
}

//给Number类型增加一个add方法,调用起来更加方便。
Number.prototype.add = function(arg) {
  return accAdd(arg, this);
};
Number.prototype.sub = function(arg) {
  return accSub(arg, this);
};
Number.prototype.Mul = function(arg) {
  return accMul(arg, this);
};
Number.prototype.Dev = function(arg) {
  return accMul(arg, this);
};

008. VSCode - ESLint, Prettier & Airbnb Setup

1. Install ESLint & Prettier extensions for VSCode

Optional - Set format on save and any global prettier options

2. Install Packages

npm i -D eslint prettier eslint-plugin-prettier eslint-config-prettier eslint-plugin-node eslint-config-node
npx install-peerdeps --dev eslint-config-airbnb

3. Create .prettierrc for any prettier rules (semicolons, quotes, etc)

4. Create .eslintrc.json file (You can generate with eslint --init if you install eslint globally)

{
  "extends": ["airbnb", "prettier", "plugin:node/recommended"],
  "plugins": ["prettier"],
  "rules": {
    "prettier/prettier": "error",
    "no-unused-vars": "warn",
    "no-console": "off",
    "func-names": "off",
    "no-process-exit": "off",
    "object-shorthand": "off",
    "class-methods-use-this": "off"
  }
}

Reference

009. Cannot use arrow keys to choose options on Windows

  • 输入选项 number , Enter

Reference

010. Markdown 使用

  • 首行缩进

  两个 &emsp; 即可

  • 图片尺寸

<img width="100%" src="http://ww1.sinaimg.cn/large/df551ea5ly1g865qolvofj20dw08zacz.jpg"></img>

参考

011. Git commit log

  • Head
    • type: feat 新特性, fix 修改问题, docs 文档, style 格式, refactor 重构, test 测试用例,revert 还原, chore 其他修改, 比如构建流程, 依赖管理.
    • scope:影响范围, 比如: route, component, utils, build... 可省略
    • subject:简短的提交信息
  • Body
    • what:详细做了什么
    • why: 为什么这样做
    • how: 有什么后果
  • Footer
    • 相关链接

012. 代码片段编写

import Taro, { Component } from '@tarojs/taro'
import { View, Text } from '@tarojs/components'
import './index.scss'

export default class Index extends Component {
 config = {
   navigationBarTitleText: '首页'
 }

 componentWillMount () { }

 componentDidMount () { }

 componentWillUnmount () { }

 componentDidShow () { }

 componentDidHide () { }

 render () {
   return (
     <View className='index'>
       <Text>1</Text>
     </View>
   )
 }
}

转换过程

符号 含义
\n 换行
\t 缩进
{
    "tarcs": {
    "prefix": "tarc",
    "body": [
      "import Taro from '@tarojs/taro';",
      "import { View, Text } from '@tarojs/components';",
      "import styles from './index.scss'",
      "",
      "export default class ${1:Test} extends Taro.Component {",
      "  render() {",
      "    return (",
      "      <View className={styles.${2:main}}>",
      "        <Text> ${1:测试输入} </Text>",
      "      </View>",
      "    );",
      "  }",
      "}",
      ""
    ],
    "description": "taro页面模板带状态"
  },
}

013. Ve2x base64 解密

浏览器 F12, 在 console 面板里输入

# 加密
btoa('13122223333') => MTMxMjIyMjMzMzM=

# 解密
atob('Y3JhY2sybkBnbWFpbC5jb20=') => crack2n@gmail.com

参考

014. require 自动导入

/**
 * @desc webpack打包入口文件
 */
let moduleExports = {};

const r = require.context('./', true, /^\.\/.+\/.+\.js$/);
r.keys().forEach(key => {
    let attr = key.substring(key.lastIndexOf('/') + 1, key.lastIndexOf('.'));
    moduleExports[attr] = r(key);
});

module.exports = moduleExports;

参考

015. package.json 文件添加注释

不能加在"dependencies"里,但是可以加为package.json的最顶层项:

{
"//": "This is comment 1",
"//": "This is comment 2"
}

或者

{
"//": [ "Line 1 of Comments", "Line 2 of Comments" ]
}

来源

016. 给你的select添加上placeholder

为 select 下拉框添加类似 input 的placeholder 的功能呢?

<select name="source" id="source">
   <option value="" disabled="" selected="" hidden="">请选择来源</option>
   <option value="1">个人</option>
   <option value="2">学校</option>
   <option value="3">平台</option>
</select>

来源

017. 微信内置浏览器缓存清理

之前做过很多公众号的项目,项目写完后给客户看项目,客户一而再再而三的修改元素向左挪1px,向右挪2px。改好之后让客户看,客户说我特泽发克,你啥都没有修改,你竟然骗我!!!

这其实就是微信内置浏览器的缓存在作祟啦,那么如何清理微信内置浏览器的缓存呢?

你们是否知道 ios版微信 和 android版微信 的内置浏览器的内核是不一样的呢?

android版微信内置浏览器(X5内核)
在安卓版微信内打开链接 http://debugx5.qq.com

拉到调试页面的最底端,勾选上所有的缓存项目,点击清除。

点击确定之后即可完成清除微信浏览器缓存的操作。

ios版微信内置浏览器(WKWebView)
ios版微信内置浏览器内核并不是 X5内核,而是使用的ios的浏览器内核WKWebView,所以安卓手机的那种方案对ios手机用户不生效,因为那个链接压根打不开

只要微信用户退出登录,然后重新登录,ios版微信内置浏览器内核即可清除,不行的话,你们回来打我

有人说了:“IOS中 设置—通用----存储空间 就会看到“正在计算空间”计算完了会清理一点清理即可”,这种办法当然也可以,但是这种办法不光是清理微信内置浏览器的缓存,同时也清理其他的一些数据,比如朋友圈的视频图片和聊天记录等等缓存,而且容易误删某些想留下的数据,对于开发而言,我认为退出重新登录是最好的解决办法。

来源

018. 接口 API 参考文档

一、基本规则

1.1 接⼝规则

协议规则:

描述 说明
传输⽅式 采⽤HTTP、HTTPS
提交⽅式 采⽤POST⽅法提交
请求参数类型 Content-Type: application/json
响应参数类型 Content-Type: application/json
字符编码 统⼀采⽤ UTF-8 字符编码
签名算法 MD5().toUpperCase()

1.2 请求参数公共字段

字段名 变量名 必填 类型 描述
调⽤⽅帐号 appId String(32) 分配给调⽤⽅的帐号 id
随机字符串 randomStr String(32) 随机字符串,不⻓于 32 位
签名 sign String(32) 签名
签名请求时间戳(毫秒级) gmtRequest Long 请求时间,Unix 时间戳格式,如1514782861001,代表‘2018-01-01 13:01:01.001’,仅允许请求时间与系统时间五分钟内的请求

1.3 返回参数字段格式

字段名 变量名 必填 类型 描述
数据实体 data json 接⼝返回的数据放这⾥
异常信息 msg string 接⼝调⽤返回的消息
异常码 code int(11) 接⼝调⽤返回异常,异常码
接⼝调⽤状态 success boolean(1) 接⼝调⽤状态 true-成功,false-失败

二、接⼝列表

2.1 查询积分

应⽤场景:查询⽤户积分余额

接⼝链接:/

请求⽅式:POST

请求参数:

字段名 变量名 必填 类型 描述
⽤户id uid int ⽤户ID

返回参数:

字段名 变量名 必填 类型 描述
账户积分 score int 会员账户积分

请求参数示例:

{ 
  "uid": 10001, 
  "appId": "xh161344685917", 
  "gmtRequest": 1603702285590, 
  "randomStr": "621465f968e0d9022f", 
  "sign": "278967FACE51E3BD45D7EB028E0CE5C8" 
} 

返回参数示例:

{ 
 "data": { 
 "uid": 10001, 
 "data": 120
  }
 "code": 0,
 "success": true 
}

收到如下返回时结束请求:

{ 
 "msg": "用户不存在", 
 "code": 100, 
 "success": false 
} 

019.在 Markdown 文档显示 diff 效果

function addTwoNumbers (num1, num2) {
-  return 1 + 2
+  return num1 + num2
}

参考

020. Markdown展开折叠功能

默认展开

details 标签默认是折叠状态,如果想默认为展开的话,可以添加一个 open 属性:

TestA1
TestB1
  • Test B1
TestB2
TestC1
  • Test C1

参考

021 markdown里怎么加引用注释或脚注

一般我们只是在markdown添加链接,但怎么在markdown里加脚注呢?下面来看看

这里有一个注脚[^1],这段话的还有其他意思[^2]在里面
[^1]:这里是注脚内容
[^2]:这里是其他意思的注脚

注脚放到中间也可以,下面是具体效果

md_footnote.png

md中链接的另一种写法

我是一段文字,[baidu][1]、[qq][2]里面有链接
[1]: http://baidu.com "baidu"
[2]: http://qq.com "qq"

参考

022 什么是好的代码?

在web前端方面,什么是好的代码?好的代码应该包含以下两个特性

  • 高性能,低时延(性能优化)
    • 熟悉数据结构与算法,减少时间复杂度或空间复杂度
    • 熟悉浏览器渲染基本原理、熟悉HTTP请求与响应细节、熟悉前端框架源码、减少不必要的渲染开销,提高加载速度
  • 可读性、可维护性、可扩展性
    • 熟悉设计模式,封装变化。代码高内聚、低耦合、指责单一、高度复用。写出好维护、好迭代、好扩展的代码
    • 化繁为简,形成特定代码规范,注意命名、注释。写出人能看懂的代码,不做*操作。尽量保持简单、易懂,在可扩展性和简单之间寻找平衡

前端只要不是写框架,性能问题会很少遇到。简单来讲,在实现功能的基础上,代码简单、易懂、好维护迭代就很好了。技术始终是为业务需求服务的。基础建设是很重要的一个环节,这样有利于快速迭代开发

参考

023 vue中为什么要使用js调用单文件组件?怎么实现js调用组件?

如果自己写一个组件。一般情况下,vue项目中在某个组件里调用另一个组件,至少需要修改三个位置

  1. 在 template 里写引入组件,加上传参等
  2. 在 components 里声明组件(如果全局引入了,可以省去这一步)
  3. data 里面写对应的传参数

代码对应如下,这种组件对于使用地方比较多时候,我们就需要想办法直接使用js来调用组件,而不是每次都要在 template 里面声明对应的组件,这样会有很多重复代码,可维护性较差。

<template>
  <el-button @click="showToast">打开toast</el-button>
  <!-- template中使用组件 -->
  <toast v-model="showToast"></toast>
</template>
<script>
export default {
  components: {
    Toast: () => import('./Toast.vue')
  }
  data() {
    return {
      showToast: false
    }
  }
}
</script>

较早之前使用js加载组件尝试

在较早之前,用js写过一个直接挂载组件到当前dom上的一个方法,分三步:

  1. 使用 Vue.extend,处理需要用js调用的vue单文件组件,返回该vue组件的一个子类
  2. new对应的组件子类,生成对应的组件实例,并使用 .$mount() 挂载组件,返回对应组件的 vm
  3. 这样可以通过 vm.$el 拿到组件dom,append到当前组件dom里,即可完成加载

具体写法如下

// 假设写好了 showInfo.vue 组件,执行clickShow函数直接显示dialog
// 组件中 dialog :visible.sync="dialogTableVisible"初始值设置为true

// demo.vue 在需要调用的vue文件中引入该组件
import ShowInfo from 'showInfo.vue'
// ...
clickShow() {
  const Component = Vue.extend(ShowInfo)

  // 挂载后返回对应组件的vm
  let showInfoVue = new Component().$mount()

  // 将组件vm的dom,append到当前页面
  this.$el.appendChild(showInfoVue.$el)
}
// ...

具体参考:使用js调用vue单文件组件

但它有一个缺点,常规调用组件时,我们会向子组件里面传入参数或事件。而这种情况不能向调用的子组件传入参数。下面来看另一种方法

使用js将单文件组件挂载到body的通用方法

我们来看看下面的create方法,其实和上面的方法流程基本一致,只是改变了创建对应vue单文件组件实例的方法。这里用render函数来替代之前的Vue.extend来创建对应组件实例,这样可以通过render函数的createElement函数向子组件内部传参数,传方法等。

// create.js
import Vue from "vue";

export default function create(Component, props) {
  // 先创建实例
  const vm = new Vue({
    render(h) {
      // h就是createElement,它返回VNode
      return h(Component, { props });
    }
  }).$mount();

  // 手动挂载
  document.body.appendChild(vm.$el);

  // 销毁方法
  const comp = vm.$children[0];
  comp.remove = function() {
    document.body.removeChild(vm.$el);
    vm.$destroy();
  };
  return comp;
}

上面的例子中,create函数参数接收一个单文件组件对象,以及在调用组件时需要传递给组件的参数,来看看具体使用例子

<template>
  <div>
    <el-button @click="showToast">打开toast</el-button>
  </div>
</template>
<script>
import Toast from "./Toast.vue";
import create from "./create";
export default {
  methods: {
    showToast() {
      console.log("show toast");
      let toast = create(Toast, {
        show: true,
        message: "我是错误信息",
        type: "error"
      });
      // 等价于 <toast :show="true" message="xx" :type="error"></toast>
      console.log(toast);
      setTimeout(() => {
        toast.remove();
      }, 2000);
    }
  }
};
</script>

Toast.vue 单文件组件代码如下

<template>
  <div class="my-toast" v-if="show">
    <div :class="type">{{ message }}</div>
  </div>
</template>
<script>
export default {
  props: {
    message: {
      type: String,
      required: true
    },
    type: {
      type: String,
      default: "error"
    }
    show: {
      type: Boolean,
    }
  }
};
</script>
<style lang="less" scoped>
.my-toast {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 300px;
  border: 1px solid #ccc;
  text-align: center;
  .error {
    color: red;
  }
  .success {
    color: green;
  }
}
</style>

再进一步封装

上面的例子中,我们引入了 create.js 以及 对应的单文件组件。它还是不够简洁,我们可以再封装一次,只需要调用一个js就搞定。如果我们将它在main.js里面引入并挂载到vue实例属性,那么调用就非常方便了。

// 在main.js里注册实例属性
import showDialog from '@/views/jsDialog/index.js'
Vue.prototype.$showDialog = showDialog

// 其他地方直接使用 this.$showDialog(options) 即可调用组件

下面来看看实现思路,关于render函数createElement的options的配置,参见 createElement 参数 - 深入数据对象

// showDialog/index.js
import Vue from "vue";
import DialogComponent from '@/views/jsDialog/src/index.vue'

let TheDialog = null
export default function showDialog(options) {
  // 如果未移除,先移除
  TheDialog && TheDialog.remove()

  TheDialog = create(DialogComponent, {
    on: {
      // 单文件组件内部可以emit该事件,销毁TheDialog组件
      'close-dialog': () => {
        TheDialog.remove()
      }
    },
    props: {
      // 需要传入的属性,单文件组件需要使用props接收
      title: '标题',
      content: '内容'
    }
    // 其他参数
    ...options
  })

  function create(Component, options) {
    // 先创建实例
    const vm = new Vue({
      render(h) {
        // h就是createElement,它返回VNode
        return h(Component, options);
      }
    }).$mount();

    // 手动挂载
    document.body.appendChild(vm.$el);

    // 销毁方法
    const comp = vm.$children[0];
    comp.remove = function() {
      document.body.removeChild(vm.$el);
      vm.$destroy();
    };
    return comp;
  }
}

注意,虽然js调用更方便了,但js处理、render组件的传参复杂度会增加。它和普通组件各有各的优缺点。

参考