Giter Club home page Giter Club logo

mynote's People

Contributors

dependabot[bot] avatar huatten avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar

Forkers

github-xiaofei

mynote's Issues

【一次贴吧面试】如何实现图片的宽高自适应

1. 需求场景

我们在实现的时候,需要轮播图充满我们的轮播区域,通常我们的做法是banner设置固定宽高,然后超出隐藏,img设置max-width:100%,这样虽然能实现充满轮播区域,可是我们会发现,虽然宽度自适应了,高度并没有自适应,有时候我们会发现纵向上图片底部不见了,因为如果图片太高,当图片宽度100% 自适应的时候,高度也会按照宽度自适应的比例进行自适应(缩小或放大),此时高度超出banner区域被隐藏了,视觉上会造成图片丢失的感觉。

2. 涉及知识点

在默认的水平文档流方向下,CSS margin和padding属性的垂直方向的百分比值都是相对于 宽度 计算的,所以不管是 padding-left 和 padding-right 还是 padding-top 和 padding-bottom 都是基于父元素宽度的百分比。

3. 原理思路

首先我们要明白一件事情:子元素的 padding 属性百分比的值是相对于父容器的宽度而言的。明白这一点特别重要。因此我们需要计算出图片的宽高比例,本文案例中使用了一张 750x366 的图片,宽高比就是 750 / 366 = 2.05,假设将图片父容器宽度设置为100%,那么图片的高度就是 100% / 2.05 = 48.8%,所以图片的高度按比例缩放就是 基于父元素宽度的 48.8%

4. 子元素的 padding 属性实现方案

举例代码:

.contain{
   max-width: 750px;
   margin: 10px auto;
   overflow: hidden;
}
.banner{
   width: 100%;
   padding-top: 48.8%;  //当当当!划重点!
   position: relative;
}
.contain img{
   position: absolute;
   top: 0;
   left: 0;
   width: 100%;
   height: 100%;
}

页面结构

 <div class="contain">
     <div class="banner">
         <img src="./image/banner.jpg" alt="banner"/>
    </div>
 </div>

5.vw 视口单位(viewport units) 实现方案

首先要明白:视口单位(viewport units)是相对于视口(viewport) 尺寸而言的。 100vw 等于视口宽度的 100% ,1vw 相对于等于视口宽度的 1%。因此这个特性就特别适合在移动端实现宽高等比自适应容器。

举例代码:

.contain{
   width: 100%;
   height: 48.8vw;  //当当当!划重点!
   position:relative;
}
.contain img{
   position: absolute;
   top: 0;
   left: 0;
   width: 100%;
   height: 100%;
}

页面结构

 <div class="contain">
     <img src="./image/banner.jpg" alt="banner"/>
 </div>

这个方法相对于图片等比缩放特性有个优点就是,无论图片是否加载完成,容器的高度始终在那里,不会造成页面抖动而影响用户体验,还有不会造成页面重绘提升性能。
当然在实际使用过程要考虑容器的 margin,padding 等因素,所以计算高度比例时估计需要 calc() 函数配合稍微多点计算。但是你如果要支持很老的手机,那就没办法使用了。

【Js数组方法】模拟实现系列

1.find()模拟实现

find() 方法返回数组中满足提供的测试函数的 第一个 元素的值,否则返回 undefined,方法的回调函数可以接受三个参数,依次为当前的值 value、当前的位置 index 和原数组 arr

const arr = [1,2,3,4,5]
const found = arr.find(item => { item > 3 })
console.log(found)   //4

遍历数组数据并将其回调,判断回调只要有一个结果为true则返回。

Array.prototype.myFind = function (fn) {
  for (let i = 0; i < this.length; i++) {
    if (fn(this[i], i, this)) {
      return this[i]
    }
  }
}

2.findIndex()模拟实现

findIndex() 方法返回数组中满足提供的测试函数的 第一个 元素的索引,否则返回 -1,方法的回调函数可以接受三个参数,依次为当前的值 value、当前的位置 index 和原数组 arr

const arr = [1, 2, 3, 4, 5]
const fundIndex = arr.findIndex(item => item > 2)
console.log(fundIndex)

遍历数组数据并将其回调,返回满足条件的数据的索引。

Array.prototype.myFindIndex = function (fn) {
  for (let i = 0; i < this.length; i++) {
    if (fn(this[i], i, this)) {
      return i
    }
  }
  return -1
}

3.sort()模拟实现

sort() 方法用 原地算法 对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值(Unicode编码)来排序的。

const months = ['March', 'Jan', 'Feb', 'Dec'];
months.sort();
console.log(months);  //["Dec", "Feb", "Jan", "March"]

//对于需要降序排列或非字符串排序,该方法就不能很好的执行了。
const array1 = [1, 30, 4, 21, 100000];
array1.sort();
console.log(array1); //[1, 100000, 21, 30, 4]

//sort有一个可选参数,它能帮我们解决这个问题,通过为sort传入一个函数,sort根据函数返回值进行排序。  
const array1 = [1, 30, 4, 21, 100000];
array1.sort();
console.log(array1); //[1, 4, 21, 30, 100000] 

const array1 = [1, 30, 4, 21, 100000,  'js', 'css'];
array1.sort();
console.log(array1); //[1, 100000, 21, 30, 4, 'css', 'js']

const array1 = [1, 30, 4, 21, 100000,  'js', 'css'];
array1.sort(function(a, b){
  return a - b
})
console.log(array1); //[1, 4, 21, 30, 100000, "js", "css"]

