童欧巴

童欧巴 查看完整档案

北京编辑北华大学  |  电子信息工程 编辑某大型砖厂  |  前端工程师 编辑 hungryturbo.com 编辑
编辑

个人动态

童欧巴 发布了文章 · 9月26日

海贼王 One Piece,一起康康Vue版本号中的彩蛋

image

观感度:🌟🌟🌟🌟🌟

口味:油焖大虾

烹饪时间:5min

本文已收录在前端食堂同名仓库Github github.com/Geekhyt,欢迎光临食堂,如果觉得酒菜还算可口,赏个 Star 对食堂老板来说是莫大的鼓励。

如果你在 Vue 的仓库中看过 Releases 历史,你会发现有一些版本发布时会带上代号。不知你是否会好奇这些代号的含义呢?

本文汇总整理了至今 Vue 3.0 发布为止的版本代号,在学习和工作之余,让我们一起来回顾一下它们吧~

(注:除了 v2.6.0 和 v3.0.0 是官方配图,其他的版本配图来源于网络)

Vue 最初的命名是 Seed

v0.6.0 改名为 VueJS

v0.8.0 Vue 的第一个对外版本

v0.9.0 Animatrix 黑客帝国

"...then man made the machine in his own likeness. Thus did man become the architect of his own demise." - The Instructor

从这个版本开始后,重要的版本都有了自己的代号。

v0.10.0 Blade Runner 银翼杀手

"A coding sequence cannot be revised once it's been established." -Tyrell

v0.11.0 Cowboy Bebop 星际牛仔 又译 赏金猎人

"There are ends we don't desire, but they're inevitable, we have to face them. It's what being human is all about." - Jet Black

v0.12.0 Dragon Ball 龙珠

"If you keep calm you'll never be a super saiyan." - Goku

v1.0.0 Evangelion 新世纪福音战士

"The fate of destruction is also the joy of rebirth." - SEELE

About the Release

同年,vue-router、vuex、vue-cli相继发布。

v2.0.0 Ghost in the Shell 攻壳机动队

"Your effort to remain what you are is what limits you." - Puppet Master

Release blog post

v2.1.0 Hunter X Hunter 全职猎人

"You should enjoy the little detours to the fullest. Because that's where you'll find the things more important than what you want." - Ging Freecss

v2.2.0 Initial D 头文字D

"Fill'er up. High Octane."

v2.3.0 JoJo's Bizarre Adventure JoJo的奇妙冒险

"It was me, Dio!"

v2.4.0 Kill la Kill 斩服少女

"Fear is freedom! Subjugation is liberation! Contradiction is truth!"

v2.5.0 Level E 灵异E接触

v2.6.0 Macross 超时空要塞

v3.0.0 One Piece 海贼王 航海王

感谢尤大在给我们贡献出这么优秀的框架之余,还推荐给我们经典的动漫看。

也许 One Piece 的另一层含义是:愿每个人都能找到属于自己的梦想宝藏。

不过我想问尤大:为什么没有火影忍者!

你最喜欢哪一部动漫呢?

❤️爱心三连击

1.如果你觉得食堂酒菜还合胃口,就点个赞支持下吧,你的是我最大的动力。

2.关注公众号前端食堂,吃好每一顿饭!

3.本文已收录在前端食堂 Github ,希望能得到你的小星星鼓励。

查看原文

赞 0 收藏 0 评论 1

童欧巴 发布了文章 · 9月19日

从今天起构建你的JavaScript世界

观感度:🌟🌟🌟🌟🌟

口味:酸辣无骨凤爪

烹饪时间:5min

本文已收录在前端食堂同名仓库Github github.com/Geekhyt,欢迎光临食堂,如果觉得酒菜还算可口,赏个 Star 对食堂老板来说是莫大的鼓励。
  • 现在的笔试题让手写xxx原理,代码太多了,记不住怎么办?
  • 要如何深刻理解 JavaScirpt 中的各种概念呢?
  • 。。。

这些问题是交流群中同学们的提问,相信或多或少的同学们都经历过这个阶段。想要解决这些问题,我决定先从一本书说起。

《如何高效学习:1 年完成麻省理工 4 年 33 门课程的整体性学习法》

一位名叫斯科特·杨的精神小伙儿因快速学习而成名,他将自己的学习方法全部总结到了这本书里。

当然,除了掌握方法以外,还要做到时间管理上的极度自律,这里还是要向罗老师学习。

拉回来,书中提到了很多的方法,感兴趣的同学们可以去阅读。

正如我读完后在书评中所写道:每个人的学习方法都不同,高手与菜鸟的成长轨迹也不同。挑选这本书中真正适合自己的方法才是最好的。

这里我们先抛开书中提到的一些概念,抽取书中一些我认为有用的方法来看一看。

书中所强调的整体性学习是一种学习理论,不同于机械记忆。比如,不断的在脑海中重复数十次来记忆一段古诗词或是物理公式,这就是机械的记忆。

(这里吐槽一下高中的语文老师,告诉我们背古诗词的时候就是要死记硬背,机械记忆,读 100 遍肯定能背下来。人无完人,只是单纯吐槽这一点,还是要感谢老师的教育。)

所以,无论什么时候,拥有独立思考的能力都是很重要的。当时的我还是按照自己熟悉的方式去背古诗词,并没有机械记忆,以至于我的语文成绩一向很好,语文成绩幸运的拿到过全校第一。

事实上,知识的学习从来就不是孤立的。

拿计算机和人脑相比,计算机本质上存储的不过是 0 或 1,而人脑中是通过数十亿个神经元相互联系储存信息的。整体性学习正是要利用你大脑中已有的丰富神经元网络,对信息进行吸收、整合。

你创造的联系越多,这些信息就会理解的越好,记忆的越牢。所以,我们需要建立属于自己的知识网络,从而达到高效学习。

那么,问题来了,如何创造关联呢?这种方法我们早在幼儿园的时候就已经在学习了。

那就是比喻。

比喻就是在你不熟悉的领域和你熟悉的领域间建立起一座桥梁。

拿我们最熟悉的 JavaScript 中的基本数据类型来举例:

JavaScript 的数据类型包括原始类型和对象类型:

原始类型:Null、Undefined、Boolean、Number、String、Symbol、BigInt

对象类型:Object

(我们习惯把对象称为引用类型,当然还有很多特殊的引用类型,比如Function、Array等)

我们知道,原始类型值是不可变的。

let str = '前端食堂';

str[0] = 6;

console.log(str); // 前端食堂

不难看出,我们无法更改字符串的值。但是如果像下面这样更改呢?

let str = '前端食堂';

str = '前端食堂老板是童欧巴';

console.log(str); // 前端食堂老板是童欧巴

这不就改变了吗?其实不然。

JavaScript 中的原始类型值被存储在栈中,上面的操作只不过是在栈中新开辟了一块内存空间用于存储新的字符串而已,然后将 str “指” 向了新的字符串所在的内存空间,原来的字符串是没有变的。

而引用类型不再具有不可变性,我们可以很容易的更改他们。

