xmax

xmax 查看完整档案

其它编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

认真却怂

个人动态

xmax 发布了文章 · 9月19日

XHTML元素类型

XHTML元素分类

根据css显示状态,XHTML元素被分为
三种类型:块状元素内联(行内)元素可变元素

块状/块级元素(block element)

三大特点:

1.默认情况下,块状元素占一行,两个相邻块状元素不会并列显示;默认情况下,块状元素会按顺序从上到下排列。

2.块状元素可以定义宽度和高度。

3.块状元素一般作为其他元素的容器,开始标签和结束标签之间可以嵌套内联元素和块状元素。一个块级元素可以看作一个盒子,盒子里可以装其他的物件。

  • 常用块级元素:

    • div--最常用的块级元素
    • dl--和dt-dd 搭配使用的块级元素
    • form--交互表单
    • h1-h6--大标题
    • ol--有序列表
    • p--段落
    • ul--无序列表
    • fieldset--表单字段集
    • colgroup-col--表单列分组元素
    • table-tr-td--表格及行-单元格

内联元素/行内元素(inline element)

三大特点:

1.一行可以显示多个内联元素

2.内联元素不能定义宽和高,它的宽度、高度由元素内的内容的高度和宽度决定。

3.内联元素也会遵循盒模型基本规则,可以定义padding,border,margin,background等属性,但个别属性不能正确显示;(padding-top/bottom;margin-top/bottom;)

  • 常用行内元素

    • a –-超链接(锚点)
    • b -- 粗体(不推荐)
    • br -- 换行
    • i -- 斜体
    • em -- 强调
    • font -- 字体设定(不推荐)
    • img -- 图片
    • input -- 输入框
    • label -- 表单标签
    • span -- 常用内联容器,定义文本内区块
    • strong -- 粗体强调
    • sub -- 下标
    • sup -- 上标
    • textarea -- 多行文本输入框
    • select -- 项目选择

其他元素

如:行内块级(inline-block),列表项(list-item)

XHTML元素类型转换

  • 通过css的display属性来改变默认的显示类型

display属性与属性值 (18个属性值)

属性值:block/inline/inline-block/none/list-item
/table-header-group/table-footer-group

作用:该属性设置或检索元素生成的类型。

  • block:块状显示,类似在元素后面添加换行符,其他元素不能在其后并列显示。(将元素转为块状元素,使该元素拥有块状元素的特点;)
  • inline:内联显示,多个元素可以在一行内并列显示。(元素转换为内联元素)
  • inline-block:行内块级元素显示,一行显示多个、可以设置宽高。
  • none:隐藏元素,不占位置。
  • list-item:将元素转换成列表项。

1.当元素设置了float属性后,就相当于给该元素加了display:inline-block;声明(设置宽高、一行显示多个)。

2.大部分块元素display属性值默认为block,其中列表li的默认值为list-item。

3.大部分内联元素的display属性值默认为inline,其中img,input,textarea默认为inline-block。

内联元素/行内元素在容器中垂直居中

<!--html-->
<div>
    <p></p>
    <span></span>
</div>

<!--css-->
div {
    width: 200px;
    height: 200px;
    text-align: center;
    background: pink;
}

p {
    display: inline-block;
    width: 40px;
    height: 40px;
    vertical-align: middle;
    background: green;
}

span {
    vertical-align: middle;
    width: 0;
    height: 100%;
    display: inline-block;
}

三个条件:
1:给父元素加上text-align:center;
2:当前元素转成行内块元素(display:inline-block;)再设置vertical-align:middle;
3:在当前元素的后面加上同级元素span;并对span进行vertical-align:middle;width:0;height:100%;display:inline-block;

文字在容器中垂直居中

<!--css-->
div {
    width: 200px;
    height: 200px;
    text-align: center;
    line-height: 200px;
}

<!--html-->
<div>
    <p>测试测试</p>
</div>

关键:
text-align: center;
line-height: 200px;

line-height的值保持和height一致。

置换元素(行内可置换元素)与非置换元素

1.置换元素:

浏览器根据元素的标签和属性,来决定元素的具体显示内容。

例如:
浏览器会根据img标签的src属性的值来读取图片信息并显示出来,而如果查看(x)html代码,则看不到图片的实际内容,input标签的type属性来决定是显示输入框,还是单选按钮等; (x)html中的img、input、textarea、select都是置换元素,这些元素往往没有实际的内容,即是一个空元素。
  • 置换元素在其显示中生成了框,这也就是有的内联元素(img,input)能够设置宽高的原因。

2.不可替换元素(非置换元素):

(x)html 的大多数元素是不可替换元素,即其内容直接表现在客户端(如浏览器)。

查看原文

赞 0 收藏 0 评论 0

xmax 收藏了文章 · 8月13日

8 种用于前端性能分析工具

作者:Mahdhi Rezvi
译者:前端小智
来源:blog.bitsrc

点赞再看,微信搜索 【大迁世界】 关注这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

我们可以编写一些漂亮,且吸引人的网站,但如果该网站无法快速加载到浏览器中,人们往往会跳过它。 尽管有许多性能规则,但归根结底,这全都取决于加载时间。

根据 Jakob Nielson说法,在网建的网站时,需要注意以下几点:

  • 小于100毫秒加载速度才是爽的
  • 100ms到300ms 感觉良好
  • 一秒钟大概是用户思路不被打断的极限。用户会感觉到延迟,但还可以接受
  • 47%的用户希望网页能在两秒或更短的时间内加载
  • 40% 的用户如果网页加载超过 3 秒,表示会放弃该网站
  • 10秒左右是用户注意力的极限。 大多数用户会在10秒后离开你的网站

如上所述,即使在最差的网络带宽上,也需要确保页面尽快加载。 但这个说起来容易做起来难。

为了能帮助更好的实现这一目标,这里推荐了几个性能分析工具。

1. PageSpeed Insights

这是一个免费的服务,分析网页的内容,然后提出建议,使该网页更快。它为您提供了关键指标,如第一个内容绘制,总阻塞时间和更多。度量标准被分类为Field DataOrigin Summary,Lab Data,OpportunitiesDiagnosticsPassed Audits。它还为我们提供了进一步改进的建议。

clipboard.png

2. Lighthouse

Lighthouse 是一个开源的自动化工具,用于改进网络应用的质量。 你可以将其作为一个 Chrome 扩展程序运行,或从命令行运行。 你为 Lighthouse 提供一个您要审查的网址,它将针对此页面运行一连串的测试,然后生成一个有关页面性能的报告。

从此处,您可以失败的测试为指示器,看看可以采取哪些措施来改进您的应用。

注意: Lighthouse 目前非常关注 Progressive Web App (PWA) 功能,如“添加到主屏幕”和离线支持。不过,此项目的首要目标是针对网络应用质量的各个方面提供端到端审查。

运行 Lighthouse 的方式有两种:作为 Chrome 扩展程序运行,或作为命令行工具运行。 Chrome 扩展程序提供了一个对用户更友好的界面,方便读取报告。 命令行工具允许您将 Lighthouse 集成到持续集成系统。

clipboard.png

3. WebPageTest

WebPageTest是一个在线的免费性能评测网站,支持IE,Chrome,使用真正的浏览器(IE和Chrome)和真实的消费者连接速度,从全球多个地点运行免费网站速度测试。可以运行简单的测试或执行高级测试,包括多步骤事务、视频捕获、内容阻塞等等。还将依据测试结果提供丰富的诊断信息,包括资源加载瀑布图,页面速度优化检查和改进建议,会给每一项内容一个最终的评级。

WebpageTest 主要提供了Advanced Testing、simple Testing、Visual Comparison、Traceroute四个功能,我们主要关注Advanced Testing。

如何使用WebPageTest

  • 填写需要测试的URL
  • 填写Test Location,下拉选择即可,可选移动端设备(包括Android、IOS),可选PC端(分地区,每个地区可支持的浏览器不同)
  • 可选支持的浏览器
  • 点击右侧START TEST

clipboard.png

4. Pingdom

我最喜欢的功能是分析后的的摘要,其中会为我们提供有关网站内容和请求的摘要。 我发现这对于了解网页上提供的内容有很大帮助。

clipboard.png

5. Sitespeed.io

Sitespeed.io 是一款开源的Web性能测试工具,用来衡量Web网站的综合性能,帮助开发和测试人员分析网页的加载速度和渲染性能。

Sitespeed.io通过驱动浏览器(如:Chrome、Firefox)进行测试,然后从开发者的站点收集多个页面的数据,并根据最佳实践等规则来分析这些网页,然后将结果以HTML报告的形式输出。

Sitespeed.io 满足了一个完整的Web性能测试工具所需的3个关键功能:

  1. 使用真实的浏览器测试Web站点,模拟真实的用户进行请求连接,收集以用户为中心的重要指标,如:响应速度指标、第一视觉呈现
  2. 可分析页面的组成,并给出相应性能反馈,增加终端用户的使用体验友好性
  3. 通过收集和保存页面组成的数据,便于跟踪定位

clipboard.png

6. Calibre

Caliber 是一款多功能的性能监控套件,可帮助你监控和审核网站的性能。 它还允许你通过指定测试服务器的位置,管理模拟的广告首选项甚至模拟移动设备来模拟现实条件。 它还允许你设置预算,并通过为你提供性能下降来帮助你将预算保持在预算之内。

clipboard.png

7. SpeedCurve

SpeedCurve 既可以让你追踪竞争对手的性能表现,也可以追踪自己的性能表现。

使用 SpeedCurve 时,你可以查看某个因素在不同站点的速度表现。对于移动用户来说,他们希望网站在手机上加载起来要快于电脑,如果感到加载迟缓,往往会迅速关上网页,所以,网站的响应速度对他们很重要。

SpeedCurve 还提供了综合监控。综合监控是在受控环境中模拟你的网站。你可以自定义选项,比如网络速度、设备、操作系统等等。

clipboard.png

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

8. SpeedTracker

SpeedTracker是一个运行在WebPageTest之上的工具,可在你的网站上进行定期的性能测试,并直观显示各种性能指标随时间变化的方式。 这使你可以不断评估网站,并查看新功能如何影响网站的性能。 你还可以定义预算并通过电子邮件和Slack获取警报。

clipboard.png

人才们的 【三连】 就是小智不断分享的最大动力,如果本篇博客有任何错误和建议,欢迎人才们留言,最后,谢谢大家的观看。


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://blog.bitsrc.io/perfor...

交流

文章每周持续更新,可以微信搜索 【大迁世界 】 第一时间阅读,回复 【福利】 有多份前端视频等着你,本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,欢迎Star。

查看原文

xmax 收藏了文章 · 8月13日

mongoDB 常用语法使用

mongoDB 常用语法使用

删除数据库或集合

db.集合.drop( ):删除整个集合,这个在实际工作中一定要谨慎使用,如果是程序,一定要二次确认。

db.dropDatabase( ):删除整个数据库,在删除库时,一定要先进入数据库,然后再删除。实际工作中这个基本不用,实际工作可定需要保留数据和痕迹的。

批量插入

   db.test.insert([  
     {"age":1},  
     {"age":2},  
     {"age":3}  
    ])
修改update

基础update修改器

$set修改器 (用来修改一个指定的键值(key))

 db .集合名称.update({"id":"1"},{"$set":{age:21}})    

修改嵌套内容

db.集合名称.update({"id:1"},$set:{user.name:"xiaoming"})

$unset用于将key删除

db.集合名称.update({id:1},{$unset:{age:""}})

$inc 用于计算并且修改

db.集合名称.update({id:1},{$inc:{age:-2}}) //本身减2

$multi选项 (用于批量插入/循环插入)

db.集合名称。update({},{sex:'男'},{$multi:true})

$upsert选项** (有更新/没有添加)

db.集合名称.update({},{$set:{sex:"男"},{$upsert:true}})

update数组修改器 (例如:集合中有一个空数组:arr=[])

$push (追加数组)

db.集合名称.update({id:1},{$push:{arr:"hello"}}) // 相当于 arr.push("hello")

嵌套属性追加数组

db.集合名称.update({id:1},{$push:{user.arr:"hello"}})

$ne 查找是否存在 (存在不追加,不存在追加)

db.集合名称.update({id:1},{arr:{$ne:"hello"},{$push:{arr."hello"}})

$addToSet (类似于$ne,她更简单一些)

db.集合名称.update({id:1},{$addToSet:{arr:"hello"}})

$each 批量追加

var newArr=["hello","world"];  
db.集合名称.update({id:1},{$addToSet:{arr:{$each:newArr}})

$pop 删除数组

 db.集合名称.update({id:1},{$pop:{arr:1}}) // 1 代表尾部删除  \-1 代表头部删除

数组指定位数修改 (类似于对象形式,毕竟数组也是对象嘛)

db.集合名称.update({id:1},{$set:{arr.1:"world"}})

状态返回

     db.集合名称.update({id:1},{$set:{age:18}})  
            var status=db.runcommand({getLastError:true})  
            printjson(status)   
            返回:{  
             "connectionId" : 1,  
             "updatedExisting" : true,  // 判断是否操作成功  
             "n" : 2,  
             "syncMillis" : 0,  
             "writtenTo" : null,  
             "err" : null,  
             "ok" : 1  
             }

操作安全 findAndModify 翻译: 找到并修改

   var findAndModifyObj ={  
             findAndModify:"集合名称",  
             query:{id:1}, // 通过什么查找  
             sort:false, //排序  
             remove:false, 是否删除 与update冲突 只能写一个  
             upsert:false, 没有找到是否添加  
             fields:[],//需要返回的字段  
             update:{$set:{age:18}}, //更新  
             new:true    //更新完成,需要查看结果,如果为false不进行查看结果  
            }  
            var result=db.runCommand(findAndModifyObj);  
            ​  
            printjson(result)

查询 find()

简单查询

db.集合名称.find() //全部查询  
db.集合名称.find({id:1}) //根据id等于1查询 

筛选字段

db.集合名称.find({id:1},{name:true,age:false}) //返回数据中不含有age

不等修饰符

image.png

db.集合名称.find({id:{$lt:5}}) //查找id字段小于5的所有数据

$in 查询一个区间

db.集合名称.find({id:{$in:[1,6]}}) // 查询id字段在1到6之间的数据

$or 条件 或

db.集合名称.find({id:{$or:[  
             {$in:[1,6]},  
             {age:18}  
            ]}}) // 查询id字段在1到6之间或年龄为18岁的数据

$and 条件 与

db.集合名称.find({id:{$and:[  
             {$in:[1,6]},  
             {age:18}  
            ]}}) // 查询id字段在1到6之间并且年龄为18岁的数据

find() 数组查询

db.集合名称.find({arr:['hello','world']}) //精确查找 只有arr等于['hello','world']  
            ​  
db.集合名称.find({arr:'hello'}) //模糊查找 arr中含有'hello'即可  
            ​  
db.集合名称.find({arr:{$all:['hello','world']}}) //并且关系 含有'hello'并且含有'world' 参能满足条件  
            ​  
db.集合名称.find({arr:{$in:['hello','world']}}) //或关系 含有'hello'或者含有'world' 参能满足条件  
            ​  
db.集合名称.find({arr:{$size:2}}) //根据数组长度查找  
            ​  
db.集合名称.find({},{arr:{$slice:2}}) //截取 返回数据只显示数组前两位 最后一项的话 直接写-1即可

find的参数使用方法

  • query:这个就是查询条件,MongoDB默认的第一个参数。
  • fields:(返回内容)查询出来后显示的结果样式,可以用true和false控制是否显示。
  • limit:返回的数量,后边跟数字,控制每次查询返回的结果数量。
  • skip:跳过多少个显示,和limit结合可以实现分页。
  • sort:排序方式,从小到大排序使用1,从大到小排序使用-1。

limit与skip可用于表格分页

dbd .workmate.find(
    {},  //查询条件           
    {name:true,age:true,_id:false} //返回内容
    ).limit(1).skip(2).sort({age:1}); 

hansNext() 是否有下一条数据

var data=db.项目名称.find()
while(data.hasNext()){
    printjson(data.next)
}
data.forEach(function(data){
    printjson(data)
})

数据库管理:创建,修改,删除用户

创建用户:

db.createUser({
    user:"用户名",
    pwd:"123456",
    customData:{
        name:'随便起名',
        age:18,
    },
    roles:['read'] // 只有读的权限
})

当然我们还可以单独配置一个数据库的权限,比如我们现在要配置list数据库的权限为读写:

db.createUser({
    user:"用户名",
    pwd:"123456",
    customData:{
        name:'随便起名',
        age:18,
    },
    //对list数据库有读写的权限 其他只有读的权限~~~~
    roles:[
        {
            role:"readWrite",
            db:"list"
        },
        'read'
    ]
})

内置角色:

  1. 数据库用户角色:read、readWrite;
  2. 数据库管理角色:dbAdmin、dbOwner、userAdmin;
  3. 集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManage;
  4. 备份恢复角色:backup、restore;
  5. 所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase
  6. 超级用户角色:root
  7. 内部角色:__system

查找用户信息

db.system.users.find()

删除用户:

db.system.users.remove({user:"用户名"})

建权:

有时候我们要验证用户的用户名密码是否正确,就需要用到MongoDB提供的健全操作。也算是一种登录操作,不过MongoDB把这叫做建权。

db.auth("jspang","123456")

如果正确返回1,如果错误返回0。(Error:Authentication failed。)

启动建权

重启MongoDB服务器,然后设置必须使用建权登录。

mongod --auth

启动后,用户登录只能用用户名和密码进行登录,原来的mongo形式链接已经不起作

查看原文

xmax 收藏了文章 · 8月13日

30分钟精通十种React组件之间通信的方法

这两天被临时抽调到别的项目组去做一个小项目的迭代。这个项目前端是用React,只是个小型项目所以并没有使用Redux等状态管理的库。刚好遇到了一个小问题:两个不太相关的组件到底该怎么进行通信。我觉得这个问题还挺有趣的,所以把我的思考过程写下来,大家也可以一起讨论讨论。

虽然重点是要讲两个不相关的组件间的通信,但我还是从最常见的父子组件通信讲起,大家就当温故而知新了。先把完整的总结列出来,然后再详细展开。

组件间通信方式总结

  • 父组件 => 子组件:

    1. Props
    2. Instance Methods
  • 子组件 => 父组件:

    1. Callback Functions
    2. Event Bubbling
  • 兄弟组件之间:

    1. Parent Component
  • 不太相关的组件之间:

    1. Context
    2. Portals
    3. Global Variables
    4. Observer Pattern
    5. Redux等

1. Props

这是最常见的react组件之间传递信息的方法了吧,父组件通过props把数据传给子组件,子组件通过this.props去使用相应的数据

const Child = ({ name }) => {
    <div>{name}</div>
}

class Parent extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            name: 'zach'
        }
    }
    render() {
        return (
            <Child name={this.state.name} />
        )
    }
}