sort的实现其实很像是一个简单的冒泡排序。

Array.prototype.mySort = function (fn) {
  for (let n in this) { //比较次数
    let isSorted = true; //假设已经排好
    for (let m in this) { //每次比较之后会得出一个最大值
      if (typeof fn === "function") { //传入回调
        if (fn(this[n], this[m]) < 0) {
          //交换
          const temp = this[n];
          this[n] = this[m]
          this[m] = temp
          isSorted = false
        }
      } else { //直接调用
        if (this[n] < this[m]) {
          //交换
          const temp = this[n]
          this[n] = this[m]
          this[m] = temp
          isSorted = false
        }
      }
    }
    if (isSorted) { //排序结束
      break
    }
  }
  return this
}

【小技巧】chrome 监听touch类事件滑动时候警告:Unable to preventDefault inside passive event listener

最近在开发vue组件库-slider滑块组件的过程中,按住拖动滑块比较卡顿而且老是报如下错误:

[Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/5093566007214080

情景复现:当我们给 document 添加了touch事件的监听器的时候,如果同时在handler内部调用了event.preventDefault(),这时候浏览器(Chrome56+)就会报如上warning。
翻译过来就是:

不能给passive(被动的)事件监听器preventDefault,因为它被认为是passive。

简而言之就是:

  • 由于浏览器必须要在执行事件处理函数之后,才能知道有没有掉用过preventDefault ,这就导致了浏览器不能及时响应滚动,略有延迟。
  • 所以为了让页面滚动的效果如丝般顺滑,从 chrome56 开始,在 windowdocumentbody 上注册的 touchstarttouchmove 事件处理函数,会默认为是 passive: true,浏览器忽略 preventDefault 就可以第一时间滚动了。

举例:

window.addEventListener('touchmove', func) 效果和下面一句一样
window.addEventListener('touchmove', func, { passive: true })

但是这就导致了一个问题:如果在 windowdocumentbody元素的 touchstarttouchmove 事件处理函数中调用 e.preventDefault() ,会被浏览器忽略掉,并不会阻止默认行为。
测试:

window.addEventListener('touchmove', e => e.preventDefault()) // 在 chrome56 中,照样滚动,而且控制台会有提示

那么如何解决这个问题呢?不让控制台提示,而且 preventDefault() 有效果呢?有如下两种方案:

方案一: 注册处理函数时,用如下方式,明确声明为不是被动的

window.addEventListener('touchmove', func, { passive: false }) //注意第三个参数

在 touch 的事件监听方法上绑定第三个参数 { passive: false },通过传递 passivefalse 来明确告诉浏览器:事件处理程序调用 preventDefault 来阻止默认滑动行为。

方案二:应用 CSS 属性 touch-action

这样任何触摸事件都不会产生默认行为,但是 touch 事件照样触发,具体参考 touch-action

div{
  touch-action: none;
}

知识点延伸- passive event listener

2016 年 Google I/O 上提出的概念,目的是用来提高页面的滑动流畅度,可以通过这篇文章来了解一下 passive event listener

A new feature in the DOM spec that enable developers to opt-in to better scroll performance by eliminating the need for scrolling to block on touch and wheel event listeners.

Developers can annotate touch and wheel listeners with {passive: true} to indicate that they will never invoke preventDefault.

简而言之就是当我们在滚动页面的时候(通常是我们监听touch事件的时候),页面其实会有一个短暂的停顿(大概200ms),浏览器不知道我们是否要preventDefault,所以它需要一个延迟来检测,这就导致了我们的滑动显得比较卡顿。从Chrome 51开始,passive event listener 被引进了Chrome,我们可以通过对 addEventListener 的第三个参数设置 { passive: true } 来避免浏览器检测这个我们是否有在touch事件的handler里调用preventDefault。在这个时候,如果我们依然调用了 preventDefault,就会在控制台打印一个警告,告诉我们这个 preventDefault 会被忽略。

当我们给 addEventListener 的第三个参数设置了 { passive: true },这个事件监听器就被称为 passive event listener

从Chrome 56开始,如果我们给 document 绑定 touchmove 或者 touchstart 事件的监听器,这个 passive 是会被默认设置为 true 以提高性能,但是我们大多数人并不知道这点,并且依旧调用了 preventDefault。这并不会导致什么页面崩溃级的错误,但是这可能导致我们忽略了一个页面性能优化的点,特别是在移动端这种更加重视性能优化的场景下。

参考

【软件安装】Mac下安装启动nginx

Mac下安装启动nginx

第一次在Mac上安装nginx,下面是我的安装过程在此做个记录,同时对nginx的配置文件有个初步的认识。

一、安装工具

Homebrew 是一款Mac OS平台下的软件包管理工具,拥有安装、卸载、更新、查看、搜索等很多实用的功能,查看Homebrew是否安装:命令行可以输入brew -v,回车即可看到版本号。

如果没看到则需要在终端输入下面的命令即可安装:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"

成功以后会出现 **Installation successful! **,关于 Homebrew 的更多细节和使用方法,可自行搜索进行学习。

二、安装nginx
1.查询nginx是否存在

首先查看是否已经安装了nginx,终端输入下面的命令,看不到绿色的对勾说明没安装。

brew search nginx

131584249259_ pic_hd

######2.查询nginx信息

在命令行输入brew info nginx

141584249315_ pic_hd

我们可以看到nginx在本地还未安装(Not installed),同时还可以看到nginx的来源(From),Docroot默认为/usr/local/var/www,默认端口已在/usr/local/etc/nginx/nginx.conf中设置为8080,这样nginx就可以在没有sudo的情况下运行,nginx将加载/usr/local/etc/nginx/servers中的所有文件,要立即启动nginx并在登录时重新启动可以使用命令: brew services start nginx,如果你不想或者不需要后台服可以运行命令:nginx

3.安装nginx

终端输入命令

brew install nginx

这个过程比较漫长如果中途失败重新执行命令即可,细心观察安装过程可以发现依赖Homebrew安装nginx帮我们下载了很多相关东西。

201584278813_ pic_hd

4.查看安装目录

根据第2步反馈的结果我们可以在终端运行/usr/local/etc/nginx/来查看安装目录

211584280651_ pic

或者不习惯命令行查看文件和目录的话可以直接打开访达文件管理器,在终端输入:open /usr/local/etc/nginx/

221584280863_ pic_hd

我们可以看到nginx.conf 这个配置文件,以后会经常用到它。但是我们并没有看见nginx被安装在哪个目录了,此时在终端运行/usr/local/Cellar可以看见nginx的文件夹,其实这个才是nginx被安装到的目录

231584281338_ pic_hd

我们可以看见一个以当前安装的nginx版本号为命名的文件夹,进入1.17.3_1/bin目录可以找到nginx的可执行启动文件,因此可以总结一下:

  • nginx默认配置文件路径:/usr/local/etc/nginx/
  • nginx默认执行文件路径: /usr/local/Cellar/nginx/1.17.3_1/bin/
  • 主页的文件路径:/usr/local/var/www

同时我们可以观察到在1.17.3_1目录下面有一个html的文件夹快捷方式

251584281949_ pic_hd

显示原身可以看到它指向的就是/usr/local/var/www目录,上面第2步有提到关键字 Dcroot

281584282965_ pic_hd

三、启动nginx
  1. 在命令行输入 nginx 命令可启动服务

  2. /usr/local/Cellar/nginx/1.17.3_1/bin/目录找到启动程序,双击就可以启动

打开浏览器访问localhost:8080,正常情况下到这一步就会能看到nginx的欢迎界面啦!

291584284415_ pic_hd

💁‍♂️ 打开 http:localhost:8080 后如果也页面出现 403 forbidden 的错误,很可能是nginx目录下面(usr/local/var/www)缺少了index.html欢迎页面文件,可以自己找到目录新建一个index.html放进去就可以解决。

四、ngnix常用命令
  • 查看nginx相关命令 nginx -h
  • 查看nginx版本号 nginx -v
  • 启动nginx nginx
  • 正常停止nginx nginx -s quit
  • 强制停止nginx nginx -s stop
  • 测试配置文件 nginx -t
  • 重新启动配置文件 nginx -s reload
  • 打开nginx配置文件 cat /usr/local/etc/nginx/nginx.conf
五、ngnix配置文件

终端输入命令:cat /usr/local/etc/nginx/nginx.conf

ngnix配置文件结构图:

Untitled1

下面是一些基本配置的解读:

#全局块

#定义Nginx运行的用户和用户组
#user  nobody;  

#nginx进程数
worker_processes  1;

#全局错误日志定义类型,[ debug | info | notice | warn | error | crit ]
#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#进程文件指定pid的存储文件位置
#pid        logs/nginx.pid;

#eventsevents {
    #指定ngnix进程的连接数上限
    worker_connections  1024;
  	#内核模型 [ kqueue | rtsig | epoll | /dev/poll | select | poll ] epoll模型是Linux 2.6以上版		  本内核中的高性能网络I/O模型如果跑在FreeBSD上面就用kqueue模型use epoll
}