obj.name = '前端食堂';
obj.age = '18';

那么我们如何使用比喻法来加深对 JavaScript 中基本数据类型的理解呢?

来到了前端食堂,我们不妨加入一些人间烟火气,一起做一道菜!

酸辣无骨凤爪!

原始类型就是盐、生抽等调料,无法改变,想用就用。

对象则是做无骨凤爪需要的主要食材,也就是凤爪,首先我们需要将凤爪用清水泡 20 分钟后将指甲剪掉,然后锅里加入姜片、料酒和凤爪,水烧开后再煮 10 分钟。期间记得捞出血末,煮熟后的凤爪需要再冰水中泡半个小时,凤爪的口感会更加的 Q 弹,接下来就是这道菜最耗时的一步,将凤爪的骨头剔出来。最后,准备一碗灵魂酱汁,根据个人喜好加入盐、生抽、白糖等调料和柠檬片,将凤爪与酱汁拌匀,盖上保鲜膜放入冰箱保鲜,两个小时后你就得到了一份酸辣无骨凤爪。

  • 原始类型的不可变性:盐、生抽
  • 对象:我们可以对凤爪进行各种处理

这样理解起来,是不是会更加深刻呢?你也可以将它们比喻为你更熟悉的事物,构建属于你自己的知识网络。

书中还介绍了一些关于比喻法的实操步骤,做起来也不难,如下几点,提供给你借鉴:

  • 明确概念
  • 建立图像
  • 让图像动起来
  • 添加感官
  • 加入情感
  • 不断优化

本文算是开了个头,希望这些感悟可以帮助你将难理解的抽象概念用比喻法进行消化吸收,构建属于你自己的 JavaScript 世界!

不多说了,我去吃凤爪了。大家周末愉快~

❤️爱心三连击

1.如果你觉得食堂酒菜还合胃口,就点个赞支持下吧,你的是我最大的动力。

2.关注公众号前端食堂,吃好每一顿饭!

3.本文已收录在前端食堂 Github github.com/Geekhyt,希望能得到你的小星星鼓励。

查看原文

赞 7 收藏 7 评论 0

童欧巴 发布了文章 · 9月8日

食堂店小二儿教你学会栈

image

观感度:🌟🌟🌟🌟🌟

口味:酸辣柠檬虾

烹饪时间:10min

本文已收录在前端食堂同名仓库Github github.com/Geekhyt,欢迎光临食堂,如果觉得酒菜还算可口,赏个 Star 对食堂老板来说是莫大的鼓励。

理解栈 Stack

前端食堂里的店小二儿对栈的理解很深刻,我们来听听他是怎样理解栈的。

店小二儿十分勤奋,前台后厨的活儿他都干,每天都要跑前跑后端顾客吃完的盘子。栈就像叠在一起的盘子,客人美滋滋的吃完饭,店小二去收拾桌子捡起盘子时,都是从下往上一个一个的放盘子。而他在后厨橱柜上取盘子给厨师时,是从上往下一个一个依次的取。

这也就是所谓的 LIFO (Last In, First Out, 后进先出)。

在 JavaScript 中,我们可以通过数组来简单模拟下栈:

function Stack() {
  var items = [];
  // 添加元素到栈顶
  this.push = function(element) {
    items.push(element);
  }
  // 移除栈顶的元素,同时返回它们
  this.pop = function() {
    return items.pop();
  }
  // 返回栈顶的元素,不对栈做任何修改
  this.peek = function() {
    return items[items.length - 1];
  }
  // 判断栈是否为空,为空则返回true,否则返回false
  this.isEmpty = function() {
    return items.length === 0;
  }
  // 返回栈里的元素个数
  this.size = function() {
    return items.length;
  }
  // 清空栈
  this.clear = function() {
    items = [];
  }
}

栈的特点

  • 栈是一种“操作受限”的线性表,只能从一端插入或删除数据。
  • 入栈和出栈的时间复杂度、空间复杂度都是 O(1)。

栈的应用

栈的应用有很多,比如我们熟知的函数调用栈、浏览器的前进后退还有汉诺塔小游戏等。

LeetCode 真题

点击上方链接即可跳转读题,这是一道难度为困难的题,不过大家不要被它所迷惑,其实它不是很难。

解决这道题,最直观的办法就是暴力求解。我们可以先来分析一波:

读题的第一遍,实际上就是要求在宽度为 1 的 n 个柱子能勾勒出来的矩形的最大面积。

这不就是个幼儿园的数学问题吗?

面积 = 底 * 高

撸它!

暴力法

方法一:双重循环遍历出所有的可能性,在遍历的过程中我们还可以求出最小的高度。

const largestRectangleArea = function(heights) {
  let maxArea = 0;
  let len = heights.length;
  for (let i = 0; i < len; i++) {
    let minHeight = heights[i];
    for (let j = i; j < len; j++) {
      minHeight = Math.min(minHeight, heights[j]);
      maxArea = Math.max(maxArea, minHeight * (j - i + 1));
    }
  }
}

方法二:确定一根柱子后,分别向前后两个方向遍历。

const largestRectangleArea = function(heights) {
  let maxArea = 0;
  let len = heights.length;
  for (let i = 0; i < len; i++) {
    let left = i;
    let right = i;
    while (left >= 0 && heights[left] >= heights[i]) {
      left--;
    }
    while (right < len && heights[right] >= heights[i]) {
      right++;
    }
    maxArea = Math.max(maxArea, (right - left - 1) * heights[i]);
  }
  return maxArea;
}

但是这两种方法的时间复杂度都是 O(n^2),空间复杂度是 O(1)。时间复杂度太高了,我们需要想办法进行优化。

使用单调递增栈

我们来思考一个问题,我们究竟想要求的最大面积是什么?

不妨拿起笔画画图,我这里帮你画好了,观察上图,便可以得出:

其实就是以 i 为中心,分别向左、向右找到第一个小于 heighs[i] 的两个边界,也就是以当前这根 i 柱子所能勾勒出的最大面积。

那么,我们为什么要借助单调递增栈这种数据结构呢?

单调递增,也就是我们可以通过 O(1) 的时间复杂度确定柱体 i 的左边界!

又是以空间换时间的套路!

如何确定右边界?

只需遍历一次柱体数组,将大于等于当前柱体的柱子们推入栈中,而当遍历到的柱子高度小于栈顶的柱子高度时,这时我们找到了右边界,可以将栈顶的柱子弹出栈
来计算矩形面积了!

处理特殊边界情况?

引入前后边界,在柱体数组前后各放入一根高度为 0 的柱子。这样便无需考虑栈空以及栈中还有剩余柱子的情况啦!

ok,上代码!

const largestRectangleArea = function(heights) {
  let maxArea = 0;
  let stack = [];
  heights.push(0);
  heights.unshift(0);
  // heights = [0, ...heights, 0]; 你也可以这样写
  for (let i = 0; i < heights.length; i++) {
    while (stack.length > 0 && heights[stack[stack.length - 1]] > heights[i]) {
      maxArea = Math.max(maxArea, heights[stack.pop()] * (i - stack[stack.length - 1] - 1));
    }
    stack.push(i);
  }
  return maxArea;
}
  • 时间复杂度:O(N)
  • 空间复杂度:O(N)