2. Instance Methods

第二种父组件向子组件传递信息的方式有些同学可能会比较陌生,但这种方式非常有用,请务必掌握。原理就是:父组件可以通过使用refs来直接调用子组件实例的方法,看下面的例子:

class Child extends React.Component {
  myFunc() {
    return "hello"
  }
}

class Parent extends React.Component {
  componentDidMount() {
    var x = this.foo.myFunc()   // x is now 'hello'
  }
  render() {
    return (
      <Child
        ref={foo => {
          this.foo = foo
        }}
      />
    )
  }
}

大致的过程:

  1. 首先子组件有一个方法myFunc
  2. 父组件给子组件传递一个ref属性,并且采用callback-refs的形式。这个callback函数接收react组件实例/原生dom元素作为它的参数。当父组件挂载时,react会去执行这个ref回调函数,并将子组件实例作为参数传给回调函数,然后我们把子组件实例赋值给this.foo
  3. 最后我们在父组件当中就可以使用this.foo来调用子组件的方法咯

了解了这个方法的原理后,我们要考虑的问题就是为啥我们要用这种方法,它的使用场景是什么?最常见的一种使用场景:比如子组件是一个modal弹窗组件,子组件里有显示/隐藏这个modal弹窗的各种方法,我们就可以通过使用这个方法,直接在父组件上调用子组件实例的这些方法来操控子组件的显示/隐藏。这种方法比起你传递一个控制modal显示/隐藏的props给子组件要美观多了。

class Modal extends React.Component {
  show = () => {// do something to show the modal}
  hide = () => {// do something to hide the modal}
  render() {
    return <div>I'm a modal</div>
  }
}

class Parent extends React.Component {
  componentDidMount() {
    if(// some condition) {
        this.modal.show()
    }
  }
  render() {
    return (
      <Modal
        ref={el => {
          this.modal = el
        }}
      />
    )
  }
}

3. Callback Functions

讲完了父组件给子组件传递信息的两种方式,我们再来讲子组件给父组件传递信息的方法。回调函数这个方法也是react最常见的一种方式,子组件通过调用父组件传来的回调函数,从而将数据传给父组件。

const Child = ({ onClick }) => {
    <div onClick={() => onClick('zach')}>Click Me</div>
}

class Parent extends React.Component {
    handleClick = (data) => {
        console.log("Parent received value from child: " + data)
    }
    render() {
        return (
            <Child onClick={this.handleClick} />
        )
    }
}

4. Event Bubbling

这种方法其实跟react本身没有关系,我们利用的是原生dom元素的事件冒泡机制。

class Parent extends React.Component {
  render() {
    return (
      <div onClick={this.handleClick}>
         <Child />
      </div>
    );
  }
  handleClick = () => {
    console.log('clicked')
  }
}
function Child {
  return (
    <button>Click</button>
  );    
}

巧妙的利用下事件冒泡机制,我们就可以很方便的在父组件的元素上接收到来自子组件元素的点击事件

5. Parent Component

讲完了父子组件间的通信,再来看非父子组件之间的通信方法。一般来说,两个非父子组件想要通信,首先我们可以看看它们是否是兄弟组件,即它们是否在同一个父组件下。如果不是的话,考虑下用一个组件把它们包裹起来从而变成兄弟组件是否合适。这样一来,它们就可以通过父组件作为中间层来实现数据互通了。

class Parent extends React.Component {
  constructor(props) {
    super(props)
    this.state = {count: 0}
  }
  setCount = () => {
    this.setState({count: this.state.count + 1})
  }
  render() {
    return (
      <div>
        <SiblingA
          count={this.state.count}
        />
        <SiblingB
          onClick={this.setCount}
        />
      </div>
    );
  }

}

6. Context

通常一个前端应用会有一些"全局"性质的数据,比如当前登陆的用户信息、ui主题、用户选择的语言等等。这些全局数据,很多组件可能都会用到,当组件层级很深时,用我们之前的方法,就得通过props一层一层传递下去,这显然太麻烦了,看下面的示例:

class App extends React.Component {
  render() {
    return <Toolbar theme="dark" />;
  }
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton theme={props.theme} />
    </div>
  );
}

class ThemedButton extends React.Component {
  render() {
    return <Button theme={this.props.theme} />;
  }
}

上面的例子,为了让我们的Button元素拿到主题色,我们必须把theme作为props,从App传到Toolbar,再从Toolbar传到ThemedButton,最后Button从父组件ThemedButton的props里终于拿到了主题theme。假如我们不同组件里都有用到Button,就得把theme向这个例子一样到处层层传递,麻烦至极。

因此react为我们提供了一个新api:Context,我们用Context改写下上例

const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}

简单的解析一下:

  1. React.createContext创建了一个Context对象,假如某个组件订阅了这个对象,当react去渲染这个组件时,会从离这个组件最近的一个Provider组件中读取当前的context值
  2. Context.Provider: 每一个Context对象都有一个Provider属性,这个属性是一个react组件。在Provider组件以内的所有组件都可以通过它订阅context值的变动。具体来说,Provider组件有一个叫value的prop传递给所有内部组件,每当value的值发生变化时,Provider内部的组件都会根据新value值重新渲染
  3. 那内部的组件该怎么使用这个context对象里的东西呢?
    a. 假如内部组件是用class声明的有状态组件:我们可以把Context对象赋值给这个类的属性contextType,如上面所示的ThemedButton组件

        class ThemedButton extends React.Component {
          static contextType = ThemeContext;
          render() {
            const value = this.context
            return <Button theme={value} />;
          }
        }

    b. 假如内部组件是用function创建的无状态组件:我们可以使用Context.Consumer,这也是Context对象直接提供给我们的组件,这个组件接受一个函数作为自己的child,这个函数的入参就是context的value,并返回一个react组件。可以将上面的ThemedButton改写下:

        function ThemedButton {
            return (
                <ThemeContext.Consumer>
                    {value => <Button theme={value} />}
                </ThemeContext.Consumer>
            )
        }

最后提一句,context对于解决react组件层级很深的props传递很有效,但也不应该被滥用。只有像theme、language等这种全局属性(很多组件都有可能依赖它们)时,才考虑用context。如果只是单纯为了解决层级很深的props传递,可以直接用component composition

7. Portals

Portals也是react提供的新特性,虽然它并不是用来解决组件通信问题的,但因为它也涉及到了组件通信的问题,所以我也把它列在我们的十种方法里面。

Portals的主要应用场景是:当两个组件在react项目中是父子组件的关系,但在HTML DOM里并不想是父子元素的关系。

举个例子,有一个父组件Parent,它里面包含了一个子组件Tooltip,虽然在react层级上它们是父子关系,但我们希望子组件Tooltip渲染的元素在DOM中直接挂载在body节点里,而不是挂载在父组件的元素里。这样就可以避免父组件的一些样式(如overflow:hiddenz-indexposition等)导致子组件无法渲染成我们想要的样式。

如下图所示,父组件是这个红色框的范围,并且设置了overflow:hidden,这时候我们的Tooltip元素超出了红色框的范围就被截断了。
image

怎么用portals解决呢?

首先,修改html文件,给portals增加一个节点

<html>
    <body>
        <div id="react-root"></div>
        <div id="portal-root"></div>
    </body>
</html>

然后我们创建一个可复用的portal容器,这里使用了react hooks的语法,看不懂的先过去看下我另外一篇讲解react hooks的文章:30分钟精通React今年最劲爆的新特性——React Hooks

import { useEffect } from "react";
import { createPortal } from "react-dom";

const Portal = ({children}) => {
  const mount = document.getElementById("portal-root");
  const el = document.createElement("div");

  useEffect(() => {
    mount.appendChild(el);
    return () => mount.removeChild(el);
  }, [el, mount]);

  return createPortal(children, el)
};

export default Portal;

最后在父组件中使用我们的portal容器组件,并将Tooltip作为children传给portal容器组件

const Parent = () => {
  const [coords, setCoords] = useState({});

  return <div style={{overflow: "hidden"}}>
      <Button>
        Hover me
      </Button>
      <Portal>
        <Tooltip coords={coords}>
          Awesome content that is never cut off by its parent container!
         </Tooltip>
      </Portal>
  </div>
}

这样就ok啦,虽然父组件仍然是overflow: hidden,但我们的Tooltip再也不会被截断了,因为它直接超脱了,它渲染到body节点下的<div id="portal-root"></div>里去了。

总结下适用的场景: Tooltip、Modal、Popup、Dropdown等等

8. Global Variables

哈哈,这也不失为一个可行的办法啊。当然你最好别用这种方法。

class ComponentA extends React.Component {
    handleClick = () => window.a = 'test'
    ...
}
class ComponentB extends React.Component {
    render() {
        return <div>{window.a}</div>
    }
}

9. Observer Pattern

观察者模式是软件设计模式里很常见的一种,它提供了一个订阅模型,假如一个对象订阅了某个事件,当那个事件发生的时候,这个对象将收到通知。

这种模式对于我们前端开发者来说是最不陌生的了,因为我们经常会给某些元素添加绑定事件,会写很多的event handlers,比如给某个元素添加一个点击的响应事件elm.addEventListener('click', handleClickEvent),每当elm元素被点击时,这个点击事件会通知elm元素,然后我们的回调函数handleClickEvent会被执行。这个过程其实就是一个观察者模式的实现过程。

那这种模式跟我们讨论的react组件通信有什么关系呢?当我们有两个完全不相关的组件想要通信时,就可以利用这种模式,其中一个组件负责订阅某个消息,而另一个元素则负责发送这个消息。javascript提供了现成的api来发送自定义事件: CustomEvent,我们可以直接利用起来。

首先,在ComponentA中,我们负责接受这个自定义事件:

class ComponentA extends React.Component {
    componentDidMount() {
        document.addEventListener('myEvent', this.handleEvent)
    }
    componentWillUnmount() {
        document.removeEventListener('myEvent', this.handleEvent)
    }
    
    handleEvent = (e) => {
        console.log(e.detail.log)  //i'm zach
    }
}

然后,ComponentB中,负责在合适的时候发送该自定义事件:

class ComponentB extends React.Component {
    sendEvent = () => {
        document.dispatchEvent(new CustomEvent('myEvent', {
          detail: {
             log: "i'm zach"
          }
        }))
    }
    
    render() {
        return <button onClick={this.sendEvent}>Send</button>
    }
}

这样我们就用观察者模式实现了两个不相关组件之间的通信。当然现在的实现有个小问题,我们的事件都绑定在了document上,这样实现起来方便,但很容易导致一些冲突的出现,所以我们可以小小的改良下,独立一个小模块EventBus专门这件事:

class EventBus {
    constructor() {
        this.bus = document.createElement('fakeelement');
    }

    addEventListener(event, callback) {
        this.bus.addEventListener(event, callback);
    }

    removeEventListener(event, callback) {
        this.bus.removeEventListener(event, callback);
    }

    dispatchEvent(event, detail = {}){
        this.bus.dispatchEvent(new CustomEvent(event, { detail }));
    }
}

export default new EventBus

然后我们就可以愉快的使用它了,这样就避免了把所有事件都绑定在document上的问题:

import EventBus from './EventBus'
class ComponentA extends React.Component {
    componentDidMount() {
        EventBus.addEventListener('myEvent', this.handleEvent)
    }
    componentWillUnmount() {
        EventBus.removeEventListener('myEvent', this.handleEvent)
    }
    
    handleEvent = (e) => {
        console.log(e.detail.log)  //i'm zach
    }
}
class ComponentB extends React.Component {
    sendEvent = () => {
        EventBus.dispatchEvent('myEvent', {log: "i'm zach"}))
    }
    
    render() {
        return <button onClick={this.sendEvent}>Send</button>
    }
}

最后我们也可以不依赖浏览器提供的api,手动实现一个观察者模式,或者叫pub/sub,或者就叫EventBus。

function EventBus() {
  const subscriptions = {};
  this.subscribe = (eventType, callback) => {
    const id = Symbol('id');
    if (!subscriptions[eventType]) subscriptions[eventType] = {};
    subscriptions[eventType][id] = callback;
    return {
      unsubscribe: function unsubscribe() {
        delete subscriptions[eventType][id];
        if (Object.getOwnPropertySymbols(subscriptions[eventType]).length === 0) {
          delete subscriptions[eventType];
        }
      },
    };
  };

  this.publish = (eventType, arg) => {
    if (!subscriptions[eventType]) return;

    Object.getOwnPropertySymbols(subscriptions[eventType])
      .forEach(key => subscriptions[eventType][key](arg));
  };
}
export default EventBus;

10. Redux等

最后终于来到了大家喜闻乐见的Redux等状态管理库,当大家的项目比较大,前面讲的9种方法已经不能很好满足项目需求时,才考虑下使用redux这种状态管理库。这里就先不展开讲解redux了...否则我花这么大力气讲解前面9种方法的意义是什么???

总结

十种方法,每种方法都有对应的适合它的场景,大家在设计自己的组件前,一定要好好考虑清楚采用哪种方式来解决通信问题。

文初提到的那个小问题,最后我采用方案9,因为既然是小迭代项目,又是改别人的代码,当然最好避免对别人的代码进行太大幅度的改造。而pub/sub这种方式就挺小巧精致的,既不需要对别人的代码结构进行大改动,又可以满足产品需求。

查看原文

xmax 收藏了文章 · 7月7日

你不知道的React 和 Vue 的20个区别【源码层面】

下载.jpeg

前言

面试竞争力越来越大,是时候撸一波Vue和React源码啦;
本文从20个层面来对比Vue和React的源码区别;
如果需要了解API的区别,请戳:
Vue 开发必须知道的 36 个技巧
React 开发必须知道的 34 个技巧
文章源码:请戳,原创码字不易,欢迎star!

1.Vue和React源码区别

1.1 Vue源码

来张Vue源码编译过程图

图片来源:分析Vue源码实现