#httphttp {
    #用以包含其他配置文件 文件扩展名与文件类型映射表
    include       mime.types;
  	#指定默认类型此处是二进制流
    default_type  application/octet-stream;
		
  	#日志的相关定义
    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';
  	#定义日志的格式后面定义要输出的内容
  	#1.$remote_addr  $http_x_forwarded_for记录客户端的ip地址
		#2.$remote_user 用于记录客户端的用户名称
  	#3.$time_local 用于记录访问的时间时区
  	#4.$request 用于记录请求的URL和Http协议
  	#5.$status 用于记录请求的状态
  	#6.$body_bytes_sent 用于记录发送给客户端的文件主体大小
  	#7.$http_referer 用于记录页面是从哪个页面地址访问过来的
  	#8.$http_user_agent 用于记录客户端浏览器的相关信息
  	
  	#连接日志的路径    指定的日志格式放在最后
    #access_log  logs/access.log  main;
  
		#只记录更为严重的错误日志减少IO压力
    error_log logs/error.log crit;
  
    #关闭日志
    #access_log  off;
  
  	#默认编码
    #charset utf-8;
  
    #服务器名字的hash表大小
    server_names_hash_bucket_size 128;
    #客户端请求单个文件的最大字节数
    client_max_body_size 8m;
    #指定来自客户端请求头的hearerbuffer大小
    client_header_buffer_size 32k;
    #指定客户端请求中较大的消息头的缓存最大数量和大小large_client_header_buffers 4 64k;
  
  	#指定是否开启高效文件传输模式
    sendfile        on;
  
    #防止网络阻塞
    #tcp_nopush   on;
		tcp_nodelay   on;
    
  	#指定连接保持活动的超时时长 单位s
    keepalive_timeout  65;
  	#客户端请求头读取超时时间
    client_header_timeout 10;
    #设置客户端请求主体读取超时时间
    client_body_timeout 10;
    #响应客户端超时时间
    send_timeout 10;
  	
  	#gzip压缩功能设置
  	#指定是否开启gzip压缩
    #gzip  on;
		#最小压缩文件大小
    gzip_min_length 1k;
  	#压缩缓冲区
    gzip_buffers 4 16k;
    #压缩版本默认1.1前端如果是squid2.5请使用1.0gzip_http_version 1.0;
    #压缩等级 1-9 等级越高压缩效果越好节约宽带但CPU消耗大
    gzip_comp_level 2;
    #压缩类型默认就已经包含text/html所以下面就不用再写了写上去也不会有问题但是会有一个warngzip_types text/plain application/x-javascript text/css application/xml;
    #前端缓存服务器缓存经过压缩的页面
    gzip_vary on;
  
  	#serverserver {
      	#指定端口号
        listen       8080;
      	#指定ip地址或者域名多个域名之间空格隔开
        server_name  localhost;
				
      	#指定默认的网页编码格式 若网页格式与此不同将被自动转码
        #charset koi8-r;
				
      	#指定访问日志的路径
        #access_log  logs/host.access.log  main;
      
				#location块  用于指定各个URL的处理方式
        location / {
          	#表示默认由ngnix处理root指定根目录的位置相对/绝对
            root   html;
          	#index定义首页索引文件名称 按顺序匹配
            index  index.html index.htm;
        }
      	
				#错误信息返回页面
        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
				
      	#访问以.php结尾的文件则自动派发给127.0.0.1
        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}
				
        #php脚本请求全部转发给FastCGI处理
        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}
      
				#禁止访问.ht页面 
        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server Https虚拟主机定义
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}
    include servers/*;
}
1.main全局块

从配置文件开始到events块之间的内容,配置服务器整体运行的配置指令,一般有运行nginx服务器的用户组,nginx进程pid存放路径,日志存放路径,配置文件引入,允许生成worker process数等。比如上面的配置第二行:

#nginx开启的进程数 建议设置为等于CPU总核心数。可以和worker_cpu_affinity配合使用
worker_processes  1; 
2.events块

影响ngnix服务器与用户的网络连接。

#eventsevents {
    #指定ngnix进程的连接数上限
    worker_connections  1024;
  	#内核模型 [ kqueue | rtsig | epoll | /dev/poll | select | poll ] epoll模型是Linux 2.6以上版		  本内核中的高性能网络I/O模型如果跑在FreeBSD上面就用kqueue模型use epoll
}
3.http块

可以嵌套多个server,配置代理,缓存,日志定义、gzip压缩等绝大多数功能和第三方模块的配置

  • 3.1 http全局块
    http {
        #用以包含其他配置文件 文件扩展名与文件类型映射表
        include       mime.types;
      	#指定默认类型,此处是二进制流
        default_type  application/octet-stream;
    		
      	#日志的相关定义
        #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
        #                  '$status $body_bytes_sent "$http_referer" '
        #                  '"$http_user_agent" "$http_x_forwarded_for"';
    
      	#连接日志的路径    指定的日志格式放在最后
        #access_log  logs/access.log  main;
      
    		#只记录更为严重的错误日志,减少IO压力
        error_log logs/error.log crit;
      
        #关闭日志
        #access_log  off;
      
      	#默认编码
        #charset utf-8;
      
      	#指定是否开启高效文件传输模式
        sendfile        on;
      
        #防止网络阻塞
        #tcp_nopush   on;
    		tcp_nodelay   on;
        
      	#指定连接保持活动的超时时长 单位s
        keepalive_timeout  65;
      	#客户端请求头读取超时时间
        client_header_timeout 10;
        #设置客户端请求主体读取超时时间
        client_body_timeout 10;
        #响应客户端超时时间
        send_timeout 10;
      	
      	#gzip压缩功能设置
      	#指定是否开启gzip压缩
        #gzip  on;
    		#最小压缩文件大小
        gzip_min_length 1k;
      	#压缩缓冲区
        gzip_buffers 4 16k;
        #压缩版本(默认1.1,前端如果是squid2.5请使用1.0)
        gzip_http_version 1.0;
        #压缩等级 1-9 等级越高,压缩效果越好,节约宽带,但CPU消耗大
        gzip_comp_level 2;
        #压缩类型,默认就已经包含text/html,所以下面就不用再写了,写上去也不会有问题,但是会有一个warn。
        gzip_types text/plain application/x-javascript text/css application/xml;
        #前端缓存服务器缓存经过压缩的页面
        gzip_vary on;
        }
    
    

    http 全局块配置的指令包括文件引入、MIME-TYPE 定义、日志定义、连接超时时间、单链接请求数上限、gzip压缩设置、http_proxy服务全局设置、负载均衡设定等等。

  • 3.2 server块

    每个 http 块可以包括多个 server 块,而每个 server 块就相当于一个虚拟主机;

    而每个 server 块也分为全局 server 块,以及可以同时包含多个 location 块。

    • 3.2.1 全局 server 块

      最常见的配置是本虚拟机主机的监听配置和本虚拟主机的名称或 IP 配置。

    • 3.2.2 location 块

      http服务中,某些特定的URL对应的一系列配置项,一个 server 块可以配置多个location 块。

【兼容性】iPhone下input设置disabled属性后字体颜色修改

最近在做项目的时候发现,将input或textarea设置为disabled后,在iphone手机上样式将被覆写,解决方案如下:

input:disabled, textarea:diabled {
    -webkit-text-fill-color: #ccc;
    opacity: 1;
    color: #ccc;
}

以上样式将覆盖其系统默认设置的值,能够实现android和ios的兼容性,使其表现一致。否则以上样式在iPhone下面表现为白色,安卓下为灰色。其中,-webkit-text-fill-color 是用来做填充色使用的,如果有设置这个值,则color属性将不生效。

【小技巧】二维数组按行排序

/*
 * @params arr
 * @returns arr
 */