希望本文可以让你对栈的理解更深一层,如果你还没看过瘾,可以移步我的主页有其他算法系列的文章。

❤️爱心三连击

1.如果你觉得食堂酒菜还合胃口,就点个赞支持下吧,你的是我最大的动力。

2.关注公众号前端食堂,吃好每一顿饭!

3.本文已收录在前端食堂 Github github.com/Geekhyt,希望能得到你的小星星鼓励。

查看原文

赞 1 收藏 1 评论 0

童欧巴 发布了文章 · 8月23日

前端工程师不可不知的Nginx知识

image

观感度:🌟🌟🌟🌟🌟

口味:虎皮鸡蛋

烹饪时间:10min

本文已收录在前端食堂同名仓库Githubgithub.com/Geekhyt,欢迎光临食堂,如果觉得酒菜还算可口,赏个 Star 对食堂老板来说是莫大的鼓励。

历史背景

互联网的全球化导致了互联网的数据量快速增长,加上在本世纪初摩尔定律在单核 CPU 上的失效,CPU 朝着多核方向发展,而 Apache 显然并没有做好多核架构的准备,它的一个进程同一时间只能处理一个连接,处理完一个请求后才能处理下一个,这无疑不能应对如今互联网上海量的用户。况且进程间切换的成本是非常高的。在这种背景下,Nginx 应运而生,可以轻松处理数百万、上千万的连接。

Nginx 优势

  • 高并发高性能
  • 可扩展性好
  • 高可靠性
  • 热部署
  • 开源许可证

Nginx 主要应用场景

  • 静态资源服务,通过本地文件系统提供服务
  • 反向代理服务、负载均衡
  • API服务、权限控制,减少应用服务器压力

Nginx 配置文件和目录

通过 rpm -ql nginx 可以查看 Nginx 安装的配置文件和目录。

如图是我在某某云上安装的最新稳定版本的Nginx的配置文件及目录。

  • /etc/nginx/nginx.conf 核心配置文件
  • /etc/nginx/conf.d/default.conf 默认http服务器配置文件
  • /etc/nginx/fastcgi_params fastcgi配置
  • /etc/nginx/scgi_params scgi配置
  • /etc/nginx/uwsgi_params uwsgi配置
  • /etc/nginx/koi-utf
  • /etc/nginx/koi-win
  • /etc/nginx/win-utf 这三个文件是编码映射文件,因为作者是俄国人
  • /etc/nginx/mime.types 设置HTTP协议的Content-Type与扩展名对应关系的文件
  • /usr/lib/systemd/system/nginx-debug.service
  • /usr/lib/systemd/system/nginx.service
  • /etc/sysconfig/nginx
  • /etc/sysconfig/nginx-debug 这四个文件是用来配置守护进程管理的
  • /etc/nginx/modules 基本共享库和内核模块
  • /usr/share/doc/nginx-1.18.0 帮助文档
  • /usr/share/doc/nginx-1.18.0/COPYRIGHT 版权声明
  • /usr/share/man/man8/nginx.8.gz 手册
  • /var/cache/nginx Nginx的缓存目录
  • /var/log/nginx Nginx的日志目录
  • /usr/sbin/nginx 可执行命令
  • /usr/sbin/nginx-debug 调试执行可执行命令

关于 Nginx 的常用命令以及配置文件语法很容易就可以搜到,本文不作赘述,下面从 Nginx 的功能以及实际场景出发看一看各个场景下 Nginx 可以提供给我们哪些配置项。在此之前,我们先来明确两个概念:

正向代理 Forward proxy

一句话解释正向代理,正向代理的对象是客户端,服务器端看不到真正的客户端。

resolver 8.8.8.8 # 谷歌的域名解析地址
server {
    location / {
      # 当客户端请求我的时候,我会把请求转发给它
      # $http_host 要访问的主机名 $request_uri 请求路径
      proxy_pass http://$http_host$request_uri;
    }
}

反向代理 Reverse proxy

一句话解释反向代理,反向代理的对象是服务端,客户端看不到真正的服务端。

跨域

跨域是前端工程师都会面临的场景,跨域的解决方案有很多。不过要知道在生产中,要么使用 CORS 、要么使用 Nginx 反向代理来解决跨域。在 Nginx 的配置文件中进行如下配置即可:

server {
    listen   80;
    server_name   localhost; # 用户访问 localhost,反向代理到 http://webcanteen.com
    location / {
        proxy_pass http://webcanteen.com
    }
}

Gzip

Gzip 是互联网上非常普遍的一种数据压缩格式,对于纯文本来说可以压缩到原大小的 40%,可以节省大量的带宽。不过需要注意的是,启用 Gzip 所需的 HTTP 最低版本是 1.1。

location ~ .*\. (jpg|png|gif)$ {
    gzip off; #关闭压缩
    root /data/www/images;
}
location ~ .*\. (html|js|css)$ {
    gzip on; #启用压缩
    gzip_min_length 1k; # 超过1K的文件才压缩
    gzip_http_version 1.1; # 启用gzip压缩所需的HTTP最低版本
    gzip_comp_level 9; # 压缩级别,压缩比率越高,文件被压缩的体积越小
    gzip_types text/css application/javascript; # 进行压缩的文件类型
    root /data/www/html;
}

请求限制

对于大流量恶意的访问,会造成带宽的浪费,给服务器增加压力。往往对于同一 IP 的连接数以及并发数进行限制。

关于请求限制主要有两种类型:

  • limit_conn_module 连接频率限制
  • limit_req_module 请求频率限制
# $binary_remote_addr 远程IP地址 zone 区域名称 10m内存区域大小
limit_conn_zone $binary_remote_addr zone=coon_zone:10m;
server {
    # conn_zone 设置对应的共享内存区域 1是限制的数量
    limit_conn conn_zone 1;
}
# $binary_remote_addr 远程IP地址 zone 区域名称 10m内存区域大小 rate 为请求频率 1s 一次
limit_req_zone $binary_remote_addr zone=req_zone:10m rate=1r/s;
server {
    location / {
        # 设置对应的共享内存区域 burst最大请求数阈值 nodelay不希望超过的请求被延迟
        limit_req zone=req_zone burst=5 nodelay;
    }
}

访问控制

关于访问控制主要有两种类型:

  • -http_access_module 基于 IP 的访问控制
  • -http_auth_basic_module 基于用户的信任登陆

(基于用户的信任登陆不是很安全,本文不做配置介绍)

以下是基于 IP 的访问控制:

server {
    location ~ ^/index.html {
        # 匹配 index.html 页面 除了 127.0.0.1 以外都可以访问
        deny 127.0.0.1;
        allow all;
    }
}

ab命令

ab命令全称为:Apache bench,是 Apache 自带的压力测试工具,也可以测试 Nginx、IIS 等其他 Web 服务器。

  • -n 总共的请求数
  • -c 并发的请求数