1.1.1 挂载

初始化$mounted会挂载组件,不存在 render 函数时需要编译(compile);

1.1.2 compile

1.compile 分为 parse,optimize 和 generate,最终得到 render 函数;

2.parse 调用 parseHtml 方法,方法核心是利用正则解析 template 的指令,class 和 stype,得到 AST;

3.optimize 作用标记 static 静态节点,后面 patch,diff会跳过静态节点;

4.generate 是将 AST 转化为 render 函数表达式,执行 vm._render 方法将 render 表达式转化为VNode,得到 render 和 staticRenderFns 字符串;

5.vm._render 方法调用了 VNode 创建的方法createElement

// render函数表达式
(function() {
  with(this){
    return _c('div',{   //创建一个 div 元素
      attrs:{"id":"app"}  //div 添加属性 id
      },[
        _m(0),  //静态节点 header,此处对应 staticRenderFns 数组索引为 0 的 render function
        _v(" "), //空的文本节点
        (message) //判断 message 是否存在
         //如果存在,创建 p 元素,元素里面有文本,值为 toString(message)
        ?_c('p',[_v("\n    "+_s(message)+"\n  ")])
        //如果不存在,创建 p 元素,元素里面有文本,值为 No message.
        :_c('p',[_v("\n    No message.\n  ")])
      ]
    )
  }
})

1.1.3 依赖收集与监听

这部分是数据响应式系统
1.调用 observer(),作用是遍历对象属性进行双向绑定;

2.在 observer 过程中会注册Object.defineProperty的 get 方法进行依赖收集,依赖收集是将Watcher 对象的实例放入 Dep 中;

3.Object.defineProperty的 set 会调用Dep 对象的 notify 方法通知它内部所有的 Watcher 对象调用对应的 update()进行视图更新;

4.本质是发布者订阅模式的应用

1.1.4 diff 和 patch

diff 算法对比差异和调用 update更新视图:
1.patch 的 differ 是将同层的树节点进行比较,通过唯一的 key 进行区分,时间复杂度只有 O(n);

2.上面将到 set 被触发会调用 watcher 的 update()修改视图;

3.update 方法里面调用 patch()得到同级的 VNode 变化;

4.update 方法里面调用createElm通过虚拟节点创建真实的 DOM 并插入到它的父节点中;

5.createElm实质是遍历虚拟 dom,逆向解析成真实 dom;

1.2 React 源码

来张React源码编译过程图
React源码导图
图片来源:React源码解析

1.2.1 React.Component

1.原型上挂载了setState和forceUpdate方法;
2.提供props,context,refs 等属性;
3.组件定义通过 extends 关键字继承 Component;

1.2.2 挂载

1.render 方法调用了React.createElement方法(实际是ReactElement方法);

2.ReactDOM.render(component,mountNode)的形式对自定义组件/原生DOM/字符串进行挂载;

3.调用了内部的ReactMount.render,进而执行ReactMount._renderSubtreeIntoContainer,就是将子DOM插入容器;

4.ReactDOM.render()根据传入不同参数会创建四大类组件,返回一个 VNode;

5.四大类组件封装的过程中,调用了mountComponet方法,触发生命周期,解析出 HTML;

1.2.3 组件类型和生命周期

1.ReactEmptyComponent,ReactTextComponent,ReactDOMComponent组件没有触发生命周期;
2.ReactCompositeComponent类型调用mountComponent方法,会触发生命周期,处理 state 执行componentWillMount钩子,执行 render,获得 html,执行componentDidMounted

1.2.4 data 更新 setState

细节请见 3.1

1.2.5 数据绑定

1.setState 更新 data 后,shouldComponentUpdate为 true会生成 VNode,为 false 会结束;
2.VNode会调用 DOM diff,为 true 更新组件;

1.3 对比

React:
1.单向数据流;
2.setSate 更新data 值后,组件自己处理;
3.differ 是首位是除删除外是固定不动的,然后依次遍历对比;

Vue:
1.v-model 可以实现双向数据流,但只是v-bind:value 和 v-on:input的语法糖;
2.通过 this 改变值,会触发 Object.defineProperty的 set,将依赖放入队列,下一个事件循环开始时执行更新时才会进行必要的DOM更新,是外部监听处理更新;
3.differcompile 阶段的optimize标记了static 点,可以减少 differ 次数,而且是采用双向遍历方法;

2.React 和 Vue 渲染过程区别

2.1 React

1.生成期(挂载):参照 1.2.1
2.更新: 参照1.1.3和 1.1.4
3.卸载:销毁挂载的组件

2.2 Vue

1.new Vue()初始化后initLifecycle(vm),initEvents(vm),initRender(vm),callHook(vm,beforeCreate),initState(vm),callHook(vm,created);

 A.initLifecycle, 建立父子组件关系,在当前实例上添加一些属性和生命周期标识。如:children、refs、_isMounted等;  
 B.initEvents,用来存放除@hook:生命周期钩子名称="绑定的函数"事件的对象。如:$on、$emit等;  
 C.initRender,用于初始化$slots、$attrs、$listeners; 
 D.initState,是很多选项初始化的汇总,包括:props、methods、data、computed 和 watch 等;  
 E.callHook(vm,created)后才挂载实例 

2.compileToFunction:就是将 template 编译成 render 函数;
3.watcher: 就是执行1.2.3;
4.patch:就是执行 1.2.4

3.AST 和 VNode 的异同

1.都是 JSON 对象;

2.AST 是HTML,JS,Java或其他语言的语法的映射对象,VNode 只是 DOM 的映射对象,AST 范围更广;

3.AST的每层的element,包含自身节点的信息(tag,attr等),同时parent,children分别指向其父element和子element,层层嵌套,形成一棵树

<div id="app">
  <ul>
    <li v-for="item in items">
      itemid:{{item.id}}
    </li>
  </ul>
</div>

//转化为 AST 格式为
{
    "type": 1,
    "tag": "div",
    "attrsList": [
        {
            "name": "id",
            "value": "app"
        }
    ],
    "attrsMap": {
        "id": "app"
    },
    "children": [
        {
            "type": 1,
            "tag": "ul",
            "attrsList": [],
            "attrsMap": {},
            "parent": {
                "$ref": "$"
            },
            "children": [
                {
                    "type": 1,
                    "tag": "li",
                    // children省略了很多属性,表示格式即可
                }
            ],
            "plain": true
        }
    ],
    "plain": false,
    "attrs": [
        {
            "name": "id",
            "value": "\"app\""
        }
    ]
}

4.vnode就是一系列关键属性如标签名、数据、子节点的集合,可以认为是简化了的dom:

{
  tag: string | void;
  data: VNodeData | void;
  children: ?Array<VNode>;
  text: string | void;
  elm: Node | void;
  ns: string | void;
  context: Component | void;
  ...
}

5.VNode 的基本分类:EmptyVNode,TextVNode,ComponentVNNode,ElementVNNode,CloneVNode

6.创建 VNode

方法一:
// 利用createDocumentFragment()创建虚拟 dom 片段
// 节点对象包含dom所有属性和方法

// html
<ul id="ul"></ul>
// js
const element  = document.getElementById('ul');
const fragment = document.createDocumentFragment();
const browsers = ['Firefox', 'Chrome', 'Opera', 'Safari', 'Internet Explorer'];

browsers.forEach(function(browser) {
    const li = document.createElement('li');
    li.textContent = browser;
    fragment.appendChild(li);  // 此处往文档片段插入子节点,不会引起回流 (相当于打包操作)
});
console.log(fragment)
element.appendChild(fragment);  // 将打包好的文档片段插入ul节点,只做了一次操作,时间快,性能好

方法二:
// 用 JS 对象来模拟 VNode
function Element (tagName, props, children) {
  console.log('this',this)
  this.tagName = tagName
  this.props = props
  this.children = children
}

let ElementO =new Element('ul', {id: 'list'}, [
  new Element('li', {class: 'item'}, ['Item 1']),
  new Element('li', {class: 'item'}, ['Item 2']),
  new Element('li', {class: 'item'}, ['Item 3'])
])

// 利用 render 渲染到页面
Element.prototype.render = function () {
  const el = document.createElement(this.tagName) // 根据tagName构建
  const props = this.props

  for (const propName in props) { // 设置节点的DOM属性
    const propValue = props[propName]
    el.setAttribute(propName, propValue)
  }

  const children = this.children || []

  children.forEach(function (child) {
    const childEl = (child instanceof Element)
      ? child.render() // 如果子节点也是虚拟DOM,递归构建DOM节点
      : document.createTextNode(child) // 如果字符串,只构建文本节点
    el.appendChild(childEl)
  })

  return el
}
console.log('ElementO',ElementO)
var ulRoot = ElementO.render()
console.log('ulRoot',ulRoot)
document.body.appendChild(ulRoot)

4.React 和Vue 的 differ 算法区

4.1 React

1.Virtual DOM 中的首个节点不执行移动操作(除非它要被移除),以该节点为原点,其它节点都去寻找自己的新位置; 一句话就是首位是老大,不移动;

2.在 Virtual DOM 的顺序中,每一个节点与前一个节点的先后顺序与在 Real DOM 中的顺序进行比较,如果顺序相同,则不必移动,否则就移动到前一个节点的前面或后面;

3.tree diff:只会同级比较,如果是跨级的移动,会先删除节点 A,再创建对应的 A;将 O(n3) 复杂度的问题转换成 O(n) 复杂度;

4.component diff:
根据batchingStrategy.isBatchingUpdates值是否为 true;
如果true 同一类型组件,按照 tree differ 对比;
如果 false将组件放入 dirtyComponent,下面子节点全部替换,具体逻辑看 3.1 setSate

5.element differ:
tree differ 下面有三种节点操作:INSERT_MARKUP(插入)、MOVE_EXISTING(移动)和 REMOVE_NODE(删除)
请戳

6.代码实现

_updateChildren: function(nextNestedChildrenElements, transaction, context) {
   var prevChildren = this._renderedChildren
  var removedNodes = {}
  var mountImages = []

  // 获取新的子元素数组
  var nextChildren = this._reconcilerUpdateChildren(
    prevChildren,
    nextNestedChildrenElements,
    mountImages,
    removedNodes,
    transaction,
    context
  )

  if (!nextChildren && !prevChildren) {
    return
  }

  var updates = null
  var name
  var nextIndex = 0
  var lastIndex = 0
  var nextMountIndex = 0
  var lastPlacedNode = null

  for (name in nextChildren) {
    if (!nextChildren.hasOwnProperty(name)) {
      continue
    }
    var prevChild = prevChildren && prevChildren[name]
    var nextChild = nextChildren[name]
    if (prevChild === nextChild) {
      // 同一个引用,说明是使用的同一个component,所以我们需要做移动的操作
      // 移动已有的子节点
      // NOTICE:这里根据nextIndex, lastIndex决定是否移动
      updates = enqueue(
        updates,
        this.moveChild(prevChild, lastPlacedNode, nextIndex, lastIndex)
      )

      // 更新lastIndex
      lastIndex = Math.max(prevChild._mountIndex, lastIndex)
      // 更新component的.mountIndex属性
      prevChild._mountIndex = nextIndex

    } else {
      if (prevChild) {
        // 更新lastIndex
        lastIndex = Math.max(prevChild._mountIndex, lastIndex)
      }

      // 添加新的子节点在指定的位置上
      updates = enqueue(
        updates,
        this._mountChildAtIndex(
          nextChild,
          mountImages[nextMountIndex],
          lastPlacedNode,
          nextIndex,
          transaction,
          context
        )
      )


      nextMountIndex++
    }

    // 更新nextIndex
    nextIndex++
    lastPlacedNode = ReactReconciler.getHostNode(nextChild)
  }

  // 移除掉不存在的旧子节点,和旧子节点和新子节点不同的旧子节点
  for (name in removedNodes) {
    if (removedNodes.hasOwnProperty(name)) {
      updates = enqueue(
        updates,
        this._unmountChild(prevChildren[name], removedNodes[name])
      )
    }
  }
  }

4.2 Vue

1.自主研发了一套Virtual DOM,是借鉴开源库snabbdom,
snabbdom地址

2.也是同级比较,因为在 compile 阶段的optimize标记了static 点,可以减少 differ 次数;

3.Vue 的这个 DOM Diff 过程就是一个查找排序的过程,遍历 Virtual DOM 的节点,在 Real DOM 中找到对应的节点,并移动到新的位置上。不过这套算法使用了双向遍历的方式,加速了遍历的速度,更多请戳

4.代码实现:

updateChildren (parentElm, oldCh, newCh) {
    let oldStartIdx = 0, newStartIdx = 0
  let oldEndIdx = oldCh.length - 1
  let oldStartVnode = oldCh[0]
  let oldEndVnode = oldCh[oldEndIdx]
  let newEndIdx = newCh.length - 1
  let newStartVnode = newCh[0]
  let newEndVnode = newCh[newEndIdx]
  let oldKeyToIdx
  let idxInOld
  let elmToMove
  let before
  while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    if (oldStartVnode == null) {   //对于vnode.key的比较,会把oldVnode = null
      oldStartVnode = oldCh[++oldStartIdx]
    }else if (oldEndVnode == null) {
      oldEndVnode = oldCh[--oldEndIdx]
    }else if (newStartVnode == null) {
      newStartVnode = newCh[++newStartIdx]
    }else if (newEndVnode == null) {
      newEndVnode = newCh[--newEndIdx]
    }else if (sameVnode(oldStartVnode, newStartVnode)) {
      patchVnode(oldStartVnode, newStartVnode)
      oldStartVnode = oldCh[++oldStartIdx]
      newStartVnode = newCh[++newStartIdx]
    }else if (sameVnode(oldEndVnode, newEndVnode)) {
      patchVnode(oldEndVnode, newEndVnode)
      oldEndVnode = oldCh[--oldEndIdx]
      newEndVnode = newCh[--newEndIdx]
    }else if (sameVnode(oldStartVnode, newEndVnode)) {
      patchVnode(oldStartVnode, newEndVnode)
      api.insertBefore(parentElm, oldStartVnode.el, api.nextSibling(oldEndVnode.el))
      oldStartVnode = oldCh[++oldStartIdx]
      newEndVnode = newCh[--newEndIdx]
    }else if (sameVnode(oldEndVnode, newStartVnode)) {
      patchVnode(oldEndVnode, newStartVnode)
      api.insertBefore(parentElm, oldEndVnode.el, oldStartVnode.el)
      oldEndVnode = oldCh[--oldEndIdx]
      newStartVnode = newCh[++newStartIdx]
    }else {
      // 使用key时的比较
      if (oldKeyToIdx === undefined) {
        oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) // 有key生成index表
      }
      idxInOld = oldKeyToIdx[newStartVnode.key]
      if (!idxInOld) {
        api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el)
        newStartVnode = newCh[++newStartIdx]
      }
      else {
        elmToMove = oldCh[idxInOld]
        if (elmToMove.sel !== newStartVnode.sel) {
          api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el)
        }else {
          patchVnode(elmToMove, newStartVnode)
          oldCh[idxInOld] = null
          api.insertBefore(parentElm, elmToMove.el, oldStartVnode.el)
        }
        newStartVnode = newCh[++newStartIdx]
      }
    }
  }
  if (oldStartIdx > oldEndIdx) {
    before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].el
    addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx)
  }else if (newStartIdx > newEndIdx) {
    removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
  }
}

4.3 对比

相同点:
都是同层 differ,复杂度都为 O(n);

不同点:
1.React 首位是除删除外是固定不动的,然后依次遍历对比;
2.Vue 的compile 阶段的optimize标记了static 点,可以减少 differ 次数,而且是采用双向遍历方法;

5.React 的 setState和 Vue 改变值的区别

5.1 setState

1.setState 通过一个队列机制来实现 state 更新,当执行 setState() 时,会将需要更新的 state 浅合并后,根据变量 isBatchingUpdates(默认为 false)判断是直接更新还是放入状态队列;

2.通过js的事件绑定程序 addEventListener 和使用setTimeout/setInterval 等 React 无法掌控的 API情况下isBatchingUpdates 为 false,同步更新。除了这几种情况外batchedUpdates函数将isBatchingUpdates修改为 true;

3.放入队列的不会立即更新 state,队列机制可以高效的批量更新 state。而如果不通过setState,直接修改this.state 的值,则不会放入状态队列;