function sortArr(arr) {
  let goNext = true
  let entries = arr.entries();
  while (goNext) {
    let result = entries.next()
   // next{ value: Array(4), done: false }
    if (result.done !== true) {
      // 用于指示迭代器是否完成:在每次迭代时进行更新而且都是false,直到迭代器结束done才是true
      result.value[1].sort((x, y) => x - y)
      goNext = true
    } else {
      goNext = false
      break
    }
  }
  return arr
}
var arr = [[1, 34], [456, 2, 3, 44, 234], [4567, 1, 4, 5, 6], [34, 78, 23, 1]]
sortArr(arr)
/*[
  [ 1, 34 ],
  [ 2, 3, 44, 234, 456 ],
  [ 1, 4, 5, 6, 4567 ],
  [ 1, 23, 34, 78 ]
]*/

【小技巧】全局处理图片加载失败

监听图片的 error 事件

图片加载失败,会抛出一个异常事件 error,可以通过监听 error 事件的方式来对图片进行降级处理。

<img src="http://sina.com.cn/img/abc.png" id="logo" />

监听error事件为当前图片设定默认图

let img = document.getElementById('logo');
img.addEventListener('error',function(e){
    e.target.str = 'http://xxx.xxx.xxx/default.png'; 
})

问题:

  • 每张图片都需要通过 JS 进行获取,并且监听 error 事件,对于大量图片的场景并不适用
  • 无法监听到动态产生的img标签
  • 给每一个img元素都绑定事件处理函数带来的页面性能损耗