ab -n 1000 -c 5000 http://127.0.0.1/

防盗链

防盗链的原理就是根据请求头中 referer 得到网页来源,从而实现访问控制。这样可以防止网站资源被非法盗用,从而保证信息安全,减少带宽损耗,减轻服务器压力。

location ~ .*\.(jpg|png|gif)$ { # 匹配防盗链资源的文件类型
    # 通过 valid_referers 定义合法的地址白名单 $invalid_referer 不合法的返回403  
    valid_referers none blocked 127.0.0.1;
    if ($invalid_referer) {
        return 403;
    }
}

负载均衡 Load Balance

当我们的网站需要解决高并发、海量数据问题时,就需要使用负载均衡来调度服务器。将请求合理的分发到应用服务器集群中的一台台服务器上。

Nginx 可以为我们提供负载均衡的能力,具体配置如下:

# upstream 指定后端服务器地址
# weight 设置权重
# server 中会将 http://webcanteen 的请求转发到 upstream 池中
upstream webcanteen {
    server 127.0.0.1:66 weight=10;
    server 127.0.0.1:77 weight=1;
    server 127.0.0.1:88 weight=1;
}
server {
    location / {
        proxy_pass http://webcanteen
    }
}

后端服务器状态

后端服务器支持以下的状态配置:

  • down:当前服务器不参与负载均衡
  • backup:当其他节点都无法使用时的备用服务器
  • max_fails:允许请求失败的次数,若到达就会休眠
  • fail_timeout:经过max_fails次失败后,服务器的暂停时间,默认为10s
  • max_conns:限制每个服务器的最大接收连接数
upstream webcanteen {
    server 127.0.0.1:66 down;
    server 127.0.0.1:77 backup;
    server 127.0.0.1:88  max_fails=3 fail_timeout=10s;
    server 127.0.0.1:99 max_conns=1000;
}

分配方式

  • 轮询(默认),每个请求按照时间顺序轮流分配到不同的后端服务器,如果某台后端服务器宕机,Nginx 轮询列表会自动将它去除掉。
  • weight(加权轮询),轮询的加强版,weight 和访问几率成正比,主要用于后端服务器性能不均的场景。
  • ip_hash,每个请求按照访问 IP 的 hash 结果分配,这样每个访问可以固定访问一个后端服务器。
  • url_hash,按照访问 URL 的 hash 结果来分配请求,使得每个URL定向到同一个后端服务器上,主要应用于后端服务器为缓存时的场景。
  • 自定义hash,基于任意关键字作为 hash key 实现 hash 算法的负载均衡
  • fair,按照后端服务器的响应时间来分配请求,响应时间短则优先分配。
查看原文

赞 31 收藏 22 评论 0

童欧巴 关注了用户 · 8月20日

程序猿小卡_casper @chyingp

阿里高级前端技术专家

github /
知乎 /

Node.js交流群 197339705

关注 1015

童欧巴 发布了文章 · 8月17日

前端音视频的那些名词

image

观感度:🌟🌟🌟🌟🌟

口味:椒盐小酥肉

烹饪时间:10min

本文已收录在前端食堂同名仓库Githubgithub.com/Geekhyt,欢迎光临食堂,如果觉得酒菜还算可口,赏个 Star 对食堂老板来说是莫大的鼓励。

比特率 Bit rate (码率、码流)

代表每秒传送的比特数。

比特率又称为“二进制位速率”,俗称“码率”,“码流”,又称为数据信号速率。这大兄弟别名好多。。

从传输速度的发展看来,摩尔定律不仅应用在晶体管的密度,同样可以用在传输速度:比特率大概每18个月提高一倍。

单位

比特/秒(bit/s 或 bps)、千比特/秒(kbit/s 或 kbps)、兆比特/秒 (Mbit/s 或 Mbps)

(1Mbps = 1000kbit/s)

比特率越高,代表单位时间传送的数据就越多。

公式

码率(kbps) = 文件大小(KB) * 8 / 时间(s)

举个例子理解:假如视频文件的容量为 2.888G,视频长度100分钟(6000秒),码率约等于 4037kbps (3.446 1024 1024 * 8 / 6000 = 4037.717)。

帧率 Frame rate

图形处理器每秒能够刷新几次,也就是每秒能够播放多少帧。

人类眼睛的特殊生理结构,如果画面的帧率高于每秒约10-12帧时,都会认为是连贯的,也就是所谓的视觉暂留。

低帧率会造成视觉卡顿,帧率越高,流畅度越高。

如果你玩过LOL,那么游戏帧数保证稳定在60帧左右或以上,你的游戏画面就是流畅的。

单位

每秒显示的帧数(Frames per Second,FPS)或赫兹(Hz)。

压缩率 Compression rate

经过压缩后文件的大小 / 原始文件的大小 * 100% = 压缩率

压缩率一般是越小越好,但是压得越小,解压时间越长。

分辨率 Image resolution

通常表示称PPI,用于度量图像内数据量多少的一个参数。

像素密度越高,说明像素越密集,5PPI表示每英寸有5个像素,500PPI表示每英寸有500个像素。

PPI的数值高,图片和视频的清晰度就更高。

公式

分辨率 = 单位长度内的像素数量 / 单位长度

单位

DPI(点每英寸)、LPI(线每英寸)、PPI(像素每英寸)和 PPD(PPPixels Per Degree 角分辨率,像素每度)。

是PPD 不是 PDD。

容器格式

MP4

由国际标准化组织(ISO)和国际电工委员会(IEC)下属的”动态图像专家组“(Moving Picture Experts Group,即MPEG)制定。

MP4 十分开放,几乎可以用来描述所有的媒体结构。支持流媒体,被广泛用于 H.264/H.265 视频和 ACC 音频,是高清视频的扛把子。

AVI

AVI(Audio Video Interleaved),是微软用于对抗苹果 QuickTime 的产物。它可以跨多个平台使用,不过体积过于庞大,压缩标准也不统一。

FLV

FLV(Flash Video)是目前最流行的流媒体格式,其文件体积小、封装播放简单,非常适合在网络场景下应用。各大视频网站大多都会使用 FLV 格式。

TS,M3U8

HLS 由 TS 和 M3U8 两部分组成:

  • .m3u8 文件:以 UTF-8 编码的 m3u 文件。
  • .ts 视频文件:一个 m3u8 文件对应着若干个 ts文件。

m3u8 只存放了一些 ts 文件的配置信息和相关路径,而 ts 文件存放了视频的数据。当视频播放时,video 标签会解析 m3u8 并找到对应的
ts 文件进行播放。

不过 HLS 的延时相对较高,延时包含了 TCP 握手、m3u8 文件下载与解析、ts 文件下载与解析等多个步骤。虽然可以缩短列表的长度和限制单个 ts 文件的大小来降低延迟,但是会造成请求次数增加,服务器压力增大。

苹果官方推荐的 ts 时长是 10s,大概会有 30s 的延迟。

WebM