4.setState 依次直接设置 state 值会被合并,但是传入 function 不会被合并;
让setState接受一个函数的API的设计是相当棒的!不仅符合函数式编程的思想,让开发者写出没有副作用的函数,而且我们并不去修改组件状态,只是把要改变的状态和结果返回给React,维护状态的活完全交给React去做。正是把流程的控制权交给了React,所以React才能协调多个setState调用的关系

// 情况一
state={
  count:0
}
handleClick() {
  this.setState({
    count: this.state.count + 1
  })
  this.setState({
    count: this.state.count + 1
  })
  this.setState({
    count: this.state.count + 1
  })
}
// count 值依旧为1

// 情况二
increment(state, props) {
  return {
    count: state.count + 1
  }
}

handleClick() {
  this.setState(this.increment)
  this.setState(this.increment)
  this.setState(this.increment)
}
// count 值为 3

5.更新后执行四个钩子:shouleComponentUpdate,componentWillUpdate,render,componentDidUpdate

5.2 Vue 的 this 改变

1.vue 自身维护 一个 更新队列,当你设置 this.a = 'new value',DOM 并不会马上更新;

2.在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更;

3.如果同一个 watcher 被多次触发,只会被推入到队列中一次;

4.也就是下一个事件循环开始时执行更新时才会进行必要的DOM更新和去重;

5.所以 for 循环 10000次 this.a = i vue只会更新一次,而不会更新10000次;

6.data 变化后如果 computed 或 watch 监听则会执行;

6. Vue的v-for 或 React 的map 中为什么不要用 index作为 key

6.1 为什么要加 key

6.1.1 React

1.上面的 5.1 讲到 React 的 differ 中 element differ 有三种节点操作;

2.场景一不加 key:
新老集合进行 diff 差异化对比,发现 B != A,则创建并插入 B 至新集合,删除老集合 A;以此类推,创建并插入 A、D 和 C,删除 B、C 和 D;
都是相同的节点,但由于位置发生变化,导致需要进行繁杂低效的删除、创建操作,其实只要对这些节点进行位置移动即可;

3.场景二加 key:
新建:从新集合中取得 E,判断老集合中不存在相同节点 E,则创建新节点 ElastIndex不做处理E的位置更新为新集合中的位置,nextIndex++;
删除:当完成新集合中所有节点 diff 时,最后还需要对老集合进行循环遍历,判断是否存在新集合中没有但老集合中仍存在的节点,发现存在这样的节点 D,因此删除节点 D;

4.总结:
显然加了 key 后操作步骤要少很多,性能更好;
但是都会存在一个问题,上面场景二只需要移动首位,位置就可对应,但是由于首位是老大不能动,所以应该尽量减少将最后一个节点移动到首位,更多请戳

6.1.2 Vue

Vue 不加 key 场景分析:
1.场景一不加 key:
也会将使用了双向遍历的方式查找,发现 A,B,C,D都不等,先删除再创建;

2.场景二加 key:双向遍历的方式查找只需要创建E,删除D,改变 B、C、A的位置

6.2 为什么 key 不能为 index

这个问题分为两个方面:
1.如果列表是纯静态展示,不会 CRUD,这样用 index 作为 key 没得啥问题;

2.如果不是

const list = [1,2,3,4];
// list 删除 4 不会有问题,但是如果删除了非 4 就会有问题
// 如果删除 2
const listN= [1,3,4]
// 这样index对应的值就变化了,整个 list 会重新渲染

3.所以 list 最好不要用 index 作为 key

7. Redux和 Vuex 设计思想

7.1 Redux

API:
1.Redux则是一个纯粹的状态管理系统,React利用React-Redux将它与React框架结合起来;

2.只有一个用createStore方法创建一个 store;

3.action接收 view 发出的通知,告诉 Store State 要改变,有一个 type 属性;

4.reducer:纯函数来处理事件,纯函数指一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,得到一个新的 state;

源码组成:

1.createStore 创建仓库,接受reducer作为参数  
2.bindActionCreator 绑定store.dispatch和action 的关系  
3.combineReducers 合并多个reducers  
4.applyMiddleware 洋葱模型的中间件,介于dispatch和action之间,重写dispatch
5.compose 整合多个中间件
6.单一数据流;state 是可读的,必须通过 action 改变;reducer设计成纯函数;

7.2 Vuex

1.Vuex是吸收了Redux的经验,放弃了一些特性并做了一些优化,代价就是VUEX只能和VUE配合;

2.store:通过 new Vuex.store创建 store,辅助函数mapState;

3.getters:获取state,有辅助函数 mapGetters;

4.action:异步改变 state,像ajax,辅助函数mapActions;

5.mutation:同步改变 state,辅助函数mapMutations;

7.3 对比

1.Redux: view——>actions——>reducer——>state变化——>view变化(同步异步一样)
2.Vuex: view——>commit——>mutations——>state变化——>view变化(同步操作) 
  view——>dispatch——>actions——>mutations——>state变化——>view变化(异步操作)

8.redux 为什么要把 reducer 设计成纯函数

1.纯函数概念:一个函数的返回结果只依赖于它的参数(外面的变量不会改变自己),并且在执行过程里面没有副作用(自己不会改变外面的变量);

2.主要就是为了减小副作用,避免影响 state 值,造成错误的渲染;

3.把reducer设计成纯函数,便于调试追踪改变记录;

9.Vuex的mutation和Redux的reducer中为什么不能做异步操作

1.在 vuex 里面 actions 只是一个架构性的概念,并不是必须的,说到底只是一个函数,你在里面想干嘛都可以,只要最后触发 mutation 就行;

2.vuex 真正限制你的只有 mutation 必须是同步的这一点(在 redux 里面就好像 reducer 必须同步返回下一个状态一样);

3.每一个 mutation 执行完成后都可以对应到一个新的状态(和 reducer 一样),这样 devtools 就可以打个 snapshot 存下来,然后就可以随便 time-travel 了。如果你开着 devtool 调用一个异步的 action,你可以清楚地看到它所调用的 mutation 是何时被记录下来的,并且可以立刻查看它们对应的状态;

4.其实就是框架是这么设计的,便于调试追踪改变记录

10.双向绑定和 vuex 是否冲突

1.在严格模式中使用Vuex,当用户输入时,v-model会试图直接修改属性值,但这个修改不是在mutation中修改的,所以会抛出一个错误;

2.当需要在组件中使用vuex中的state时,有2种解决方案:

在input中绑定value(vuex中的state),然后监听input的change或者input事件,在事件回调中调用mutation修改state的值;  

// 双向绑定计算属性
<input v-model="message">

computed: {
  message: {
    get () {
      return this.$store.state.obj.message
    },
    set (value) {
      this.$store.commit('updateMessage', value)
    }
  }
}

11. Vue的nextTick原理

11.1 使用场景

什么时候会用到?
nextTick的使用原则主要就是解决单一事件更新数据后立即操作dom的场景。

11.2 原理

1.vue 用异步队列的方式来控制 DOM 更新和 nextTick 回调先后执行;

2.microtask 因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕;

3.考虑兼容问题,vue 做了 microtask 向 macrotask 的降级方案;

4.代码实现:

const simpleNextTick = function queueNextTick (cb) {   
    return Promise.resolve().then(() => {
      cb()
    })
}

simpleNextTick(() => {
  console.log(this.$refs.test.innerText)
})

13. Vue 的data 必须是函数而 React 的 state 是对象

13.1 Vue 的 data 必须是函数

对象是引用类型,内存是存贮引用地址,那么子组件中的 data 属性值会互相污染,产生副作用;
如果是函数,函数的{}构成作用域,每个实例相互独立,不会相互影响;

13.2 React 的 state 是对象

因为 state 是定义在函数里面,作用域已经独立

14.Vue 的合并策略

1.生命周期钩子:合并为数组

function mergeHook (
  parentVal,
  childVal 
) {
  return childVal
    ? parentVal // 如果 childVal存在
      ? parentVal.concat(childVal) // 如果parentVal存在,直接合并
      : Array.isArray(childVal) // 如果parentVal不存在
        ? childVal  // 如果chilidVal是数组,直接返回
        : [childVal] // 包装成一个数组返回
    : parentVal  // 如果childVal 不存在 直接返回parentVal 
}
// strats中添加属性,属性名为生命周期各个钩子
config._lifecycleHooks.forEach(function (hook) {
  strats[hook] = mergeHook // 设置每一个钩子函数的合并策略
})

2.watch:合并为数组,执行有先后顺序;

3.assets(components、filters、directives):合并为原型链式结构,合并的策略就是返回一个合并后的新对象,新对象的自有属性全部来自 childVal, 但是通过原型链委托在了 parentVal 上

function mergeAssets (parentVal, childVal) { // parentVal: Object childVal: Object
  var res = Object.create(parentVal || null) // 原型委托
  return childVal
    ? extend(res, childVal)
    : res
}

config._assetTypes.forEach(function (type) {
  strats[type + 's'] = mergeAssets
})

4.data为function,需要合并执行后的结果,就是执行 parentVal 和 childVal 的函数,然后再合并函数返回的对象;

5.自定义合并策略:

Vue.config.optionMergeStrategies.watch = function (toVal, fromVal) {
  // return mergedVal
}

14.Vue-router 的路由模式

1.三种:"hash" | "history" | "abstract";

2.hash(默认),history 是浏览器环境,abstract是 node 环境;

3.hash: 使用 URL hash 值来作路由,是利用哈希值实现push、replace、go 等方法;

4.history:依赖 HTML5 History API新增的 pushState() 和 replaceState(),需要服务器配置;

5.abstract:如果发现没有浏览器的 API,路由会自动强制进入这个模式。

15.Vue 的事件机制

class Vue {  
  constructor() {    
    //  事件通道调度中心    
    this._events = Object.create(null);  
  }  
  $on(event, fn) {    
    if (Array.isArray(event)) {      
      event.map(item => {        
        this.$on(item, fn);      
    });    
  } else {      
    (this._events[event] || (this._events[event] = [])).push(fn);    }    
    return this; 
 }  
$once(event, fn) {    
  function on() {      
    this.$off(event, on);      
    fn.apply(this, arguments);    
    }    
    on.fn = fn;    
    this.$on(event, on);    
    return this;  
}  
$off(event, fn) {    
  if (!arguments.length) {      
    this._events = Object.create(null);      
    return this;    
  }    
  if (Array.isArray(event)) {      
    event.map(item => {        
      this.$off(item, fn);      
  });      
  return this;    
  }    
const cbs = this._events[event];    
if (!cbs) {      
  return this;
}    
if (!fn) {      
  this._events[event] = null;
  return this;    
}    
let cb;    
let i = cbs.length;    
while (i--) {      
  cb = cbs[i];      
  if (cb === fn || cb.fn === fn) {        
    cbs.splice(i, 1);        
    break;      
}    
}    
return this;  
}  
$emit(event) {    
  let cbs = this._events[event];    
  if (cbs) {      
    const args = [].slice.call(arguments, 1);      
    cbs.map(item => {        
      args ? item.apply(this, args) : item.call(this);      
});    
}    
return this;  
}}

16.keep-alive 的实现原理和缓存策略

1.获取keep-alive第一个子组件;

2.根据include exclude名单进行匹配,决定是否缓存。如果不匹配,直接返回组件实例,如果匹配,到第3步;

3.根据组件id和tag生成缓存组件的key,再去判断cache中是否存在这个key,即是否命中缓存,如果命中,用缓存中的实例替代vnode实例,然后更新key在keys中的位置,(LRU置换策略)。如果没有命中,就缓存下来,如果超出缓存最大数量max,删除cache中的第一项。

4.keep-alive是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中;

5.LRU算法:根据数据的历史访问记录来进行淘汰数据,其实就是访问过的,以后访问概率会高;

6.LRU 实现:
新数据插入到链表头部;
每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
当链表满的时候,将链表尾部的数据丢弃。

17.Vue 的 set 原理

1.由于 Object.observe()方法废弃了,所以Vue 无法检测到对象属性的添加或删除;

2.原理实现:
判断是否是数组,是利用 splice 处理值;
判断是否是对象的属性,直接赋值;
不是数组,且不是对象属性,创建一个新属性,不是响应数据直接赋值,是响应数据调用defineReactive;