内联事件来监听 error 事件

<img src="http://sina.com.cn/img/abc.png" onerror="this.src='http://xxx.xxx.xxx/default.png'"/>

问题:仍然需要手动的向 img 标签中添加内联事件,在实际开发过程中,很难保证每张图片都不漏写。

全局监听

利用事件冒泡的机制来监听?可惜error事件并不会冒泡,但是可以捕获的。(注:DOM2级事件中,error事件是会冒泡的,DOM3级事件中,error事件不会冒泡。)
DOM事件发生的三个阶段:

  • 捕获阶段: 从根节点开始顺序而下,检测每个节点是否注册了事件处理函数。在标准浏览器中,我们可以通过指定 addEventListener 的第三个参数 useCapturetrue,以使事件处理函数在该阶段运行。(低版本IE中无法指定事件处理函数在该阶段执行)
  • 目标阶段:触发在目标对象本身注册的事件处理函数,也称正常事件派发阶段
  • 冒泡阶段:从目标节点到根节点,检测每个节点是否注册了事件处理函数。在标准浏览器中,我们可以通过指定 addEventListener 的第三个参数 useCapturetrue,以使事件处理函数在该阶段运行。

因此,首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到的事件。最后一个阶段是冒泡阶段。

我们上文中的监听图片自身的 error 事件,实际上在事件流中是处于目标阶段。

对于 img 的 error 事件来说,是无法冒泡的,但是是可以捕获的,我们的实现如下:

document.addEventListener("error", function (e) {
    let el = e.target;
    //图片加载异常引起
    if (el.tagName.toLowerCase() === 'img') {
      el.src = "http://xxx.xxx.xxx/default.png";
    }
  }, true /*指定事件处理函数在捕获阶段执行*/);

注意:由于低版本IE中 attachEvent 方法无法指定事件处理函数在捕获阶段执行,所以,该方案在低版本IE中不能适用。

效果如下:

WeChatd51f566fe5d2dc0283a0116d0697a00a

当网络出现异常的时候,必然会出现什么网络图片都无法加载的情况,这样就会导致我们监听的 error 事件 被无限触发,所以我们可以设定一个计数器,当达到期望的错误次数时停止对图片赋予默认图片的操作:

document.addEventListener("error", function (e) {
    let el = e.target,
      times = Number(el.dataset.times) || 0, //失败次数
      allTimes = 3; // 总失败次数,此时设定为3
    if (el.tagName.toLowerCase() === 'img') {
      if (times >= allTimes) {
       // 断网
        el.src = "http://xxx.xxx.xxx/error.png";
      } else {
       // 加载异常
        el.dataset.times = times + 1;
        el.src = "http://xxx.xxx.xxx/default.png"; 
      }
    }
  }, true);

修改默认占位图路径模拟断网:

WeChat4e812ab89bdb5cd4c3f5fc6464e8ccc0

参考:如何处理图片加载失败

【面试题】Js字符串与二进制的相互转换

字符串转二进制

charCodeAt

charCodeAt() 方法可返回指定位置的字符的 Unicode 编码。这个返回值是 0 - 65535 之间的整数。
语法:

str.charCodeAt(index) // index: 必需。表示字符串中某个位置的数字,即字符在字符串中的下标。

toString

toString(radix)方法可返回表示该数字的指定进制形式的字符串。
语法:

const n = 97
n.toString(radix)  // "1100001"   radix支持 [2, 36] 之间的整数。默认为10

结合上述两个api我们可以整理思路

  • 将字符串转换成 ASCII 码
  • ASCII 码转换成二进制

因此最终简单代码实现:

// 字符串 =>  ASCII 码 => 二进制
'a'.charCodeAt(0).toString(2)  //  "a" => 97 => "1100001"

实现一个字符串转二进制函数

//将字符串转换成二进制形式,中间用空格隔开
function strToBinary(str) {
  if (!str) {
    return;
  }
  let total2Str = "";
  for (let i = 0; i < str.length; i++) {
    let binaryStr = str.charCodeAt(i).toString(2);
    if (binaryStr == "") {
      total2Str = binaryStr;
    } else {
      total2Str = total2Str + " " + binaryStr;
    }
  }
  return total2Str;
}
strToBinary('abc') // 1100001 1100010 1100011

二进制转字符串

parseInt

parseInt(string, radix) 将一个字符串 string 转换为 radix 进制的整数, radix 为介于2-36之间的数。
语法:

parseInt(string, radix) 

fromCharCode

fromCharCode() 可接受一个指定的 Unicode 值,然后返回一个字符串
语法:

String.fromCharCode(numX,numX,...,numX) //  numX必需。一个或多个 Unicode 值,即要创建的字符串中的字符的 Unicode 编码。

结合两个api简单代码实现:

let str2 = '1100001' 
let num10 = parseInt(str2, 2)  // 97
String.fromCharCode(num10) // a

实现一个二进制转字符串函数

//将二进制字符串转换成Unicode字符串
function binaryToStr(str) {
  let result = [];
  let list = str.split(" ");
  for (let i = 0; i < list.length; i++) {
    let item = list[i];
    let asciiCode = parseInt(item, 2);
    let charValue = String.fromCharCode(asciiCode);
    result.push(charValue);
  }
  return result.join("");
}
binaryToStr("1100001 1100010 1100011")  // "abc"

【软件安装】mac系统使用Homebrew安装nvm

1.Homebrew介绍

Homebrew是Mac OSX上的软件包管理工具,能在Mac中方便的安装软件或者卸载软件,相当于linux下的apt-get、yum神器;Homebrew可以在Mac上安装一些OS X没有的UNIX工具,Homebrew将这些工具统统安装到了 /usr/local/Cellar 目录中,并在 /usr/local/bin 中创建符号链接。
简单来说,Homebrew提供 Apple 没有预装但你需要的东西。详情请见 Homebrew官网

2.nvm介绍

在我们的日常开发中经常会遇到这种情况:手上有好几个项目,每个项目的需求不同,进而不同项目必须依赖不同版的 NodeJS 运行环境。如果没有一个合适的工具,这个问题将非常棘手。nvm 应运而生,nvm 是 Mac 下的 node 管理工具,有点类似管理 Ruby 的 rvm。

3.安装Homebrew

Homebrew的安装很简单,只需在终端下输入如下指令:

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Homebrew安装成功后,会自动创建目录 /usr/local/Cellar 来存放Homebrew安装的程序。 这时你在命令行状态下面就可以使用 brew 命令了,安装完成如下:

271573795703_ pic

我们可以输入命令 brew -v 来查看是否安装成功,如果出现版本号说明安装ok啦

281573795822_ pic

下面列一下常见的使用操作:

  • 安装软件:brew install 软件名,例:brew install wget
  • 搜索软件:brew search 软件名,例:brew search wget
  • 卸载软件:brew uninstall 软件名,例:brew uninstall wget
  • 更新所有软件:brew update⚠️通过 update 可以把包信息更新到最新,不过包更新是通过git命令,前提是你的机子装了git,如果没有可以先通过 brew install git 命令安装git)
  • 更新具体软件:brew upgrade 软件名 ,例:brew upgrade git
  • 显示已安装软件:brew list / brew ls
  • 查看软件信息:brew info/home 软件名 ,例:brew info git / brew home git(⚠️brew home指令是用浏览器打开官方网页查看软件信息)
  • 查看那些已安装的程序需要更新: brew outdated
  • 显示包依赖:brew reps

4. 安装nvm

  • 4.1 安装之前先卸载干净nodejs

卸载brew安装的 node/npm

brew uninstall node 

卸载官网下载安装的 node/npm

npm ls -g --depth=0 #查看已经安装在全局的模块,以便删除这些全局模块后再按照不同的 node 版本重新进行全局安装
sudo rm -rf /usr/local/lib/node_modules #删除全局 node_modules 目录
sudo rm /usr/local/bin/node #删除 node
cd  /usr/local/bin && ls -l | grep "../lib/node_modules/" | awk '{print $9}'| xargs rm #删除全局 node 模块注册的软链
  • 4.2 使用Homebrew安装nvm(不推荐)

终端直接输入命令:

brew install nvm

回车之后即可进行安装,安装成功提示:

==> Summary
🍺  /usr/local/Cellar/nvm/0.35.1: 7 files, 148.0KB, built in 8 seconds

安装成功之后,还不能直接使用nvm命令,否则会报错提示 zsh: command not found: nvm ,还必须在你的 .bash_profile 加入以下这行,让你可以直接在shell使用nvm指令,将以下命令复制到终端执行:

echo "source $(brew --prefix nvm)/nvm.sh" >> .bash_profile

修改之后,需要重新定向来源,复制以下命令并执行:

. ~/.bash_profile

此时在终端输入:

nvm --version

可以看到当前nvm的版本号,表明nvm安装成功啦

291573798414_ pic

  • 4.3 安装node

查看Node所有版本,用nvm ls-remote命令,如下:

macbook@MacBookdeMacBook-Pro  ~  nvm ls-remote
       v0.1.14
       v0.1.15
       v0.1.16
        ...
       v7.7.1
       v7.7.2
       v13.1.0