WebM 由 Google 提出,是一种专为 Web 设计的开放,免版税的媒体文件格式。WebM 文件包含使用 VP8 或 VP9 视频编解码器压缩的视频流和使用 Vorbis 或 Opus 音频编解码器压缩的音频流。

OGV

OGV 是 HTML5 中的一个名为 Ogg Theora 的视频格式,起源于 OGG 容器格式(由 Xiph.Org 开源),它不受软件专利的限制。

MOV

MOV 是 QuickTime 中常见的影片格式,拥有着出色的兼容能力,兼容 Macintosh 和 Windows。

编码格式

H.264 (AVC)

H.264 是由国际电信联盟和国际标准化组织/国际电工委员会运动图像专家组联合开发的视频压缩技术或编解码器(如 MPEG-4 Part 10,高级视频编码或 AVC)。它是在 MPEG-4 技术的基础之上建立起来的。广泛应用于网络流媒体数据、网络软件、高清晰度电视、卫星等。

H.265(HEVC)

用来替代 H.264/AVC 编码标准。相同视频文件使用 H.265 编码方式编码后的文件体积大约是 MPEG-4 编码后文件的 1/3。

  • 支持 4k 及更高的分辨率,用户体验好
  • 高压缩能为用户提供更多内容、降低费用、提升下载速度
  • 浏览器支持较差,业界解决方案:libe265.js, FFMpeg + WebAssembly

VP9

VP9 是 Google 为了替换 VP8 并与 H.265/HEVC 竞争所开发的免费、开源的影像编码格式。超过20亿个端点支持VP9解码,包括Chrome,Opera,Edge,Firefox和Android设备以及数百万台智能电视。

VP10 视频编码的技术被收录在开放媒体联盟所领导的 AV1 编码中,因此 Google 表示不会在内部部署或正式发布 VP10。

AV1

AV1(Alliance for Open Media Video 1)是由 AOM(Alliance for Open Media,开放媒体联盟)制定的一个开源、免版权费的视频编码格式,专门为通过网络进行流传输而设计。IETF 也将这项工作标准化为互联网视频编解码器(NetVC)。

AV1 的目标是解决 H.265 昂贵的专利费用和复杂的专利授权问题并成为新一代领先的免版权费的编码标准。它是 Google 制定的 VP9 标准的继任者,也是 H.265 强有力的竞争者。

  • YouTube 已开始尝试在部分影片使用AV1视频格式。
  • 2020年2月5日,Netflix开始在Android设备上有限度使用AV1视频格式播放影片,其压缩效率较原本的VP9提升20%
  • 2020年4月30日,爱奇艺宣布在个人电脑网页浏览器和Android设备上支持AV1视频格式。

参考

❤️爱心三连击

1.看到这里了就点个赞支持下吧,你的是我创作的动力。

2.关注公众号前端食堂,你的前端食堂,记得按时吃饭

3.本文已收录在前端食堂Githubgithub.com/Geekhyt,求个小星星,感谢Star。

查看原文

赞 13 收藏 12 评论 1

童欧巴 赞了文章 · 8月14日

如何解决滚动条scrollbar出现造成的页面宽度被挤压的问题?

引言

页面滚动条造成宽度减小的场景很常见了,由于div块级元素的流动性,其宽度默认为100%的body宽度,但是当容器的高度超过视口宽度时候,页面就会出现滚动条,这个滚动条的宽度就会挤压body的可用宽度,也就是会挤压我们的容器的宽度,造成页面晃动的现象,很不友好,下面就来探讨下如何解决这个滚动条的问题。

滚动条的宽度是多少?

既然要解决滚动条造成的问题那么首先需要了解滚动条,即scrollbar的信息主要就是他的宽度,我们把页面的overflow置为scroll,那么滚动条就会默认占据了空间,下面代码就可以很容易得到其宽度了:

CSS: 先把body的间距置为0

* {
  margin: 0;
  padding: 0;
}
html {
  overflow-y: scroll;
}

JS: 用视口的innerWidth减去body就是滚动条的宽度

console.log('chrome下滚动条的宽度', window.innerWidth - document.body.clientWidth)

可以得出chrome浏览器下,宽度为17px,我在jsfiddle中写的话打印出来是16px,我没有在所有浏览器都去验证,但是各浏览器的值可能略有不同,但都是一个固定的值。以chrome来说,就是在触发页面滚动条时候,会挤压掉17px的空间,那我们就可以从不同角度考虑去解决了。

如何解决?

一、最原始的scroll方法

首先来讲下最原始的方法,其思想是既然在触发滚动条时候会挤压空间,那么直接在没有滚动条的时候也触发不就可以了么,也就是我们上面算宽度时候的设置:

html {
  overflow-y: scroll;
}

这样不论什么时候都有滚动宽度占据空间,不存在挤压的问题了...但是这样做有点蠢,毕竟在不需要滚动条的时候也有那么个丑丑的条子放在右边。但是他的优点在于方便而且没有兼容性的问题,其实很多大网站有时候也就这样用了。。。

二、新属性overlay方法

chrome下overflow有个新的属性值overlay,这个属性简直就是为了这个问题而生,他和auto有点像,但是区别就是在触发滚动条时候并不挤压空间,说得直白点就像是移动端的悬浮滚动条,唯一的区别就是不会像手机上那样自动出现自动消失了,滚动条会遮盖住容器17px的空间。眼见为实用下面代码看一下就知道。
高度还未触发滚动条时候:

* {
  margin: 0;
  padding: 0;
}
html {
  overflow-y: overlay;
}
.container {
  height: 200px;
  padding: 17px;
  background-color: #00b83f;
  text-align: right;
}


<div class="container">
  <h1>我是容器内容</h1>
</div>

效果图如下:
图片描述

然后修改容器高度,触发滚动条:

.container {
  height: 2000px;
  padding: 17px;
  background-color: #00b83f;
  text-align: right;
}

效果图如下:
图片描述

可以看到虽然出现了滚动条但是并未挤压容器的宽度,而是遮住了17px的空间,其实本质上就相当于实现了移动端的滚动条表现。
但是非常遗憾,这个属性值目前只有chrome支持,要是ff/ie都支持,后面也就不用写了,但是据说以后都会加上去支持的,可以说是非常好用了,后面的方法也只是用其他方法实现这个效果而已。

三、利用vw和calc实现

因为100vw是window的宽度,其实就是window.innerWidth, 而容器的宽度100%就是除了滚动条的可用宽度,因此在没有滚动条时候calc(100% - 100vw)就是0,触发滚动条时候其值为负的滚动条宽度,我们将其赋值给容器的margin-right,即可巧妙补偿这个宽度的挤压,在滚动条存在的情况下容器宽度仍然占据整个视口的宽度。

* {
  margin: 0;
  padding: 0;
}
html {
  overflow-y: auto;
  overflow-x: hidden;
}
.container {
  height: 2000px;
  margin-right: calc(100% - 100vw);
  padding: 17px;
  background-color: #00b83f;
  text-align: right;
}