export function set (target: Array<any> | Object, key: any, val: any): any {
// 如果 set 函数的第一个参数是 undefined 或 null 或者是原始类型值,那么在非生产环境下会打印警告信息
// 这个api本来就是给对象与数组使用的
if (process.env.NODE_ENV !== 'production' &&
  (isUndef(target) || isPrimitive(target))
) {
  warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
if (Array.isArray(target) && isValidArrayIndex(key)) {
  // 类似$vm.set(vm.$data.arr, 0, 3)
  // 修改数组的长度, 避免索引>数组长度导致splcie()执行有误
  target.length = Math.max(target.length, key)
  // 利用数组的splice变异方法触发响应式, 这个前面讲过
  target.splice(key, 1, val)
  return val
}
// target为对象, key在target或者target.prototype上。
// 同时必须不能在 Object.prototype 上
// 直接修改即可, 有兴趣可以看issue: https://github.com/vuejs/vue/issues/6845
if (key in target && !(key in Object.prototype)) {
  target[key] = val
  return val
}
// 以上都不成立, 即开始给target创建一个全新的属性
// 获取Observer实例
const ob = (target: any).__ob__
// Vue 实例对象拥有 _isVue 属性, 即不允许给Vue 实例对象添加属性
// 也不允许Vue.set/$set 函数为根数据对象(vm.$data)添加属性
if (target._isVue || (ob && ob.vmCount)) {
  process.env.NODE_ENV !== 'production' && warn(
    'Avoid adding reactive properties to a Vue instance or its root $data ' +
    'at runtime - declare it upfront in the data option.'
  )
  return val
}
// target本身就不是响应式数据, 直接赋值
if (!ob) {
  target[key] = val
  return val
}
// 进行响应式处理
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
https://juejin.im/post/5e04411f6fb9a0166049a073#heading-18

18.简写 Redux

function createStore(reducer) {
    let state;
    let listeners=[];
    function getState() {
        return state;
    }

    function dispatch(action) {
        state=reducer(state,action);
        listeners.forEach(l=>l());
    }

    function subscribe(listener) {
        listeners.push(listener);
        return function () {
            const index=listeners.indexOf(listener);
            listeners.splice(inddx,1);
        }
    }
    
    dispatch({});
    
    return {
        getState,
        dispatch,
        subscribe
    }

}

19 react-redux是如何来实现的

源码组成:
1.connect 将store和dispatch分别映射成props属性对象,返回组件
2.context 上下文 导出Provider,,和 consumer
3.Provider 一个接受store的组件,通过context api传递给所有子组件

20. react16 的 fiber 理解

1.react 可以分为 differ 阶段和 commit(操作 dom)阶段;

2.v16 之前是向下递归算法,会阻塞;

3.v16 引入了代号为 fiber 的异步渲染架构;

4.fiber 核心实现了一个基于优先级和requestIdleCallback循环任务调度算法;

5.算法可以把任务拆分成小任务,可以随时终止和恢复任务,可以根据优先级不同控制执行顺序,更多请戳

总结

文章源码:请戳,原创码字不易,欢迎star!
您的鼓励是我持续创作的动力!

查看原文

xmax 赞了文章 · 7月7日

那些不常见,但却非常实用的js知识(整理不易)

一、window

window 对象表示一个包含 DOM 文档的窗口,其 document 属性指向窗口中载入的 DOM 文档 。

1、window 属性和方法

在有标签页功能的浏览器中,每个标签都拥有自己的 window 对象;也就是说,同一个窗口的标签页之间不会共享一个 window 对象。

1.1、几个浏览器的高度

window.screen.height==window.screen.availHeight
表示手机的屏幕高度,在一部手机中是固定的。不同浏览器打开,都不会变。

window.innerHeight==document.documentElement.clientHeight
表示浏览器中,除去顶部地址栏,下部工具栏之外,暴露给用户,可以看的见的中间区域。也就是实际的网页浏览高度,不同浏览器不同。

document.body.clientHeight
body 元素的高度。
如果设置 body 的 height:30px; 那么这个属性就是 30。

下面是示意图,在 pc 端同理
image

1.2、window.performance

Web Performance API 允许网页访问某些函数来测量网页和 Web 应用程序的性能,包括 Navigation Timing API 和高分辨率时间数据。

performance.now()

该方法返回一个 DOMHighResTimeStamp 对象,该对象表示从某一时刻(译者注:某一时刻通常是 navigationStart 事件发生时刻)到调用该方法时刻的毫秒数。

image

1.3、devicePixelRatio

返回当前显示设备的物理像素分辨率与 CSS 像素分辨率之比。

物理像素分辨率表示,你的电脑的硬件部分,在出场时,已经确定。

而 css 像素分辨率,则可以动态调整。
当电脑显示设置为 100%时,物理像素分辨率和 css 像素分辨率是相等的。即 devicePixelRatio 为 1

window.devicePixelRatio   //1

image

而如果你将电脑设置为 150%;或者在电脑设置 100%时,同时将浏览器分辨率调成 150%,那么 devicePixelRatio 都为 1.5
image

image

window.devicePixelRatio   //1.5

你可以将 devicePixelRatio 为 1.5 理解为,1px 的 css 样式,占用了电脑硬件屏幕分辨路的 1.5 个 dpi,所以肉眼看起来电脑上的文字更大些。

这种放大的效果在某些情况下,会导致 html 中的元素失真,典型的便是 canvas 变得模糊。

下面是矫正代码

//size为原本的大小
let scale = window.devicePixelRatio;
canvas.width = Math.floor(size * scale);
canvas.height = Math.floor(size * scale);

image

1.4、base64 编码/解码
window.btoa() 编码为 base64

window.atob() 解码

let encodedData = window.btoa("Hello, world"); // 编码
let decodedData = window.atob(encodedData);    // 解码

image

1.5、requestAnimationFrame / cancelAnimationFrame()

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。

回调函数执行次数通常是每秒 60 次,但在大多数遵循 W3C 建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配。但是却和 setInterval(),不同,因为 setInterval 设置的秒,并不一定准确在对应秒后执行,而是需要看是否有其他资源在执行,如果有,则等待。实际可能大于等于设置的秒数,而 requestAnimationFrame 会准确的按照浏览器渲染的频率想匹配

//兼容性处理
var requestAnimationFrame =
  window.requestAnimationFrame ||
  window.mozRequestAnimationFrame ||
  window.webkitRequestAnimationFrame ||
  window.msRequestAnimationFrame;

var cancelAnimationFrame =
  window.cancelAnimationFrame || window.mozCancelAnimationFrame;

var start = window.mozAnimationStartTime; // 只有Firefox支持mozAnimationStartTime属性,其他浏览器可以使用Date.now()来替代.

var myReq;
function step(timestamp) {
  var progress = timestamp - start;
  d.style.left = Math.min(progress / 10, 200) + "px";
  if (progress < 2000) {
    myReq = requestAnimationFrame(step); //必须再调用一次,这样就形成了递归
  }
}
myReq = requestAnimationFrame(step);

//当不需要是取消动画,比如 在vue destoryed中
window.cancelAnimationFrame(myReq);

image

image

1.6、getSelection()

此方法不必传入具体的 dom,选中哪个区域或者光标在哪个输入框, 此方法会获取到那个 dom。 返回一个selection对象

window.getSelection();

image

1.7、scroll() / scrollBy() / scrollTo()

scroll()滚动至文档中的绝对位置(类似于 css 中的 display:absolute), scrollBy()滚动指定的距离(类似于 css 中的 display:relative)

scrollTo 和 scroll 一致。

window.scroll({
  top: 100,
  left: 100,
  behavior: 'smooth'  //表示是否平滑的过渡还是即可调到指定位置,同css属性scroll-behavior
});
window.scrollBy({
  top: 100,
  left: 100,
  behavior: "smooth"
});

scroll() scrollBy() scrollTo()合称为 scrollOptions API
image

2、window 事件

通常在定义 window 中的事件时,有两种方式:

  • window.onXXX = function(){};
  • window.addEventListener("XXX", ()=>{}, {capture:true, once:true, passive:true});

那么这两种方式有区别吗? 有区别,但也没区别。

因为虽然定义的方式不同,可最终都会走到事件中。

但是又有区别,尤其在多个组件中,同时定义相同的事件,那么 window.onXXX 会后面定义的把前面定义的相同事件覆盖掉。最终只会执行最后定义的那个。而 window.addEventListener 不同,定义几个,执行几次,而且还可以再 options 中设置 once,capture,passive 等选项。

以 passive 为例,它可以忽略事件得默认行为,不会阻止它。当你在 js 操作时,html 中会立马响应到。最典型得可以使得触摸事件和 scroll 事件不再卡顿,非常流畅。

通常有 XXX 方式,那么对应也会有 onXXX

2.1、onbeforeunload

当窗口即将被卸载(关闭)时,会触发该事件.此时页面文档依然可见,且该事件的默认动作可以被取消.

需要注意的是,点击浏览器标签页的 x 号前,如果没有对页面进行过任何“操作”,则不会弹框直接关闭,如果进行过操作,则会弹出确认框。

这里所指的“操作”包括,但不限于,input 输入,打开 console,复制页面的一段文字等等。

window.onbeforeunload = () => {
    let isIE = xxxxxxxx; //忽略通过user-agent判断ie浏览器的方法
    if (isIE) {
      return "The system may be not save your changes.";
    } else {
      return false; //其他浏览器会弹出自己的文字,如上图。是中文
    }
};

image

2.2、onhashchange

当 一个窗口的 hash (URL 中 # 后面的部分)改变时就会触发 hashchange 事件(参见 location.hash)。

window.onhashchange = (newURL, oldURL)=>{
    //newURL 当前页面新的URL
    //oldURL 当前页面旧的URL
};

image

2.3、onload / DOMContentLoaded

DOMContentLoaded 指 html 页面加载完毕就会触发,至于一些异步资源的比如 img,video 等等,它不关心。

而 onload 表示除了 html 加载完,img,video 等也加载完,才执行

window.addEventListener("load", function () {

});

window.addEventListener("DOMContentLoaded", function () {

});

image

image

看到没: JavaScript event that fires when the DOM is loaded, but before all page assets are loaded (CSS, images, etc.). 说的再清楚不过了

2.4、onmouseover、onmouseout / onmouseenter、onmouseleave

onmouseover、onmouseout:鼠标移动到自身时候会触发事件,同时移动到其子元素身上也会触发事件 onmouseenter、onmouseleave:鼠标移动到自身是会触发事件,但是移动到其子元素身上不会触发事件

onmouseover 和 onmouseout 支持性一致
image

onmouseenter 和 onmouseleave 支持性一致
image

2.5、onscroll / onwheel

onwheel 事件在鼠标滚轮在元素上下滚动时触发,必须滚轮要转,至于页面滚不滚动,它不关心。

onscroll 事件在元素滚动条在滚动时触发,必须滚动条存在且上下运动。
触发方式有:滚轮滚动,鼠标按住滚动条拖动,键盘上下键滚动,js 脚本去滚动如 scrollTo,scrollBy,scrollByLines, scrollByPages 等。

在这里有个特殊,在手机端,虽然没有鼠标,但是手指触摸上下滚动,也是触发 onscroll 的。

image

image
什么? 支持率这么一点点? 大大出乎我的意料

2.6、copy 事件/cut 事件/ paste 事件

window 的复制/剪切, 粘贴事件。

window.addEventListener("copy", function (e) {
    //将复制的数据,存入到剪切板中
    e.clipboardData.setData("abc", "Hello, world!");
    e.preventDefault();
});

window.addEventListener("paste", function (e) {
    //再从剪切板中取出数据
    let data = e.clipboardData.getData("abc");
    e.preventDefault();
});

这三个事件支持性一致

image

2.7、error 事件

当资源加载失败或无法使用时,会在 Window 对象触发 error 事件。例如:script 执行时报错。

window.addEventListener('error', (event) => {

});

image

2.8、orientationchange 事件

orientationchange 事件在设备的纵横方向改变时触发。

window.addEventListener("orientationchange", function() {

});

image
pc 上几乎都不支持,这也能理解,毕竟 pc 上是使用 resize 的,只有手机才会使用 横竖屏。

2.9、rejectionhandled 事件 / unhandledrejection 事

当 Promise 被 rejected 且有 rejection 处理器时会在全局触发 rejectionhandled。

当 Promise 被 reject 且没有 reject 处理器的时候,会触发 unhandledrejection 事件;

window.addEventListener("rejectionhandled", event => {

}, false);

window.addEventListener("unhandledrejection", event => {

});


function getName() {
    return Promise.reject("error");
}

let p = getName();   //触发unhandledrejection
setTimeout(() => {
    p.catch((err) => {});  //触发rejectionhandled
}, 1000);

image

image

2.10、storage 事件

当 localStorage 被修改时,将触发 storage 事件。

注意:

  • 只有在同域名的不同文件之间,才能够监听到,
  • 同一个文件中,是监听不到的(合理,同一个文件直接拿就行了,用 storage 监听多此一举)
  • 通过 iframe 引入的另一个站点,也可以监听到

image

2.11、online 事件 / offline 事件

online 当网络连接时,触发,
offline 当网络断开时触发

online 和 offline 支持性一致
image

二、element(DOM 对象)

1、element 属性和方法

1.1、clientHeight / clientWidth 属性
  • clientHeight:元素内部的高度,包含内边距,但不包括水平滚动条、边框和外边距。
  • clientWidth:元素内部的宽度,包含内边距,但不包括垂直滚动条、边框和外边距。

此属性会将获取的值四舍五入取整数。

document.querySelector("span").clientWidth

clientHeight 和 clientWidth 支持性一致
image

1.2、scrollHeight / scrollWidth 属性

类似于上方的 clientHeight / clientWidth,不同在于 clientHeight / clientWidth 在元素设置 overflow 后,不包含隐藏不可见的高度部分。而 scrollHeight / scrollWidth 却包含隐藏的那部分高度。

我觉得利用这个特性来判断是否超长,如果 scrollWidth 大于 clientWidth,则表示超长,此时可以对于那些超长后显示...的元素在浮上去后展示一个自定义的 toolip.

scrollHeight 和 scrollWidth 支持性一致
image

1.3、getBoundingClientRect()方法

返回元素的大小及其相对于视口的位置(眼睛看的见的文档区域)。

这个方法超级好用。不管你的元素在什么位置,它都会计算出来当前元素 相当于 视口的边缘的位置,和滚动条无关。

image

返回值为

{
    bottom: xx,  //元素底部离视口顶部的距离
    height: xx,   //元素高度,和元素的clientHeight属性一致,但比它精确,会保留小数
    left: xx,  //元素左边离视口左侧的距离
    right: xx,  //元素右边离视口左侧的距离
    top: xx,   //元素上部离视口顶部的距离
    width: xx,   //元素宽度,和元素的clientWidth属性一致,但比它精确,会保留小数
}

image
这个方法的支持性为 100%, 良心啊,为什么我才知道这个方法?

1.4、scrollIntoView()方法

如果父元素定义了 overflow,并产生了滚动条,里面有个子元素,在滚动条滑动后,看不到了。那么可以让这个子元素执行这个方法,让元素滚动到父元素可视区域内。

可以定义滚动条可视区域的顶部,底部,还是左边,右边。
也可以定义平滑的滚动过来,还是一瞬间滚动过来。

这个方法的好处是自动计算里面子元素距离可见区域多少,不需要给数值进行人为干涉,而 scroll()或 scrollTo()方法,必须人为计算滚动条的距离,然后给个数值,才能滚动到某个位置。

首先看个例子,
image

div1 超过屏幕的区域有个 div2,超过 div2 的区域有个 span1,此时我们可以通过调用 span1 元素的 scrollIntoView 方法,让 span1,滚动到 div2 的可见区域,也就是向左边滚动,但是有个坏处,就是 div1 也会向上滚动,直到 div2 能被用户看到。
而 scroll()或 scrollTo()方法在 span1 滚动到 div2 的可见区域时,div1 不动。不会产生其他附加作用,直到你慢慢滑屏,才会看到 span1,已经停留在 div2 的可视范围了。

image

1.5、scroll() / scrollTo() / scrollBy() 方法

scrollTo 方法可以使界面滚动到给定元素的指定坐标位置。

以产生滚动条的那个父元素为基准,

scroll() scrollBy() scrollTo()合称为 scrollOptions API
image

2、element 事件

2.1、contextmenu 事件

contextmenu 事件会在用户尝试打开上下文菜单时被触发。该事件通常在鼠标点击右键或者按下键盘上的菜单键时被触

<p id="noContextMenu">这个段落右键菜单已被禁用。</p>



noContext = document.getElementById('noContextMenu');

noContext.addEventListener('contextmenu', e => {
  e.preventDefault();
});

单击右键,不会出现标准的右键菜单项目

image

2.2、copy 事件/cut 事件/ paste 事件

dom 的复制/剪切, 粘贴事件。

<div class="source">
  Try copying text from this box...
</div>
<div class="target" contenteditable="true">
  ...and pasting it into this one
</div>
let sourceDom = document.querySelector(".source");
sourceDom.addEventListener("copy", function (e) {
  //将数据存入剪切板
  e.clipboardData.setData("abc", "Hello, world!");
  e.preventDefault();
});

let targetDom = document.querySelector(".target");
targetDom.addEventListener("paste", function (e) {
  //从剪切板取出来
  let data = e.clipboardData.getData("abc");

  //自定义转化数据
  data = data.toUpperCase();

  //获取光标位置
  const selection = window.getSelection();
  if (!selection.rangeCount) return false;
  selection.deleteFromDocument();
  //将转换后的数据插入到光标位置
  selection.getRangeAt(0).insertNode(document.createTextNode(data));

  e.preventDefault();
});

这三个事件支持性一致

image

2.3、focusin 事件 / focusout 事件

当元素获得焦点时,focusin 事件被触发。
focusin 事件和 focus 事件之间的主要区别在于 focus 不会冒泡。

当元素即将失去焦点时,focusout 事件被触发。
focusout 事件和 blur 事件之间的主要区别在于 blur 不会冒泡。

image

2.4、onscroll / onwheel

类似于 window 的 onscroll 和 onwheel。只不过绑定对象为 element

let sourceDom = document.querySelector(".source");
sourceDom.addEventListener("scroll", function (e) {
    console.log("111");
});

sourceDom.addEventListener("wheel", function (e) {
    console.log("111");
});

三、document(文档对象)

1、document 属性和方法

1.1、characterSet

返回当前文档的字符编码,但有相当一部分浏览器未实现,可使用原始的 charset 代替

document.characterSet || document.charset
1.2、compatMode

表明当前文档的渲染模式是怪异模式/混杂模式还是标准模式。

document.compatMode  //  BackCompat怪异模式, CSS1Compat标准模式

image

1.3、defaultView

返回当前 document 对象所关联的 window 对象,如果没有,会返回 null。

document.defaultView   //返回还是window,我为什么不直接 使用window呢?

上面的代码很平淡,不足为奇,可是下面的就不一定了

<div class="source" style="height: 200px; overflow: auto;">
    <object type="text/html" style="width: 100%; height: 100%;"> </object>
</div>
let doc = document.querySelector("object").contentDocument; //得到一个document对象

doc.defaultView; //接着得到一个window

得到这个 window 后,有什么用呢?

我们可以想象一个场景,当我们缩放 window 窗口时,会触发 window 的 onresize 事件,但是缩放 div,却不会触发 onresize 事件,因为 dom 没有 onresize,那么如何监听一个 div 的 resize 呢?

就是上述的方式,在 div 中套一个 object。我不监听 div,我监听 div 中的 object,一旦 div 变了,object 不也就变了吗?

然后通过 object 返回的一个 window 对象,就自然可以绑定 onresize 事件了

document.querySelector("object").contentDocument.defaultView .addEventListener("resize", () => {
    //只要div尺寸变化,object尺寸就变化,resize就能监听到。不管何种原因导致的尺寸变化。都会监听到。
});

image

除此之外,我有一遍专门介绍如何彻底解决 div 尺寸变化问题的文章,有兴趣的伙伴可以阅读

1.4、designMode

控制整个文档是否可编辑。有效值为 "on" 和 "off" 。

默认值为 "off" 。如果设置为"on",则好比给 html 所有的元素都添加了 contenteditable 属性。

image

1.5、documentElement

会返回文档对象(document)的根元素。

可以通过 document.documentElement.clientHeight 来获取浏览器的可用高度,这个高度和 html 或者 body 的 style 上的 height 无关,只与浏览器的上方地址栏,下方工具栏等有关,和 window.innerHeight 相等。

image

2、document 事件

2.1、onscroll / onwheel

类似于 window 的 onscroll 和 onwheel。只不过绑定对象为 document

document.addEventListener("scroll", function (e) {
    console.log("111");
});

document.addEventListener("wheel", function (e) {
    console.log("111");
});
查看原文

赞 61 收藏 51 评论 4

xmax 收藏了文章 · 7月7日

那些不常见,但却非常实用的js知识(整理不易)

一、window

window 对象表示一个包含 DOM 文档的窗口,其 document 属性指向窗口中载入的 DOM 文档 。

1、window 属性和方法

在有标签页功能的浏览器中,每个标签都拥有自己的 window 对象;也就是说,同一个窗口的标签页之间不会共享一个 window 对象。

1.1、几个浏览器的高度

window.screen.height==window.screen.availHeight
表示手机的屏幕高度,在一部手机中是固定的。不同浏览器打开,都不会变。

window.innerHeight==document.documentElement.clientHeight
表示浏览器中,除去顶部地址栏,下部工具栏之外,暴露给用户,可以看的见的中间区域。也就是实际的网页浏览高度,不同浏览器不同。

document.body.clientHeight
body 元素的高度。
如果设置 body 的 height:30px; 那么这个属性就是 30。

下面是示意图,在 pc 端同理
image

1.2、window.performance

Web Performance API 允许网页访问某些函数来测量网页和 Web 应用程序的性能,包括 Navigation Timing API 和高分辨率时间数据。

performance.now()

该方法返回一个 DOMHighResTimeStamp 对象,该对象表示从某一时刻(译者注:某一时刻通常是 navigationStart 事件发生时刻)到调用该方法时刻的毫秒数。

image

1.3、devicePixelRatio

返回当前显示设备的物理像素分辨率与 CSS 像素分辨率之比。

物理像素分辨率表示,你的电脑的硬件部分,在出场时,已经确定。

而 css 像素分辨率,则可以动态调整。
当电脑显示设置为 100%时,物理像素分辨率和 css 像素分辨率是相等的。即 devicePixelRatio 为 1

window.devicePixelRatio   //1

image

而如果你将电脑设置为 150%;或者在电脑设置 100%时,同时将浏览器分辨率调成 150%,那么 devicePixelRatio 都为 1.5
image

image

window.devicePixelRatio   //1.5

你可以将 devicePixelRatio 为 1.5 理解为,1px 的 css 样式,占用了电脑硬件屏幕分辨路的 1.5 个 dpi,所以肉眼看起来电脑上的文字更大些。

这种放大的效果在某些情况下,会导致 html 中的元素失真,典型的便是 canvas 变得模糊。

下面是矫正代码

//size为原本的大小
let scale = window.devicePixelRatio;
canvas.width = Math.floor(size * scale);
canvas.height = Math.floor(size * scale);

image

1.4、base64 编码/解码
window.btoa() 编码为 base64

window.atob() 解码

let encodedData = window.btoa("Hello, world"); // 编码
let decodedData = window.atob(encodedData);    // 解码

image

1.5、requestAnimationFrame / cancelAnimationFrame()

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。

回调函数执行次数通常是每秒 60 次,但在大多数遵循 W3C 建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配。但是却和 setInterval(),不同,因为 setInterval 设置的秒,并不一定准确在对应秒后执行,而是需要看是否有其他资源在执行,如果有,则等待。实际可能大于等于设置的秒数,而 requestAnimationFrame 会准确的按照浏览器渲染的频率想匹配

//兼容性处理
var requestAnimationFrame =
  window.requestAnimationFrame ||
  window.mozRequestAnimationFrame ||
  window.webkitRequestAnimationFrame ||
  window.msRequestAnimationFrame;

var cancelAnimationFrame =
  window.cancelAnimationFrame || window.mozCancelAnimationFrame;

var start = window.mozAnimationStartTime; // 只有Firefox支持mozAnimationStartTime属性,其他浏览器可以使用Date.now()来替代.

var myReq;
function step(timestamp) {
  var progress = timestamp - start;
  d.style.left = Math.min(progress / 10, 200) + "px";
  if (progress < 2000) {
    myReq = requestAnimationFrame(step); //必须再调用一次,这样就形成了递归
  }
}
myReq = requestAnimationFrame(step);

//当不需要是取消动画,比如 在vue destoryed中
window.cancelAnimationFrame(myReq);

image

image

1.6、getSelection()

此方法不必传入具体的 dom,选中哪个区域或者光标在哪个输入框, 此方法会获取到那个 dom。 返回一个selection对象

window.getSelection();

image

1.7、scroll() / scrollBy() / scrollTo()

scroll()滚动至文档中的绝对位置(类似于 css 中的 display:absolute), scrollBy()滚动指定的距离(类似于 css 中的 display:relative)

scrollTo 和 scroll 一致。

window.scroll({
  top: 100,
  left: 100,
  behavior: 'smooth'  //表示是否平滑的过渡还是即可调到指定位置,同css属性scroll-behavior
});
window.scrollBy({
  top: 100,
  left: 100,
  behavior: "smooth"
});

scroll() scrollBy() scrollTo()合称为 scrollOptions API
image

2、window 事件

通常在定义 window 中的事件时,有两种方式:

  • window.onXXX = function(){};
  • window.addEventListener("XXX", ()=>{}, {capture:true, once:true, passive:true});

那么这两种方式有区别吗? 有区别,但也没区别。

因为虽然定义的方式不同,可最终都会走到事件中。

但是又有区别,尤其在多个组件中,同时定义相同的事件,那么 window.onXXX 会后面定义的把前面定义的相同事件覆盖掉。最终只会执行最后定义的那个。而 window.addEventListener 不同,定义几个,执行几次,而且还可以再 options 中设置 once,capture,passive 等选项。

以 passive 为例,它可以忽略事件得默认行为,不会阻止它。当你在 js 操作时,html 中会立马响应到。最典型得可以使得触摸事件和 scroll 事件不再卡顿,非常流畅。

通常有 XXX 方式,那么对应也会有 onXXX

2.1、onbeforeunload

当窗口即将被卸载(关闭)时,会触发该事件.此时页面文档依然可见,且该事件的默认动作可以被取消.

需要注意的是,点击浏览器标签页的 x 号前,如果没有对页面进行过任何“操作”,则不会弹框直接关闭,如果进行过操作,则会弹出确认框。

这里所指的“操作”包括,但不限于,input 输入,打开 console,复制页面的一段文字等等。

window.onbeforeunload = () => {
    let isIE = xxxxxxxx; //忽略通过user-agent判断ie浏览器的方法
    if (isIE) {
      return "The system may be not save your changes.";
    } else {
      return false; //其他浏览器会弹出自己的文字,如上图。是中文
    }
};

image

2.2、onhashchange

当 一个窗口的 hash (URL 中 # 后面的部分)改变时就会触发 hashchange 事件(参见 location.hash)。

window.onhashchange = (newURL, oldURL)=>{
    //newURL 当前页面新的URL
    //oldURL 当前页面旧的URL
};

image

2.3、onload / DOMContentLoaded

DOMContentLoaded 指 html 页面加载完毕就会触发,至于一些异步资源的比如 img,video 等等,它不关心。

而 onload 表示除了 html 加载完,img,video 等也加载完,才执行

window.addEventListener("load", function () {

});

window.addEventListener("DOMContentLoaded", function () {

});

image

image

看到没: JavaScript event that fires when the DOM is loaded, but before all page assets are loaded (CSS, images, etc.). 说的再清楚不过了

2.4、onmouseover、onmouseout / onmouseenter、onmouseleave

onmouseover、onmouseout:鼠标移动到自身时候会触发事件,同时移动到其子元素身上也会触发事件 onmouseenter、onmouseleave:鼠标移动到自身是会触发事件,但是移动到其子元素身上不会触发事件

onmouseover 和 onmouseout 支持性一致
image

onmouseenter 和 onmouseleave 支持性一致
image

2.5、onscroll / onwheel

onwheel 事件在鼠标滚轮在元素上下滚动时触发,必须滚轮要转,至于页面滚不滚动,它不关心。

onscroll 事件在元素滚动条在滚动时触发,必须滚动条存在且上下运动。
触发方式有:滚轮滚动,鼠标按住滚动条拖动,键盘上下键滚动,js 脚本去滚动如 scrollTo,scrollBy,scrollByLines, scrollByPages 等。

在这里有个特殊,在手机端,虽然没有鼠标,但是手指触摸上下滚动,也是触发 onscroll 的。

image

image
什么? 支持率这么一点点? 大大出乎我的意料

2.6、copy 事件/cut 事件/ paste 事件

window 的复制/剪切, 粘贴事件。

window.addEventListener("copy", function (e) {
    //将复制的数据,存入到剪切板中
    e.clipboardData.setData("abc", "Hello, world!");
    e.preventDefault();
});

window.addEventListener("paste", function (e) {
    //再从剪切板中取出数据
    let data = e.clipboardData.getData("abc");
    e.preventDefault();
});

这三个事件支持性一致

image

2.7、error 事件

当资源加载失败或无法使用时,会在 Window 对象触发 error 事件。例如:script 执行时报错。

window.addEventListener('error', (event) => {

});

image

2.8、orientationchange 事件

orientationchange 事件在设备的纵横方向改变时触发。

window.addEventListener("orientationchange", function() {

});

image
pc 上几乎都不支持,这也能理解,毕竟 pc 上是使用 resize 的,只有手机才会使用 横竖屏。

2.9、rejectionhandled 事件 / unhandledrejection 事

当 Promise 被 rejected 且有 rejection 处理器时会在全局触发 rejectionhandled。

当 Promise 被 reject 且没有 reject 处理器的时候,会触发 unhandledrejection 事件;

window.addEventListener("rejectionhandled", event => {

}, false);

window.addEventListener("unhandledrejection", event => {

});


function getName() {
    return Promise.reject("error");
}

let p = getName();   //触发unhandledrejection
setTimeout(() => {
    p.catch((err) => {});  //触发rejectionhandled
}, 1000);

image

image

2.10、storage 事件

当 localStorage 被修改时,将触发 storage 事件。

注意:

  • 只有在同域名的不同文件之间,才能够监听到,
  • 同一个文件中,是监听不到的(合理,同一个文件直接拿就行了,用 storage 监听多此一举)
  • 通过 iframe 引入的另一个站点,也可以监听到

image

2.11、online 事件 / offline 事件

online 当网络连接时,触发,
offline 当网络断开时触发

online 和 offline 支持性一致
image

二、element(DOM 对象)

1、element 属性和方法

1.1、clientHeight / clientWidth 属性
  • clientHeight:元素内部的高度,包含内边距,但不包括水平滚动条、边框和外边距。
  • clientWidth:元素内部的宽度,包含内边距,但不包括垂直滚动条、边框和外边距。

此属性会将获取的值四舍五入取整数。

document.querySelector("span").clientWidth

clientHeight 和 clientWidth 支持性一致
image

1.2、scrollHeight / scrollWidth 属性

类似于上方的 clientHeight / clientWidth,不同在于 clientHeight / clientWidth 在元素设置 overflow 后,不包含隐藏不可见的高度部分。而 scrollHeight / scrollWidth 却包含隐藏的那部分高度。

我觉得利用这个特性来判断是否超长,如果 scrollWidth 大于 clientWidth,则表示超长,此时可以对于那些超长后显示...的元素在浮上去后展示一个自定义的 toolip.

scrollHeight 和 scrollWidth 支持性一致
image

1.3、getBoundingClientRect()方法

返回元素的大小及其相对于视口的位置(眼睛看的见的文档区域)。

这个方法超级好用。不管你的元素在什么位置,它都会计算出来当前元素 相当于 视口的边缘的位置,和滚动条无关。

image

返回值为

{
    bottom: xx,  //元素底部离视口顶部的距离
    height: xx,   //元素高度,和元素的clientHeight属性一致,但比它精确,会保留小数
    left: xx,  //元素左边离视口左侧的距离
    right: xx,  //元素右边离视口左侧的距离
    top: xx,   //元素上部离视口顶部的距离
    width: xx,   //元素宽度,和元素的clientWidth属性一致,但比它精确,会保留小数
}

image
这个方法的支持性为 100%, 良心啊,为什么我才知道这个方法?

1.4、scrollIntoView()方法

如果父元素定义了 overflow,并产生了滚动条,里面有个子元素,在滚动条滑动后,看不到了。那么可以让这个子元素执行这个方法,让元素滚动到父元素可视区域内。

可以定义滚动条可视区域的顶部,底部,还是左边,右边。
也可以定义平滑的滚动过来,还是一瞬间滚动过来。

这个方法的好处是自动计算里面子元素距离可见区域多少,不需要给数值进行人为干涉,而 scroll()或 scrollTo()方法,必须人为计算滚动条的距离,然后给个数值,才能滚动到某个位置。

首先看个例子,
image

div1 超过屏幕的区域有个 div2,超过 div2 的区域有个 span1,此时我们可以通过调用 span1 元素的 scrollIntoView 方法,让 span1,滚动到 div2 的可见区域,也就是向左边滚动,但是有个坏处,就是 div1 也会向上滚动,直到 div2 能被用户看到。
而 scroll()或 scrollTo()方法在 span1 滚动到 div2 的可见区域时,div1 不动。不会产生其他附加作用,直到你慢慢滑屏,才会看到 span1,已经停留在 div2 的可视范围了。

image

1.5、scroll() / scrollTo() / scrollBy() 方法

scrollTo 方法可以使界面滚动到给定元素的指定坐标位置。

以产生滚动条的那个父元素为基准,

scroll() scrollBy() scrollTo()合称为 scrollOptions API
image

2、element 事件

2.1、contextmenu 事件

contextmenu 事件会在用户尝试打开上下文菜单时被触发。该事件通常在鼠标点击右键或者按下键盘上的菜单键时被触

<p id="noContextMenu">这个段落右键菜单已被禁用。</p>



noContext = document.getElementById('noContextMenu');

noContext.addEventListener('contextmenu', e => {
  e.preventDefault();
});

单击右键,不会出现标准的右键菜单项目

image

2.2、copy 事件/cut 事件/ paste 事件

dom 的复制/剪切, 粘贴事件。

<div class="source">
  Try copying text from this box...
</div>
<div class="target" contenteditable="true">
  ...and pasting it into this one
</div>
let sourceDom = document.querySelector(".source");
sourceDom.addEventListener("copy", function (e) {
  //将数据存入剪切板
  e.clipboardData.setData("abc", "Hello, world!");
  e.preventDefault();
});

let targetDom = document.querySelector(".target");
targetDom.addEventListener("paste", function (e) {
  //从剪切板取出来
  let data = e.clipboardData.getData("abc");

  //自定义转化数据
  data = data.toUpperCase();

  //获取光标位置
  const selection = window.getSelection();
  if (!selection.rangeCount) return false;
  selection.deleteFromDocument();
  //将转换后的数据插入到光标位置
  selection.getRangeAt(0).insertNode(document.createTextNode(data));

  e.preventDefault();
});

这三个事件支持性一致

image

2.3、focusin 事件 / focusout 事件

当元素获得焦点时,focusin 事件被触发。
focusin 事件和 focus 事件之间的主要区别在于 focus 不会冒泡。

当元素即将失去焦点时,focusout 事件被触发。
focusout 事件和 blur 事件之间的主要区别在于 blur 不会冒泡。

image

2.4、onscroll / onwheel

类似于 window 的 onscroll 和 onwheel。只不过绑定对象为 element

let sourceDom = document.querySelector(".source");
sourceDom.addEventListener("scroll", function (e) {
    console.log("111");
});

sourceDom.addEventListener("wheel", function (e) {
    console.log("111");
});

三、document(文档对象)

1、document 属性和方法

1.1、characterSet

返回当前文档的字符编码,但有相当一部分浏览器未实现,可使用原始的 charset 代替

document.characterSet || document.charset
1.2、compatMode

表明当前文档的渲染模式是怪异模式/混杂模式还是标准模式。

document.compatMode  //  BackCompat怪异模式, CSS1Compat标准模式

image

1.3、defaultView

返回当前 document 对象所关联的 window 对象,如果没有,会返回 null。

document.defaultView   //返回还是window,我为什么不直接 使用window呢?

上面的代码很平淡,不足为奇,可是下面的就不一定了

<div class="source" style="height: 200px; overflow: auto;">
    <object type="text/html" style="width: 100%; height: 100%;"> </object>
</div>
let doc = document.querySelector("object").contentDocument; //得到一个document对象

doc.defaultView; //接着得到一个window

得到这个 window 后,有什么用呢?

我们可以想象一个场景,当我们缩放 window 窗口时,会触发 window 的 onresize 事件,但是缩放 div,却不会触发 onresize 事件,因为 dom 没有 onresize,那么如何监听一个 div 的 resize 呢?

就是上述的方式,在 div 中套一个 object。我不监听 div,我监听 div 中的 object,一旦 div 变了,object 不也就变了吗?

然后通过 object 返回的一个 window 对象,就自然可以绑定 onresize 事件了

document.querySelector("object").contentDocument.defaultView .addEventListener("resize", () => {
    //只要div尺寸变化,object尺寸就变化,resize就能监听到。不管何种原因导致的尺寸变化。都会监听到。
});

image

除此之外,我有一遍专门介绍如何彻底解决 div 尺寸变化问题的文章,有兴趣的伙伴可以阅读

1.4、designMode

控制整个文档是否可编辑。有效值为 "on" 和 "off" 。

默认值为 "off" 。如果设置为"on",则好比给 html 所有的元素都添加了 contenteditable 属性。

image

1.5、documentElement

会返回文档对象(document)的根元素。

可以通过 document.documentElement.clientHeight 来获取浏览器的可用高度,这个高度和 html 或者 body 的 style 上的 height 无关,只与浏览器的上方地址栏,下方工具栏等有关,和 window.innerHeight 相等。

image

2、document 事件

2.1、onscroll / onwheel

类似于 window 的 onscroll 和 onwheel。只不过绑定对象为 document

document.addEventListener("scroll", function (e) {
    console.log("111");
});

document.addEventListener("wheel", function (e) {
    console.log("111");
});
查看原文

xmax 发布了文章 · 7月2日

简析CSS盒模型(Box Model)

CSS 盒子模型(Box Model)

每个HTML元素都可以看作是一个盒子,父元素和子元素的关系就行大盒子里放了个小盒子,兄弟元素就像在大盒子平放了两个小盒子。盒模型包括:外边距(margin)、边框(border)、内边距(padding)、实际内容(content)四个属性。

  • 个人觉得盒模型主要是用来区分如何计算元素的大小

标准盒模型 (W3C盒模型)

标准的盒模型由content内容+padding内填充+border边框+margin外边距组成
盒子的大小由:内容的宽高+内填充的大小+边框的大小+外边距的大小决定
div{
    width: 200px;
    height: 200px;
    background: red;
    padding:10px;
    border:10px solid #ccc;
    margin:10px;
}
//盒子宽:260px;高:260px;
//div元素宽:240px;高:240px;
//content宽200px,高:200px;

image.png

怪异盒模型 (IE盒模型)

怪异盒模型由content内容+padding内填充+border边框组成
盒子的大小由:width、height决定
div{
    width: 200px;
    height: 200px;
    background: red;
    padding:10px;
    border:10px solid #ccc;
    margin:10px;
}
//盒子宽:200px;高:200px;
//div元素宽:200px;高:200px;
//content宽160px,高:160px;

image.png

  • 既标准盒模型的width、height是指content的大小,怪异盒模型的width、height是指content+padding+border的大小
  • 当box-sizing为content-box时,使用的是W3C盒模型,当box-sizing为border-box时,使用的是IE盒模型。

内边距padding

  • padding-方向(left,right,top.bottom)表示给指定方向设置内边距。
  • padding 简写,其属性值遵循上右下左的顺序,若缺值找反向的值。若只有一个值,则上右下左方向的值都一样。
div{
    width: 200px;
    height: 200px;
    background: red;
    padding:10px 20px;
    padding-left:30px;
} 

外边距margin

  • margin-方向(left,right,top.bottom)表示给指定方向设置外边距。
  • margin 简写,其属性值遵循上右下左的顺序,若缺值找反向的值。若只有一个值,则上右下左方向上的值都是一样的。
div{
    width: 200px;
    height: 200px;
    background: red;
    margin:10px 20px 30px 40px;
    margin-top:50px;
}

边框属性 border

1.边框宽度 border-width

2.边框样式 border-style,属性值为:solid实线 dashed虚线 dotted点线 double双线

3.边框颜色 border-color

div{
    width: 200px;
    height: 200px;
    border-width:3px;
    border-style: double;
    border-color:red;
    <!--border:1px solid #ccc;合并写法-->
}

4.改变某条边框的属性值,border-方向left、right、top、bottom)

border-left:2px dashed red;
border-bottom:4px solid orange;

5.改变某条边框的具体某个属性的属性值,border-方向-属性(width、style、color)

border-right-style:dotted;
border-left-color:pink;
border-top-width:1px;
查看原文

赞 0 收藏 0 评论 0

xmax 收藏了文章 · 6月23日

工作中99%能用到的git命令

【Git】工作中99%能用到的git命令

分支操作

  1. git branch 创建分支
  2. git checkout -b 创建并切换到新建的分支上
  3. git checkout 切换分支
  4. git branch 查看分支列表
  5. git branch -v 查看所有分支的最后一次操作
  6. git branch -vv 查看当前分支
  7. git brabch -b 分支名 origin/分支名 创建远程分支到本地
  8. git branch --merged 查看别的分支和当前分支合并过的分支
  9. git branch --no-merged 查看未与当前分支合并的分支
  10. git branch -d 分支名 删除本地分支
  11. git branch -D 分支名 强行删除分支
  12. git branch origin :分支名 删除远处仓库分支
  13. git merge 分支名 合并分支到当前分支上

暂存操作

  1. git stash 暂存当前修改
  2. git stash apply 恢复最近的一次暂存
  3. git stash pop 恢复暂存并删除暂存记录
  4. git stash list 查看暂存列表
  5. git stash drop 暂存名(例:stash@{0}) 移除某次暂存
  6. git stash clear 清除暂存

回退操作

  1. git reset --hard HEAD^ 回退到上一个版本
  2. git reset --hard ahdhs1(commit_id) 回退到某个版本
  3. git checkout -- file撤销修改的文件(如果文件加入到了暂存区,则回退到暂存区的,如果文件加入到了版本库,则还原至加入版本库之后的状态)
  4. git reset HEAD file 撤回暂存区的文件修改到工作区

标签操作

  1. git tag 标签名 添加标签(默认对当前版本)
  2. git tag 标签名 commit_id 对某一提交记录打标签
  3. git tag -a 标签名 -m '描述' 创建新标签并增加备注
  4. git tag 列出所有标签列表
  5. git show 标签名 查看标签信息
  6. git tag -d 标签名 删除本地标签
  7. git push origin 标签名 推送标签到远程仓库
  8. git push origin --tags 推送所有标签到远程仓库
  9. git push origin :refs/tags/标签名 从远程仓库中删除标签

其它操作

常规操作

  1. git push origin test 推送本地分支到远程仓库
  2. git rm -r --cached 文件/文件夹名字 取消文件被版本控制
  3. git reflog 获取执行过的命令
  4. git log --graph 查看分支合并图
  5. git merge --no-ff -m '合并描述' 分支名 不使用Fast forward方式合并,采用这种方式合并可以看到合并记录
  6. git check-ignore -v 文件名 查看忽略规则
  7. git add -f 文件名 强制将文件提交

git创建项目仓库

1、git init 初始化 2、git remote add origin url 关联远程仓库 3、git pull 4、git fetch 获取远程仓库中所有的分支到本地

忽略已加入到版本库中的文件

1、git update-index --assume-unchanged file 忽略单个文件 2、git rm -r --cached 文件/文件夹名字 (. 忽略全部文件)

取消忽略文件

git update-index --no-assume-unchanged file

拉取、上传免密码

git config --global credential.helper store

查看原文

xmax 收藏了文章 · 6月18日

记一次grid布局实战应用分享会

记一次grid布局实战应用分享会

记录了我在组内的技术分享, 有同样需求的同学可以参考一下
分享全程下来时间大约 45分钟
最终画图如下:↓

表情.png

一. 到底能不能用在工程??(兼容性)

老生长谈的问题了, 但也是决定性的问题,如下图:
兼容.png
对于不用兼容ie浏览器的工程可以玩起来了, grid也不算新技术了, 技术的发展需要coder的推动, 有时候问问自己不用grid的原罪是不是'懒得学'哈哈哈哈, 我查了一下 chrome 57版本是 2017.3月发布的 .
ie使用display: -ms-grid的写法也可以有效, 但是网上看到很多同学遇到了未知错误, 所以如果真的必须兼容ie的话还是不要使用grid.

二. 技能定位(flex的好战友)

grid不会代替flex, 相反他两位是非常棒的搭档, 配合使用简直开始螺旋人生, grid布局可以打破dom位置的限制,使用不好的话容易造成语义混乱, 导致鲁棒性下降, 这次我们就一起来探究一下grid布局的实战场景.
timg.jpg

三. 从上下左右居中'新方法'引导兴趣

烂大街的一个面试题, 居中的方法.
你可以秀出grid了, 那么分数一定多加一分到两分.. 此处我介绍'四种'之后的内容会解释为什么会这样, 而且还有很多qiyinqiaoji... 可以说学会了grid布局那么你的居中技巧就多出了6种以上!

整体dom结构如下
<div class="wrap">
     <div class="box"></div>
  </div>
固定的样式
.box{
      width: 100px;
      height: 100px;
      border: 1px solid red;
    }

实现方法: 有点玩赖, 就是两个属性的随意组合,突出一个'秀'
注意这里的属性不是flex的, 他在grid布局模式下就是属于grid.

.wrap{
      // 当然了开启布局的方式也与flex一样
      display: inline-grid | grid;
      width: 300px;
      height: 300px;
      // 第一种
      align-items: center;
      justify-items: center;
      // 第二种
      align-items: center;
      justify-content: center;
      // 第三种
      align-content: center;
      justify-items: center;
      // 第四种
      align-content: center;
      justify-content: center;
    }

四. 横排的排列

开启布局时, 我们打开调试模式鼠标悬停在dom上会出现网格
与flex不一样, 单纯的开启布局并不会影响元素的布局,不会压缩抻拉宽高.
  <style>
    /* 只是开启grid布局不会有变化, 不像flex布局默认把内部的元素排成一排 */
     .wrap{
       display: grid;
       height: 100px;
       width: 300px;
       border: 1px solid;
     }
     .box{
       height: 50px;
       width: 50px;
       border: 1px solid red;
     }
  </style>
</head>
<body>
   <div class="wrap">
     <div class="box">1</div>
     <div class="box">2</div>
     <div class="box">3</div>
   </div>
</body>

格子.png

grid-template-columns 定义把dom横排分成几份,每份宽度是多少.

这里就是把dom分为4段, 每段100px宽, 然后我们就可以往里面放内容了.
grid-template-columns: 100px 100px 100px 100px;
分割.png

横排排列
<style>
     .wrap{
       display: grid;
       height: 100px;
       width: 300px;
       border: 1px solid;
       /* 1: 成为一行, 开启这个 */
       /* 总共3列, 每列100px */
       grid-template-columns: 100px 100px 100px;
     }
     .box{
       height: 50px;
       /* 子组件不设置宽度就会自动填充单元格 */
       /* 与上面的3个100px相对应*/
       border: 1px solid red;

     }
  </style>
</head>
<body>
   <div class="wrap">
     <div class="box">1</div>
     <div class="box">2</div>
     <div class="box">3</div>
   </div>
</body>

横排对齐.png

repeat 定义重复的部分

比如我们要把横排分成10份, 那我们写10个100px显然很笨拙,可以使用如下的方式:
grid-template-columns: repeat(10, 100px);

我们想实现如下效果:

grid-template-columns: 100px 100px 100px 100px 100px 100px 200px 100px 100px 100px 100px;
可以这样写:
grid-template-columns: repeat(6, 100px) 200px repeat(4, 100px);

重复的类型也可以多样

grid-template-columns: 100px 200px 100px 200px 100px 200px 100px 200px 100px 200px 100px 200px
可以如下的方式书写:(要注意这里只有一个'逗号')
grid-template-columns: repeat(6, 100px 200px);

auto-fill属性 根据设定的宽度,自动填充

比如我dom的宽度是400px 那么下面的代码就会生成 10个 40px宽的格子, 当然如果dom宽是390px, 它只会生成9个;
grid-template-columns: repeat(auto-fill, 40px);

技巧跳列
比如我们把横排分为3个格子每个100px, 但是我只放入两个元素, 分别在第1个与第3个格子, 那么我们可以使用一个技巧, 就是中间放一个空元素, 而这个空元素应尽可能小的消耗内存, 那么可以尝试放一个br标签, 当然这种方式不推荐, 布局上会给其他同学造成一些困扰, 代码如下:
 <style>
     .wrap{
       display: grid;
       height: 100px;
       width: 300px;
       border: 1px solid;
       grid-template-columns: 100px 100px 100px 100px;
     }
     .box{
       height: 50px;
       border: 1px solid red;
     }
  </style>
</head>
<body>
   <div class="wrap">
     <div class="box">1</div>
     <br>
     <div class="box">3</div>
   </div>
</body>

两端.png

五. [fr, auto] 单位与关键字

fr

我们知道在flex布局里面, 比如我写flex:1, 他代表着在flex布局里面占总份数里面的一份, grid里面也有类似的属性, 而且更加的定制化语义化, 他就是fr.

下面这段代码的意思就是把dom分成5份, 每份就是占一份的宽.

grid-template-columns: repeat(5,1fr);

大家瓜分剩余空间

grid-template-columns: 120px 1fr 2fr 3fr 1fr;
分配.png

auto

auto关键字, 单独存在时功能与1fr差不多, 但是语义化更明显
grid-template-columns: 20px 20px auto 20px 20px;
auto.png

auto与1fr的区别, 他不参与fr的计算规则, 所以与fr同时使用会被挤成自身宽度
grid-template-columns: 20px 2fr auto 1fr 20px;
下图里面的3就是靠自身撑起的宽度
auto被挤压.png

六. minmax限定范围

下面设置最小宽与最大宽

<style>
     .wrap{
       display: grid;
       border: 1px solid;
       width: 300px;
       height: 100px;
       /* 最小值, 最大值, 比如被压缩的时候要留一个基本宽度*/
       /* 里面可以填写fr为单位的数字, 这就是好处 */
       grid-template-columns: 1fr 1fr minmax(300px,1fr);
     }
     .box{
       border: 1px solid red;
     }
  </style>
</head>
<body>
   <div class="wrap">
     <div class="box">1</div>
     <div class="box">2</div>
     <div class="box">3</div>
   </div>
当父级320px宽

4F0B4A83-0DE2-468c-85F7-5F4D807C5ED5.png

当父级200px宽

父级窄.png

七. 纵列格子

我们已经把横向弄忘了,就像学会了九阳神功的张无忌学乾坤大挪移.
grid-template-rows 属性就是定义列的
当然, 在行可以用的技巧, 在列上都可以使用

  <style>
     .wrap{
       display: grid;
       border: 1px solid;
       height: 200px;
       width: 500px;
       grid-template-columns: 1fr 1fr 100px;
       grid-template-rows: 30px 60px 1fr;
     }
     .box{
       border: 1px solid red;
     }
  </style>
</head>
<body>
   <div class="wrap">
     <div class="box">1</div>
     <div class="box">2</div>
     <div class="box">3</div>
     <div class="box">4</div>
     <div class="box">5</div>
     <div class="box">6</div>
     <div class="box">7</div>
     <div class="box">8</div>
     <div class="box">9</div>
   </div>
</body>

纵横.png

八. 元素间隔

距离产生美, 这么多格子当然需要点距离啦。

固定代码
<style>
     .wrap{
       display: grid;
       border: 1px solid;
       height: 300px;
       width: 300px;
       grid-template-columns: 1fr 1fr 1fr;
       grid-template-rows:  1fr 1fr 1fr;
     }
     .box{
       border: 1px solid red;
     }
  </style>
</head>
<body>
   <div class="wrap">
     <div class="box">1</div>
     <div class="box">2</div>
     <div class="box">3</div>
     <div class="box">4</div>
     <div class="box">5</div>
     <div class="box">6</div>
     <div class="box">7</div>
     <div class="box">8</div>
     <div class="box">9</div>
   </div>
</body>
添加: grid-row-gap: 10px;

航向.png

添加:grid-column-gap: 10px;

纵向.png

当然了,这两个属性可以简写为 grid-gap:10px;或者grid-gap:10px 10px;

大宝.png
给父级一个padding:10px
判定.png

注意: 暂时不支持这样写grid-row-gap: 10px 20px;也就是说不固定的间距需要想别的办法了。

我们可以在画格子的时候, 把间距也当格子画。

九. 颠倒

grid-auto-flow: column; 格子的定位从横向变为了纵向

  <style>
     .wrap{
       display: grid;
       border: 1px solid;
       height: 300px;
       width: 300px;
       grid-template-columns: 1fr 1fr 1fr;
       grid-template-rows:  1fr 1fr 1fr;
       padding: 10px;
       grid-gap:10px;
       /* 定义排列的顺序, 类似方块旋转了 */
       /* 比如说竖排的布局, 类似对联的效果就简单实现了 */
       grid-auto-flow: column; 
     }
     .box{
       border: 1px solid red;
     }
  </style>
</head>
<body>
   <div class="wrap">
     <div class="box">1</div>
     <div class="box">2</div>
     <div class="box">3</div>
     <div class="box">4</div>
     <div class="box">5</div>
     <div class="box">6</div>
     <div class="box">7</div>
     <div class="box">8</div>
     <div class="box">9</div>
   </div>
</body>

颠倒.png

十. 居中对齐方式的解析

这里我们就可以解释一下最开始说的对齐方式的原理。

第一部分: 网格的位置

  <style>
     .wrap{
       display: grid;
       border: 1px solid;
       height: 330px;
       width: 330px;
       /* 当单元格小于容器时效果显著 */
       grid-template-columns: 80px 80px 80px;
       grid-template-rows:  80px 80px 80px;
       grid-gap:10px;

     }
     .box{
       border: 1px solid red;
     }
  </style>
</head>
<body>
   <div class="wrap">
     <div class="box">1</div>
     <div class="box">2</div>
     <div class="box">3</div>
     <div class="box">4</div>
     <div class="box">5</div>
     <div class="box">6</div>
     <div class="box">7</div>
     <div class="box">8</div>
     <div class="box">9</div>
   </div>
</body>
居中
 justify-content: center; 
 align-content: center;

6EBD59D3-D366-4841-B317-F1C1A6AB207B.png

居右
 justify-content: end; 
 align-content: center;

右.png

居右下
 justify-content: end; 
 align-content: end;

右下.png

简洁写法,直接定义两个属性

place-content:center

奇怪的取值范围(重点!)

justify-content: 取值范围:left right flex-end flex-start end start

align-content: 取值范围:flex-end flex-start end start

奇怪的点1:align-content不可以用 left right
奇怪的点2:flex-end这种属性居然有效
奇怪的点3:可以用left right 但是不可以用 top bottom

真是奇奇怪怪没有脑袋。。。。我建议采用center end start这样专属grid的意义更加明确。

第二部分: 单元格的对齐

每个小网格就像是excel的一个单元格, 那么这些单元格的排布方式也很有趣,第一种就整体的排布,第二种是自身的排布。

<style>
     .wrap{
       display: grid;
       border: 1px solid;
       height: 300px;
       width: 300px;
       grid-template-columns: 1fr 1fr 1fr;
       grid-template-rows:  1fr 1fr 1fr;
       grid-gap:10px;
       justify-items: center;
       align-items: center;
     }
     .box{
       height: 50px;
       width: 50px;
       border: 1px solid red;
     }
     .box4{
       justify-self:end;
       align-self: end;
     }
     .box5{
       justify-self:start;
       align-self: start;
     }
     .box6{
       justify-self:stretch;
       align-self: stretch;
       border: 1px solid red;
     }
     .box7{
       border: 1px solid red;
     }
  </style>
</head>
<body>
   <div class="wrap">
     <div class="box">1</div>
     <div class="box">2</div>
     <div class="box">3</div>
     <div class="box box4">4</div>
     <div class="box box5">5</div>
     <div class="    box6">6</div>
     <div class="    box7">7</div>
   </div>
</body>

大杂烩.png

  1. 前3个收到父级的 justify-items:` align-items:`影响所以上下左右居中。
  2. 第4个自身布局优先级最高, 出现在右下角
  3. 6与7是因为未设置宽高的情况下,设置了stretch属性导致自身是否被拉伸。

stretch 填满单元格的宽度(默认值)

通过这里的学习, 我们就能明白最开是的居中方式了, 面试的时候你可以大展身手了。

十一. dom排位 grid-row-start

定义格子从哪里开始, 到哪里结束, 这样可以不用br这种站位标签了.
<style>
    /* 只是开启grid布局不会有变化, 不像flex布局默认把内部的元素排成一排 */
     .wrap{
       display: grid;
       border: 1px solid;
       height: 500px;
       width: 500px;
       grid-template-columns: 1fr 1fr 1fr;
       grid-template-rows:  1fr 1fr 1fr;
       grid-gap:10px;
     }
     .box{
       /* height: 50px;
       width: 50px; */
       border: 1px solid red;
     }
     .box1 {
       /* 1: 我可以自由选择我从网格的哪里开始, 哪里结束 */
       /* 也就是说打破了自由排列的起始值 */
       grid-column-start: 2;

       /* 2: 结束就是这个元素占据的’宽度‘ */
       /* 注意: 无法作到跨行的延续 */
       grid-column-end: 4;

       /* 3: 列当然也可以这么玩*/
       /* 他到了下面去, 而不占用空间, 就是说他并不会把全体都往下弄一行 */
       grid-row-start: 2;
       grid-row-end: 4;


       /* 4: 简洁写法, 斜杠挺假的 */
       /* 讨论一下, 为啥要用 斜杠??? 而不是空格 */
       /* 不写就是默认跨越一个 */
       grid-column: 1 / 3;
       grid-row: 1 / 3;
     }
  </style>
</head>
<body>
   <div class="wrap">
     <div class="box box1">1:第一格开始, 第三格结束</div>
     <div class="box">2</div>
     <div class="box">3</div>
     <div class="box">4</div>
   </div>
</body>

乱乱.png
这种写法突然出现 '/'斜线, 我感觉挺不舒服的, 不知道为啥这么设计.

 grid-column: 1 / 3;
 grid-row: 1 / 3;
要注意, 这种布局方式会有计算量的, 我个人不太建议这样玩....

十二. 古怪的居中方式(脑洞)

中规中矩的思维没法走的更快, 让我来抛砖引如一下。

  <style>
     .wrap{
       display: grid;
       border: 1px solid;
       height: 300px;
       width: 300px;
       grid-template-columns: 1fr 1fr 1fr;
       grid-template-rows:  1fr 1fr 1fr;
       /* 1: 把单元格内的项目居中 */
       justify-items: center;
       align-items: center;
     }
     .box{
       /* 可以满足大于单元格的物体居中 */
       height: 250px;
       width: 250px;
       border: 1px solid red;
       /* 2: 设置了9个单元格, 把他放到中间的单元格就ok了 */
       grid-row-start: 2;
       grid-column-start:2 ;
     }
     /* 四个角, 位置被完全混淆了, 不要这样用 */
     .box1{
      grid-column-start: 1;
      grid-column-end: 2;
     }
     .box2{
       grid-column-start: 3;
      grid-column-end: 4;
     }
     .box3{
       /* 涉及到了网格错位的知识, 不建议这么用, 当然你会算别人不一定会算~~~ */
      grid-column-start: 1;
      grid-column-end: 2;
      grid-row-start: 3;
     }
     .box4{
      grid-column-start: 3;
      grid-column-end: 4;
      grid-row-start: 3;
     }
  </style>
</head>
<body>
   <div class="wrap">
     <div class="box1">角</div>
     <div class="box2">角</div>
     <div class="box">***** 这样居中的好处是, 可以在四个角做一些文章,这是一种思维游戏</div>
     <div class="box3">角</div>
     <div class="box4">角</div>
   </div>
</body>

四个角有文章.png
小号.png

坑点: 这四个角有点错乱,你计算好了, 但是其他同学不一定计算的对, 需要把他封装起来.
扩展: 这四个角我们可以放一点花样, 应该挺好看的...

十三. span关键字

grid-column-start: span 2;不用写结尾了, 表明站几个就好了, 当然也有row属性可以设置.

也就是说 span 3 意思就是当前位置 往后3个格子都是我的了

八卦.png

十四. 映射布局(精髓)

有的时候就要直接一点, 这个知识点才是grid的灵魂.

grid-template-areas:

我们照常使用grid-template-columns为dom画格子, 然后为每个单元格定义一个名字, 什么名字都可以, 如果用不上的单元格名字就定义为'.', 举个例子如下:

 grid-template-areas: 
        "header header header header"
        "main   main   .      sidebar"
        "footer footer footer footer";
上面的代码我们可以给内部dom自身一个grid-area: header;属性, 那么 dom就会占据最上面四个格子的位置, 简直太直接了, 相当于多了个缩略图.
只能写出'矩形'的方阵, 并且必须是一体的, 俩个'header'不连贯东一个西一个也不会生效.
  <style>
     .wrap{
       display: grid;
       border: 1px solid;
       height: 300px;
       width: 300px;
       grid-template-columns: repeat(4,1fr);
       grid-template-rows:   repeat(3,1fr);
       grid-gap:10px;
       justify-content: center;
       align-content: center;
       padding: 10px;

      /* 1: 可视化映射, 我的理解这个才是灵魂, 这个功能才是无法取代 */
      /* '.'表示一个空的单元格 */
       grid-template-areas: 
        "header header header header"
        "main main . sidebar"
        "footer footer footer footer";

     }
     .box{
       border: 1px solid red;
     }
     .box1{
      grid-area: header;
     }
     .box2{
      grid-area: main;
     }
     .box3{
      grid-area: sidebar;
     }
     .box4{
      grid-area: footer;
     }
  </style>
</head>
<body>
  <p>可以利用可视化做居中, 没人这样用, 但是如果是你不敢想那就...</p>
   <div class="wrap">
     <div class="box box1">1</div>
     <div class="box box2">2</div>
     <div class="box box3">3</div>
     <div class="box box4">4</div>
   </div>
</body>

可视化.png

别看它简单, 接下来可以做一些有意思的事情了.

十五. 位置挪移要当心

我画了一个简易的游戏, 把点击的部位变黑, 当时设想是做最强大脑里面的那个奇偶消融游戏, 但是grid布局有自身的局限后来放弃了, 相关的问题我通过图片展示一下.

游戏.png
游戏变化.png

当我变换小球位置的时候, 前面的小球会补充之前的位置, 导致位置信息不好算了, 当然我们可以把每个小球固定, 然后在上面覆盖一层, 但这样做也没必要用grid布局了.

十六. 画表情的一点启发(类似我的世界)

之前还画了个'大风车'和'八卦图', 就不在这里展示了, 但是我画了个笑脸的代码还是分享给大家把:
<style>
    .wrap{
      display: grid;
      height: 600px;
      width: 600px;
      margin: 50px auto;
      /* 用auto的话会导致大小不一致, 因为auto算是自适应里面的宽高 */
      grid-template-columns: repeat(15,1fr);
      grid-template-rows: repeat(15,1fr);
      justify-items: stretch;
      align-items: stretch;    
      grid-template-areas: 
      ". . . . . a a a a a . . . . . "
      ". . . b b f1 f1 f1 f1 f1 c c . . . "
      ". . d g5 g5 f1 f1 f1 f1 f1 f2 f2 e . . "
      ". f g4 g4 g4 f1 f1 f1 f1 f1 f4 f4 f4 g . "
      ". f g4 g4 g4 f1 f1 f1 f1 f1 f4 f4 f4 g . "
      "h g3 g4 g4 g4 f1 f1 f1 f1 f1 f4 f4 f4 f3 i "
      "h g3 g2 q q f1 f1 f1 f1 f1 r r r f3 i "
      "h g3 g2 q q f1 f1 f1 f1 f1 f6 f6 f5 f3 i "
      "h g3 g2 f9 f9 f1 f1 f1 f1 f1 f6 f6 f5 f3 i "
      "h g3 g2 f9 f9 f1 f1 f1 f1 f1 f6 f6 f5 f3 i "
      ". j g2 f9 f9 g1 s s s f7 f6 f6 f5 p ."
      ". j g2 f9 f9 f8 f8 f8 f8 f7 f6 f6 f5 p . "
      ". . k f9 f9 f8 f8 f8 f8 f7 f6 f6 o . . "
      ". . . m m f8 f8 f8 f8 f7 n n . . . "
      ". . . . . l l l l l . . . . . "
      ;  
    }
    .box {
      background-color: black;
    }
    .box-a{
      grid-area: a;
    }
    .box-b{
      grid-area: b;
    }
    .box-c{
      grid-area: c;
    }
    .box-d{
      grid-area: d;
    }
    .box-e{
      grid-area: e;
    }
    .box-f{
      grid-area: f;
    }
    .box-g{
      grid-area: g;
    }
    .box-h{
      grid-area: h;
    }
    .box-i{
      grid-area: i;
    }
    .box-j{
      grid-area: j;
    }
    .box-k{
      grid-area: k;
    }
    .box-l{
      grid-area: l;
    }
    .box-m{
      grid-area: m;
    }
    .box-n{
      grid-area: n;
    }
    .box-o{
      grid-area: o;
    }
    .box-p{
      grid-area: p;
    }
    .box-q{
      grid-area: q;
      display: flex;
      justify-content: center;
      align-items: center;
      background-color: rgb(255, 206, 46);
    }
    .box-q::after{
      content: '';
      display: block;
      height: 70px;
      width: 70px;
      border-radius: 50%;
      background-color: red;
    }
    .box-r{
      grid-area: r;
    }
    .box-s{
      grid-area: s;
    }
    .box-t{
      grid-area: t;
    }
    .f{
      background-color: rgb(251, 209, 71);
    }
    .f1{
      grid-area: f1;
    }
    .f2{
      grid-area: f2;
    }
    .f3{
      grid-area: f3;
    }
    .f4{
      grid-area: f4;
    }
    .f5{
      grid-area: f5;
    }
    .f6{
      grid-area: f6;
    }
    .f7{
      grid-area: f7;
    }
    .f8{
      grid-area: f8;
    }
    .f9{
      grid-area: f9;
    }
    .g1{
      grid-area: g1;
    }
    .g2{
      grid-area: g2;
    }
    .g3{
      grid-area: g3;
    }
    .g4{
      grid-area: g4;
    }

    .g5{
      grid-area: g5;
    }
    .g6{
      grid-area: g6;
    }
  </style>
</head>
<body>
   <!-- 采用3个dom拼接,或者采用拼图板, 拼图更能体现grid布局的又优势 -->
   <div class="wrap">
     <div class="box box-a"> v </div>
     <div class="box box-b"> v </div>
     <div class="box box-c"> v </div>
     <div class="box box-d"> v </div>
     <div class="box box-e"> v </div>
     <div class="box box-f"> v </div>
     <div class="box box-g"> v </div>
     <div class="box box-h"> v </div>
     <div class="box box-i"> v </div>
     <div class="box box-j"> v </div>
     <div class="box box-k"> v </div>
     <div class="box box-l"> v </div>
     <div class="box box-m"> v </div>
     <div class="box box-n"> v </div>
     <div class="box box-o"> v </div>
     <div class="box box-p"> v </div>
     <div class="box box-q">  </div>
     <div class="box box-r"> v </div>
     <div class="box box-s"> v </div>
     <!-- 肤色 -->
     <div class="f f1"></div>
     <div class="f f2"></div>
     <div class="f f3"></div>
     <div class="f f4"></div>
     <div class="f f5"></div>
     <div class="f f6"></div>
     <div class="f f7"></div>
     <div class="f f8"></div>
     <div class="f f9"></div>
     <div class="f g1"></div>
     <div class="f g2"></div>
     <div class="f g3"></div>
     <div class="f g4"></div>
     <div class="f g5"></div>
     <div class="f g6"></div>
   </div>
</body>
也就是开局那张图, 使用grid的来画这张图简直舒服!!

表情.png

end

本次的分享会就到这里, 如果有收获的话点在赞在走喽....
希望和你一起进步.

end.

查看原文

认证与成就

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

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-07-24
个人主页被 195 人浏览