正常安装的话会比较慢,可以使用下面的命令:

nvm install v10.15.3

如果没翻墙安装很慢可以推荐国内淘宝镜像:

NVM_NODEJS_ORG_MIRROR=https://npm.taobao.org/mirrors/node nvm install 10

需要哪个版本,就在最后将10换成对应的版本号就行,默认下载版本中最新的版本号!不过在安装过程中可能会出现下面这样的情况

301573799587_ pic_hd

运行命令 nvm use --delete-prefix v10.15.3,此时提示安装成功

311573799724_ pic_hd

但是切换窗口打开新的命令行,会提示 zsh: command not found: node,好崩溃啊,至于为什么用homebrew安装会出现上面的问题,可参考Github上的一个issue
查看nvm文档发现:文档中Installation 那一小节倒数第二行有一句 Homebrew installation is not supported.,👴💊☁️🌶️重新卸载了按照官网推荐方式安装!

5. nvm官网推荐安装方式

⚠️安装之前先卸载node,先卸载node!运行官网的命令:

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.32.1/install.sh | bash

完成后nvm就被安装在了~/.nvm下啦,接下来就需要配一下环境变量了
首先进入当前用户的home目录

cd ~

新建.bash_profile

touch .bash_profile

编辑.bash_profile文件

open -e .bash_profile

输入下面的配置保存退出

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"

然后更新刚配置的环境变量

source .bash_profile

这里注意一下,如果你使用了 zsh 的话,就不用按照上面四步配置了,而需要在 ~/.zshrc 这个配置文件中配置,打开 ~/.zshrc,在最后一行加上:

export NVM_DIR="$HOME/.nvm"
  [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" # This loads nvm

这一步的作用是每次新打开一个bash,nvm都会被自动添加到环境变量中了。
完成后输入source ~/.zshrc重新启动一下配置。
此时输入 nvm 看到如下说明安装成功了,切换新的命令窗口也依旧生效✌️。

331573804999_ pic_hd

接下来就是使用nvm啦,例举一些常见命令:

  • nvm ls-remote 列出所有可安装的版本

  • nvm install <version> 安装指定的版本,如nvm install v8.14.0

  • nvm uninstall <version> 卸载指定的版本

  • nvm ls 列出所有已经安装的版本

  • nvm use <version> 切换临时使用指定的版本

  • nvm current 显示当前使用的版本

  • nvm alias default <version> 设置永久默认node版本

【兼容性】解决vue2.0下低版本浏览器白屏问题思路及方法

公司的项目来来回回折腾了几个月,终于终于终于要上线了,先不说中间有多曲折各种新建分支,各种来回变更,👴简直8⃣️想提了..... 不过还好算是能上线一部分模块了🤮,正好有事就请假了,结果在火车上跟我说有的用户手机白屏没有请求,这锅是真的甩不掉了,开始怀疑是不是因为之前项目打包慢,更改了webpack配置采用 DllPlugin 优化打包性能导致,于是赶紧掏出电脑连上热点一把唆恢复webpack配置,然并卵。放开测试环境的 VConsole ,查看控制台发现红色一道杠,在开发中没有问题,但是打包后放到手机低版本中就会白屏因此想都8⃣️用想,白屏基本都是es6语法导致的,打开dist查看下打包后首页的js包含es6箭头函数 () => 若干处,可以确定到,代码中es6语法没有完全解析导致白屏。
其实之前项目一开始就加过 babel-polyfill 。相关配置操作如下:

  1. 安装 babel-polyfill
npm install --save-dev babel-polyfill
  1. 修改webpack配置,添加 babel-polyfill
//webpack.base.conf.js
entry: {
    app: ["babel-polyfill", "./src/main.js"]
 },
  1. 在页面入口配置main.js中引入 babel-polyfill
import 'babel-polyfill'

其实关于 babel-polyfill 插件的使用是正确的,之所以出现这些未转义的代码,我在想是不是因为babel-loader的编译范围所限,因为我的static文件夹也用了es6的语法,所以也要加入编译,于是继续修改配置:

// webpack.base.conf.js
{
    test: /\.js$/,
    loader: 'babel-loader',
    include: [
      resolve('src'), //默认编译文件
      resolve('static'), //编译静态文件
    ]
}

md,结果打包后的首页js还是包含es6箭头函数语法,看到网上一篇文章说是用es6-promise解决,用法配置如下:

//安装
npm install es6-promise--save

//使用 main.js
import Es6Promise from 'es6-promise'
Es6Promise.polyfill()

其实这个时候我也想到不是了,如果是promise导致的不兼容那不应该仅仅是首页报错。
这个时候首页推荐位置的产品位轻轻划过,突然一只🦙飘过,开发首页这位👴是不是 swiper的引入有问题啊,打开首页推荐位代码一看果然是

import Swiper from 'swiper' 

为什么我能一眼定位到是swiper引起的,因为之前在开发日历组件的时候,采用的是swiper 4.0版本,当时就踩坑🌶️, Unexpected token: name (Dom7)..... 关键词就是 Dom7!Swiper4.0自带有Dom7库,因此无需另外加载Jquery即可对Dom7/Jquery对象使用以下常用操作。这些操作适用于Swiper5和Swiper4,但是要注意DOM7的更新。因此有结果🌶️:Dom7使用的是 es6 的语法,但是在使用过程中并没有转换成 es5。接下来就记录一下几种解决办法:

  • 方法一:降低swiper的版本,可以用3.4.2
    但是swiper和swiper4之间的区别挺大,这样改又要去看swiper3的api,重新调整相关代码,比较恶心也不方便
  • 方法二:直接引入编译好的兼容es6的swiper.min.js(推荐)
import Swiper from 'swiper/dist/js/swiper.min.js';

ps:不要偷懒,该有的不要省!我问👴为什么不按照我之前的方式引入,收到的答复:我觉得你那样引入好麻烦

  • 方法三:babel转换的时候加入swiper模块
// vue-cli2.0  webpack.base.conf.js
{
    test: /\.js$/,
    loader: 'babel-loader',
    include: [resolve('src'), resolve('test'), resolve('node_modules/dom7'), resolve('node_modules/swiper')]
}

//vue-cli3.0 vue.config.js
module.exports = {
  chainWebpack: config => {
    config.rule('js').include.add(/node_modules\/(dom7|swiper)\/.*/)
  }
}

至此,解决了,以后遇见白屏的问题就按照上面的办法一步步排查吧,微博的webview这么🌶️🐔🐎,👴💊☁️🌶️.....

【随记】eslint-plugin-vue升级到9.16.1后 `vue/no-setup-props-destructure`规则提示问题

vue3项目eslint依赖eslint-plugin-vue升级到 V9.16.1 之后,关于props有很多的报错如下:

ESLint: Getting a value from the `props` in root scope of `<script setup>` will cause the value to lose reactivity.(vue/no-setup-props-destructure)

大概意思就是 从<script setup>的根作用域中的props中获取值将导致该值失去响应式!!!

<script setup>
const props = defineProps({
  count: {
    type: Number,
    default: 0,
  },
})
console.log(props.count)
              ^^^^^^ Failing
console.log(ref(props.count))
              ^^^^^^ Failing
</script>

在9.15.1之前的版本没这个问题,这一处调整就是在9.16.0这个 版本更新 引起的。
关于这一点也有不同的争议,可以去 issue #2259PR #2244 下面看讨论。比如下面来自 @volarname评论

i strongly agree with a completely new opt-in rule instead of this update because vue/no-setup-props-destructure its not correct for this, and i will absolutely opt-out this rule, because in some cases you want the prop as initial non-reactive value for the component

const props = defineProps(['initialCounter'])
const counter = ref(props.initialCounter)

this is a correct code, also in vue docs

如果不想启用这个规则,可以在eslint中关闭 vue/no-setup-props-destructure 规则:

'vue/no-setup-props-destructure': 'off'

或者我们使用 toRef

<script setup>
const props = defineProps({
  count: {
    type: Number,
    default: 0,
  },
})

const count = toRef(() => props.count)
console.log(count)
</script>

【vue项目】vue组件监听滚动事件

我们在做vue项目的时候很少会直接操作dom了,但是有一些需求不得不操作dom,比如这个需求:监听滚动事件实现某元素吸顶或者固定位置显示。

首先在mounted钩子中给window添加滚动监听事件:

export default{
  mouted(){
  	window.addEventListener('scroll', this.handleScroll)
	}
}

然后在方法中定义监听滚动执行的方法handleScroll,监听tab元素到页面顶部的距离并监听滚动距离如果大于元素到顶部的距离时,表示此时tab元素要吸顶:

export default{
  methods:{
    handleScroll(){
      let scrollY = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
      let offsetTop = document.querySelector('.tab-bar').offsetTop;
      let headerHeight = document.querySelector('.header').offsetHeight; //头部导航高度
      this.tabBarFixed = scrollY > (offsetTop - headerHeight) ? true : false
    }
  }
}

这个时候就能实现效果了,但是如果你的页面正好作为子组件,那么直接在mounted里addEventListener监听滚动是监听不到的,只能监听到父组件的滚动。

查阅文档得知:element.addEventListener(event, function, useCapture),第三个参数是一个布尔值,指定是使用事件冒泡还是事件捕获。默认是false使用冒泡传播。

因为子组件滚动是捕获事件,父组件滚动是冒泡事件,addEventListener第三参数默认false只监听冒泡事件,所以子组件的捕获事件默认监听不到,改为true才可以。

export default{
  mouted(){
  	window.addEventListener('scroll', this.handleScroll, true);
	}
}

此时子组件父组件的滚动都能被监听到了,只不过前者是捕获后者是冒泡,通过 Event事件对象得知,冒泡事件的event.bubbles值是true,捕获事件的值是false,因此可以通过这一点来判断是父组件滚动还是子组件滚动。

export default{
  methods:{
    handleScroll(e){
      e = event || ev;
      if(e.bubbles){ 
        console.log('父组件滚动');
      }else{
        console.log('子组件滚动');
      }
    }
  }
}

最后记得在组件销毁的时候移除监听事件。

export default{
  destroyed(){
  	window.removeEventListener('scroll', this.handleScroll);
	}
}

【小技巧】css3遮罩实现png图片变色

需求:将黑色图标变成红色图标。

image

原理:
CSS的mask是基于透明度实现遮罩层效果的,也就是实色区域显示,透明区域隐藏。因此只需要把目标图标的颜色作为背景色,然后将原始图标作为遮罩图片,即可实现效果。缺点是IE不支持。

代码:

<span class="icon"></span>
.icon{
    display: inline-block;
    width: 64px;
    height: 64px;
    -webkit-mask: url("happy.png") no-repeat;
    mask: url("happy.png") no-repeat;
    -webkit-mask-size: 100% 100%;
    mask-size: 100% 100%;
}

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.