效果如同方法二,很完美,并且兼容性还不错,起码高版本的ie和ff都没问题了。

四、张鑫旭大佬那里抄来的absolute方法

链接在此,这个利用了绝对定位,保证了body的宽度一直保持完整空间。

html {
  overflow-y: scroll; //这是为了兼容ie8,不支持:root, vw
}

:root {
  overflow-y: auto;
  overflow-x: hidden;
}

:root body {
  position: absolute;
}

body {
  width: 100vw;
  overflow: hidden;
}

在一个普通的容器滚动条挤压怎么办?

上面是针对浏览器视口的滚动条方案,但是假如在一个普通div容器中也有如此的需求改怎么办呢?因此此时并没有像100vw这样的值直接获取容器的宽度,只能使用js的方法来检测计算然后再用margin-right做补偿,原理都是一样的,但是我强烈不推荐也不喜欢用js来计算布局...因此在这种情况下勉强委屈下用上面的第一种方法了,如果是chrome下用第二种方法。

.wrap {
    overflow-y: scroll;
    overflow-y: overlay;
}

当然你坚决不能忍的话也可以用js去算吧...本质也是一样的,这里给个链接作为参考

总结

方法主要就是上面的几种,大家根据需求自由选用即可,最重要的是在使用某些新属性的时候多加思考,很多问题的本质并没什么区别,只是用新的工具去做而已。

参考

都在文中了

查看原文

赞 15 收藏 5 评论 0

童欧巴 赞了文章 · 8月6日

React和Vue中,是如何监听变量变化的

React 中

本地调试React代码的方法

  • 先将React代码下载到本地,进入项目文件夹后yarn build
  • 利用create-react-app创建一个自己的项目
  • 把react源码和自己刚刚创建的项目关联起来,之前build源码到build文件夹下面,然后cd到react文件夹下面的build文件夹下。里面有node_modules文件夹,进入此文件夹。发现有react文件夹和react-dom文件夹。分别进入到这两个文件夹。分别运行yarn link。此时创建了两个快捷方式。react和react-dom
  • cd到自己项目的目录下,运行yarn link react react-dom 。此时在你项目里就使用了react源码下的build的相关文件。如果你对react源码有修改,就刷新下项目,就能里面体现在你的项目里。

场景

假设有这样一个场景,父组件传递子组件一个A参数,子组件需要监听A参数的变化转换为state。

16之前

在React以前我们可以使用componentWillReveiveProps来监听props的变换

16之后

在最新版本的React中可以使用新出的getDerivedStateFromProps进行props的监听,getDerivedStateFromProps可以返回null或者一个对象,如果是对象,则会更新state

getDerivedStateFromProps触发条件

我们的目标就是找到 getDerivedStateFromProps的 触发条件

我们知道,只要调用setState就会触发getDerivedStateFromProps,并且props的值相同,也会触发getDerivedStateFromProps(16.3版本之后)

setStatereact.development.js当中

Component.prototype.setState = function (partialState, callback) {
  !(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : void 0;
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
ReactNoopUpdateQueue {
    //...部分省略
    
    enqueueSetState: function (publicInstance, partialState, callback, callerName) {
    warnNoop(publicInstance, 'setState');
  }
}

执行的是一个警告方法

function warnNoop(publicInstance, callerName) {
  {
    // 实例的构造体
    var _constructor = publicInstance.constructor;
    var componentName = _constructor && (_constructor.displayName || _constructor.name) || 'ReactClass';
    // 组成一个key 组件名称+方法名(列如setState)
    var warningKey = componentName + '.' + callerName;
    // 如果已经输出过警告了就不会再输出
    if (didWarnStateUpdateForUnmountedComponent[warningKey]) {
      return;
    }
    // 在开发者工具的终端里输出警告日志 不能直接使用 component.setState来调用 
    warningWithoutStack$1(false, "Can't call %s on a component that is not yet mounted. " + 'This is a no-op, but it might indicate a bug in your application. ' + 'Instead, assign to `this.state` directly or define a `state = {};` ' + 'class property with the desired state in the %s component.', callerName, componentName);
    didWarnStateUpdateForUnmountedComponent[warningKey] = true;
  }
}

看来ReactNoopUpdateQueue是一个抽象类,实际的方法并不是在这里实现的,同时我们看下最初updater赋值的地方,初始化Component时,会传入实际的updater

function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}

我们在组件的构造方法当中将this进行打印

class App extends Component {
  constructor(props) {
    super(props);
    //..省略

    console.log('constructor', this);
  }
}

-w766

方法指向的是,在react-dom.development.jsclassComponentUpdater

var classComponentUpdater = {
  // 是否渲染
  isMounted: isMounted,
  enqueueSetState: function(inst, payload, callback) {
    // inst 是fiber
    inst = inst._reactInternalFiber;
    // 获取时间
    var currentTime = requestCurrentTime();
    currentTime = computeExpirationForFiber(currentTime, inst);
    // 根据更新时间初始化一个标识对象
    var update = createUpdate(currentTime);
    update.payload = payload;
    void 0 !== callback && null !== callback && (update.callback = callback);
    // 排队更新 将更新任务加入队列当中
    enqueueUpdate(inst, update);
    //
    scheduleWork(inst, currentTime);
  },
  // ..省略
}

enqueueUpdate
就是将更新任务加入队列当中

function enqueueUpdate(fiber, update) {
  var alternate = fiber.alternate;
  // 如果alternat为空并且更新队列为空则创建更新队列
  if (null === alternate) {
    var queue1 = fiber.updateQueue;
    var queue2 = null;
    null === queue1 &&
      (queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState));
  } else

    (queue1 = fiber.updateQueue),
      (queue2 = alternate.updateQueue),
      null === queue1
        ? null === queue2
          ? ((queue1 = fiber.updateQueue = createUpdateQueue(
              fiber.memoizedState
            )),
            (queue2 = alternate.updateQueue = createUpdateQueue(
              alternate.memoizedState
            )))
          : (queue1 = fiber.updateQueue = cloneUpdateQueue(queue2))
        : null === queue2 &&
          (queue2 = alternate.updateQueue = cloneUpdateQueue(queue1));
  null === queue2 || queue1 === queue2
    ? appendUpdateToQueue(queue1, update)
    : null === queue1.lastUpdate || null === queue2.lastUpdate
      ? (appendUpdateToQueue(queue1, update),
        appendUpdateToQueue(queue2, update))
      : (appendUpdateToQueue(queue1, update), (queue2.lastUpdate = update));
}

我们看scheduleWork下

function scheduleWork(fiber, expirationTime) {
  // 获取根 node
  var root = scheduleWorkToRoot(fiber, expirationTime);
  null !== root &&
    (!isWorking &&
      0 !== nextRenderExpirationTime &&
      expirationTime < nextRenderExpirationTime &&
      ((interruptedBy = fiber), resetStack()),
    markPendingPriorityLevel(root, expirationTime),
    (isWorking && !isCommitting$1 && nextRoot === root) ||
      requestWork(root, root.expirationTime),
    nestedUpdateCount > NESTED_UPDATE_LIMIT &&
      ((nestedUpdateCount = 0), reactProdInvariant("185")));
}
function requestWork(root, expirationTime) {
  // 将需要渲染的root进行记录
  addRootToSchedule(root, expirationTime);
  if (isRendering) {
    // Prevent reentrancy. Remaining work will be scheduled at the end of
    // the currently rendering batch.
    return;
  }

  if (isBatchingUpdates) {
    // Flush work at the end of the batch.
    if (isUnbatchingUpdates) {
      // ...unless we're inside unbatchedUpdates, in which case we should
      // flush it now.
      nextFlushedRoot = root;
      nextFlushedExpirationTime = Sync;
      performWorkOnRoot(root, Sync, true);
    }
    // 执行到这边直接return,此时setState()这个过程已经结束
    return;
  }

  // TODO: Get rid of Sync and use current time?
  if (expirationTime === Sync) {
    performSyncWork();
  } else {
    scheduleCallbackWithExpirationTime(root, expirationTime);
  }
}

太过复杂,一些方法其实还没有看懂,但是根据断点可以把执行顺序先理一下,在setState之后会执行performSyncWork,随后是如下的一个执行顺序

performSyncWork => performWorkOnRoot => renderRoot => workLoop => performUnitOfWork => beginWork => applyDerivedStateFromProps

最终方法是执行

function applyDerivedStateFromProps(
  workInProgress,
  ctor,
  getDerivedStateFromProps,
  nextProps
) {
  var prevState = workInProgress.memoizedState;
      {
        if (debugRenderPhaseSideEffects || debugRenderPhaseSideEffectsForStrictMode && workInProgress.mode & StrictMode) {
          // Invoke the function an extra time to help detect side-effects.
          getDerivedStateFromProps(nextProps, prevState);
        }
      }
      // 获取改变的state
      var partialState = getDerivedStateFromProps(nextProps, prevState);
      {
        // 对一些错误格式进行警告
        warnOnUndefinedDerivedState(ctor, partialState);
      } // Merge the partial state and the previous state.
      // 判断getDerivedStateFromProps返回的格式是否为空,如果不为空则将由原的state和它的返回值合并
      var memoizedState = partialState === null || partialState === undefined ? prevState : _assign({}, prevState, partialState);
      // 设置state
      // 一旦更新队列为空,将派生状态保留在基础状态当中
      workInProgress.memoizedState = memoizedState; // Once the update queue is empty, persist the derived state onto the
      // base state.
      var updateQueue = workInProgress.updateQueue;

      if (updateQueue !== null && workInProgress.expirationTime === NoWork) {
        updateQueue.baseState = memoizedState;
      }
}

Vue

vue监听变量变化依靠的是watch,因此我们先从源码中看看,watch是在哪里触发的。

Watch触发条件

src/core/instance中有initState()

/core/instance/state.js

在数据初始化时initData(),会将每vue的data注册到objerserver

function initData (vm: Component) {
  // ...省略部分代码
  
  // observe data
  observe(data, true /* asRootData */)
}
/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 */
export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    // 创建observer
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

来看下observer的构造方法,不管是array还是obj,他们最终都会调用的是this.walk()

constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
      // 遍历array中的每个值,然后调用walk
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

我们再来看下walk方法,walk方法就是将object中的执行defineReactive()方法,而这个方法实际就是改写setget方法

/**
* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
}

/core/observer/index.js
defineReactive方法最为核心,它将set和get方法改写,如果我们重新对变量进行赋值,那么会判断变量的新值是否等于旧值,如果不相等,则会触发dep.notify()从而回调watch中的方法。

/**
 * Define a reactive property on an Object.
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // dep当中存放的是watcher数组 
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) { 
    // 如果第三个值没有传。那么val就直接从obj中根据key的值获取
    val = obj[key]
  }

  let childOb = !shallow && observe(val)
    
    Object.defineProperty(obj, key, {
    enumerable: true,
    // 可设置值
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        // dep中生成个watcher
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    // 重点看set方法
    set: function reactiveSetter (newVal) {
      // 获取变量原始值
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      // 进行重复值比较 如果相等直接return
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        // dev环境可以直接自定义set
        customSetter()
      }
        
      // 将新的值赋值
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      // 触发watch事件
      // dep当中是一个wacher的数组
      // notify会执行wacher数组的update方法,update方法触发最终的watcher的run方法,触发watch回调
      dep.notify()
    }
  })
}

小程序

自定义Watch

小程序的data本身是不支持watch的,但是我们可以自行添加,我们参照Vue的写法自己写一个。
watcher.js

export function defineReactive (obj, key, callbackObj, val) {
  const property = Object.getOwnPropertyDescriptor(obj, key);
  console.log(property);

  const getter = property && property.get;
  const setter = property && property.set;

  val = obj[key]

  const callback = callbackObj[key];

  Object.defineProperty(obj, key, {
    enumerable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      
      return value
    },
    set: (newVal) => {
      console.log('start set');
      const value = getter ? getter.call(obj) : val

      if (typeof callback === 'function') {
        callback(newVal, val);
      }

      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      console.log('finish set', newVal);
    }
  });
}

export function watch(cxt, callbackObj) {
  const data = cxt.data
  for (const key in data) {
    console.log(key);
    defineReactive(data, key, callbackObj)
  }
}

使用

我们在执行watch回调前没有对新老赋值进行比较,原因是微信当中对data中的变量赋值,即使给引用变量赋值还是相同的值,也会因为引用地址不同,判断不相等。如果想对新老值进行比较就不能使用===,可以先对obj或者array转换为json字符串再比较。

//index.js
//获取应用实例
const app = getApp()

import {watch} from '../../utils/watcher';

Page({
  data: {
    motto: 'hello world',
    userInfo: {},
    hasUserInfo: false,
    canIUse: wx.canIUse('button.open-type.getUserInfo'),
    tableData: []
  },
    onLoad: function () {
    this.initWatcher();
  },
  initWatcher () {
    watch(this, {
      motto(newVal, oldVal) {
        console.log('newVal', newVal, 'oldVal', oldVal);
      },

      userInfo(newVal, oldVal) {
        console.log('newVal', newVal, 'oldVal', oldVal);
      },

      tableData(newVal, oldVal) {
        console.log('newVal', newVal, 'oldVal', oldVal);
      }
    });    
  },
  onClickChangeStringData() {
    this.setData({
      motto: 'hello'
    });
  },
  onClickChangeObjData() {
    this.setData({
      userInfo: {
        name: 'helo'
      }
    });
  },
  onClickChangeArrayDataA() {
    const tableData = [];
    this.setData({
      tableData
    });
  }
})

参考

广而告之

本文发布于薄荷前端周刊,欢迎Watch & Star ★,转载请注明出处。

欢迎讨论,点个赞再走吧 。◕‿◕。 ~

查看原文

赞 8 收藏 5 评论 0

童欧巴 发布了文章 · 8月3日

这些手写代码会了吗?少年

图怪兽_aba93a3814353520e3427ff5d8f73f4c_26222.png
观感度:🌟🌟🌟🌟🌟

口味:蟹黄豆腐

烹饪时间:5min

本文已收录在前端食堂同名仓库Githubgithub.com/Geekhyt,欢迎光临食堂,如果觉得酒菜还算可口,赏个 Star 对食堂老板来说是莫大的鼓励。

从略带银丝的头发和干净利落的步伐我察觉到,面前坐着的这个面试官有点深不可测。我像往常一样,准备花 3 分钟的时间给面试官来一套昨天晚上精心准备的自我介绍。我自信且得意的诉说着对过往项目所付出的心血,所做的优化取得了怎样的成果,为公司提高了多少的收入。。。

显然,面试官对我说的数字很感兴趣,嘴角微微上扬,经过了一番细节的探讨和技术的对线后。面试官拿出了一张纸。

手写代码。

注重基础的面试官是靠谱的,为了征服他,我一边讲解着实现原理一边写出了代码。

手写 call

call 和 apply 的区别:call 方法接收的是一个参数列表,apply 方法接收的是一个包含多个参数的数组。

  • 1.context 存在就使用 context,否则是 window
  • 2.使用 Object(context)context 转换成对象,并通过 context.fnthis 指向 context
  • 3.循环参数,注意从 1 开始,第 0 个是上下文,后面才是我们需要的参数
  • 4.将参数字符串 pushargs
  • 5.字符串和数组拼接时,数组会调用 toString 方法,这样可以实现将参数一个个传入,并通过 eval 执行
  • 6.拿到结果返回前,删除掉 fn
Function.prototype.call = function(context) {
    context = context ? Object(context) : window;
    context.fn = this;
    let args = [];
    for (let i = 1; i < arguments.length; i++) {
        args.push('arguments['+ i +']');
    }
    let res = eval('context.fn('+ args +')');
    delete context.fn;
    return res;
}

手写 apply

  • 1.apply 无需循环参数列表,传入的 args 就是数组
  • 2.但是 args 是可选参数,如果不传入的话,直接执行
Function.prototype.apply = function(context, args) {
    context = context ? Object(context) : window;
    context.fn = this;
    if (!args) {
        return context.fn();
    }
    let res = eval('context.fn('+ args +')');
    delete context.fn;
    return res;
}

手写 bind

  • 1.bind 的参数可以在绑定和调用的时候分两次传入
  • 2.bindArgs 是绑定时除了第一个参数以外传入的参数,args 是调用时候传入的参数,将二者拼接后一起传入
  • 3.如果使用 new 运算符构造绑定函数,则会改变 this 指向,this 指向当前的实例
  • 4.通过 Fn 链接原型,这样 fBound 就可以通过原型链访问父类 Fn 的属性
Function.prototype.bind = function(context) {
    let that = this;
    let bindArgs = Array.prototype.slice.call(arguments, 1);
    function Fn () {};
    function fBound(params) {
        let args = Array.prototype.slice.call(arguments) ;
        return that.apply(this instanceof fBound ? this : context, bindArgs.concat(args));
    }
    Fn.prototype = this.prototype;
    fBound.prototype = new Fn();
    return fBound;
}

手写 new

  • 1.Constructor 就是 new 时传入的第一个参数,剩余的 arguments 是其他的参数
  • 2.使用obj.__proto__ = Constructor.prototype 继承原型上的方法
  • 3.将剩余的 arguments 传给 Contructor ,绑定 this 指向为 obj,并执行
  • 4.如果构造函数返回的是引用类型,直接返回该引用类型,否则返回 obj
const myNew = function() {
    let Constructor = Array.prototype.shift.call(arguments);
    let obj = {};
    obj.__proto__ = Constructor.prototype;
    let res = Constructor.apply(obj, arguments);
    return res instanceof Object ? res : obj;
}

手写 instanceOf

  • 1.在 left 的原型链中层层查找,是否有原型等于 prototype
  • 2.确定边界条件,如果 left === null,即找到头没找到返回 falseright === left,即找到返回 true
  • 3.left = left.__proto__,不停的向上查找
const myInstanceof = function(left, right) {
    right = right.prototype;
    left = left.__proto__;
    while (true) {
        if (left === null) {
            return false;
        }
        if (right === left) {
            return true;
        }
        left = left.__proto__;
    }
}

手写 Object.create

  • 新建一个空的构造函数 F ,然后让 F.prototype 指向 obj,最后返回 F 的实例
const myCreate = function (obj) {
  function F() {};
  F.prototype = obj;
  return new F();
}

掘金尾图.png

查看原文

赞 19 收藏 15 评论 0

童欧巴 发布了文章 · 7月27日

「提升效率」精选MAC应用推荐,让你搬砖效率翻倍!

观感度:🌟🌟🌟🌟🌟

口味:椒盐藕片

烹饪时间:5min

本文已收录在前端食堂同名仓库Githubgithub.com/Geekhyt,欢迎光临食堂,如果觉得酒菜还算可口,赏个 Star 对食堂老板来说是莫大的鼓励。

最近看到的一段话很有感触,日复一日的低效率工作只会消磨你的热情,而巨大的时间成本会让你错失很多机会。

在这波涛汹涌的浪潮中,作为叶子结点的我们要学会主动提高生产效率。本文是提升效率系列专栏的第二弹。

温馨提示:下面每个标题都可以点击直达官网。

(迷妹一号:欧巴太贴心了!)

0.SwitchHosts!

便捷切换 Hosts 神器,再也不用蠢蠢的将 hosts 文件复制到桌面再覆盖回去。一个字,好爽!

1.Transmit

记得小时候第一个玩具就是小卡车,而这款小卡车是 MAC 上最好用的FTP客户端,轻松帮你把管理服务器上的文件。

2.Postman

程序员届无人不知,无人不晓的 Postman,接口联调必备神器。

3.Wireshark

老司机必备,至今为止最强大的抓包工具,feature 如图:

4.MarginNote 3

信息碎片化的时代如何静下心来好好读一本书?

MarginNote 3 可以在你读书的同时建立你的思维脑图,多维度构建知识链接,提升阅读学习生产力。欧巴认为它是最好用的学习App之一。

强推!!!

5.WonderPen

超级干净,沉浸式体验的写作App。欧巴的每一篇原创文章的初稿就是用它来创作哒。

6.uTools

uTools 是日常使用频率最高的工具之一。它具有丰富的插件,通过简单配置可以打造属于你自己的工具集合。用官网的话说:

当你熟悉它后,能够为你节约大量时间,让你可以更加专注地改变世界。

真香~

7.Sourcetree

据说是 Mac 上最好用的git仓库管理客户端。

8.XMind ZEN

思维脑图终极效率神器。

官方 Slogn:大脑的全功能瑞士军刀,笔和纸的高科技替代者。

new.png

查看原文

赞 5 收藏 3 评论 0

认证与成就

  • 获得 441 次点赞
  • 获得 4 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 4 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

注册于 2018-03-05
个人主页被 6.4k 人浏览