1,事件委托,如何判断哪个子元素;(扩展-js的事件监听机制)
JavaScript的事件机制,例如,事件绑定、事件监听、事件委托(事件代理)等。
来自——http://blog.xieliqun.com/2016/08/12/event-delegate/
事件绑定
要想让 JavaScript 对用户的操作作出响应,首先要对 DOM 元素绑定事件处理函数。所谓事件处理函数,就是处理用户操作的函数,不同的操作对应不同的名称。
在JavaScript中,有三种常用的绑定事件的方法:
- 在DOM元素中直接绑定;
- 在JavaScript代码中绑定;
- 绑定事件监听函数。
在DOM中直接绑定事件:我们可以在DOM元素上绑定onclick、onmouseover、onmouseout、onmousedown、onmouseup、ondblclick、onkeydown、onkeypress、onkeyup等。好多不一一列出了。
在JavaScript代码中绑定事件:在JavaScript代码中(即script
标签内)绑定事件可以使JavaScript代码与HTML标签分离,文档结构清晰,便于管理和开发。
使用事件监听绑定事件:绑定事件的另一种方法是用 addEventListener() 或 attachEvent() 来绑定事件监听函数。下面详细介绍,
事件监听。
关于事件监听,W3C规范中定义了3个事件阶段,依次是捕获阶段、目标阶段、冒泡阶段。
起初Netscape制定了JavaScript的一套事件驱动机制(即事件捕获)。随即IE也推出了自己的一套事件驱动机制(即事件冒泡)。最后W3C规范了两种事件机制,分为捕获阶段、目标阶段、冒泡阶段。
IE8以前IE一直坚持自己的事件机制(前端人员一直头痛的兼容性问题),IE9以后IE也支持了W3C规范。
W3C规范
语法:
element.addEventListener(event, function, useCapture)
event : (必需)事件名,支持所有DOM事件。
function:(必需)指定要事件触发时执行的函数。
useCapture:(可选)指定事件是否在捕获或冒泡阶段执行。true,捕获。false,冒泡。默认false。
注:IE8以下不支持。
<input type="button" value="click me" id="btn1">
<script>
document.getElementById("btn1").addEventListener("click",hello);
function hello(){
alert("hello world!");
}
</script>
IE标准
语法:
element.attachEvent(event, function)
event:(必需)事件类型。需加“on“,例如:onclick。
function:(必需)指定要事件触发时执行的函数。
<input type="button" value="click me" id="btn2">
<script>
document.getElementById("btn2").attachEvent("onclick",hello);
function hello(){
alert("hello world!");
}
</script>
事件监听的优点
1、可以绑定多个事件。
<input type="button" value="click me" id="btn3">
<script>
var btn3 = document.getElementById("btn3");
btn3.onclick = function(){
alert("hello 1"); //不执行
}
btn3.onclick = function(){
alert("hello 2"); //执行
}
</script>
常规的事件绑定只执行最后绑定的事件。
<input type="button" value="click me" id="btn4">
<script>
var btn4 = document.getElementById("btn4");
btn4.addEventListener("click",hello1);
btn4.addEventListener("click",hello2);
function hello1(){
alert("hello 1");
}
function hello2(){
alert("hello 2");
}
</script>
两个事件都执行了。
2、可以解除相应的绑定
<input type="button" value="click me" id="btn5">
<script>
var btn5 = document.getElementById("btn5");
btn5.addEventListener("click",hello1);//执行了
btn5.addEventListener("click",hello2);//不执行
btn5.removeEventListener("click",hello2);
function hello1(){
alert("hello 1");
}
function hello2(){
alert("hello 2");
}
</script>
封装事件监听
<input type="button" value="click me" id="btn5">
//绑定监听事件
function addEventHandler(target,type,fn){
if(target.addEventListener){
target.addEventListener(type,fn);
}else{
target.attachEvent("on"+type,fn);
}
}
//移除监听事件
function removeEventHandler(target,type,fn){
if(target.removeEventListener){
target.removeEventListener(type,fn);
}else{
target.detachEvent("on"+type,fn);
}
}
//测试
var btn5 = document.getElementById("btn5");
addEventHandler(btn5,"click",hello1);//添加事件hello1
addEventHandler(btn5,"click",hello2);//添加事件hello2
removeEventHandler(btn5,"click",hello1);//移除事件hello1
事件委托
事件委托就是利用冒泡的原理,把事件加到父元素或祖先元素上,触发执行效果。
<input type="button" value="click me" id="btn6">
var btn6 = document.getElementById("btn6");
document.onclick = function(event){
event = event || window.event;
var target = event.target || event.srcElement;
if(target == btn6){
alert(btn5.value);
}
}
上面只是个例子,代码尽可能的简化了。在实际的代码中 我们可能用到jQuery的live()、delegate()、bind()、on()等。
event.target确定是哪一个子元素。
事件委托优点
1、提高JavaScript性能。事件委托可以显著的提高事件的处理速度,减少内存的占用
传统写法:
<ul id="list">
<li id="item1" >item1</li>
<li id="item2" >item2</li>
<li id="item3" >item3</li>
</ul>
<script>
var item1 = document.getElementById("item1");
var item2 = document.getElementById("item2");
var item3 = document.getElementById("item3");
item1.onclick = function(){
alert("hello item1");
}
item2.onclick = function(){
alert("hello item2");
}
item3.onclick = function(){
alert("hello item3");
}
</script>
事件委托
<ul id="list">
<li id="item1" >item1</li>
<li id="item2" >item2</li>
<li id="item3" >item3</li>
</ul>
<script>
var item1 = document.getElementById("item1");
var item2 = document.getElementById("item2");
var item3 = document.getElementById("item3");
document.addEventListener("click",function(event){
var target = event.target;
if(target == item1){
alert("hello item1");
}else if(target == item2){
alert("hello item2");
}else if(target == item3){
alert("hello item3");
}
})
</script>
2、动态的添加DOM元素,不需要因为元素的改动而修改事件绑定。
传统写法
<ul id="list">
<li id="item1" >item1</li>
<li id="item2" >item2</li>
<li id="item3" >item3</li>
</ul>
<script>
var list = document.getElementById("list");
var item = list.getElementsByTagName("li");
for(var i=0;i<item.length;i++){
(function(i){
item[i].onclick = function(){
alert(item[i].innerHTML);
}
})(i)
}
var node=document.createElement("li");
var textnode=document.createTextNode("item4");
node.appendChild(textnode);
list.appendChild(node);
</script>
点击item1到item3都有事件响应,但是点击item4时,没有事件响应。说明传统的事件绑定无法对动态添加的元素而动态的添加事件。
事件委托
<ul id="list">
<li id="item1" >item1</li>
<li id="item2" >item2</li>
<li id="item3" >item3</li>
</ul>
<script>
var list = document.getElementById("list");
document.addEventListener("click",function(event){
var target = event.target;
if(target.nodeName == "LI"){
alert(target.innerHTML);
}
})
var node=document.createElement("li");
var textnode=document.createTextNode("item4");
node.appendChild(textnode);
list.appendChild(node);
</script>
当点击item4时,item4有事件响应。说明事件委托可以为新添加的DOM元素动态的添加事件。
target.nodeName = DOM元素标签
2,promise对象解决了什么问题,如果两个端口对其发送消息,如何等两个都完成后再执行。
//一个图片加载的异步操作
function loadImageAsync(url){
return new Promise(function (resolve,reject) {
var image = new Image();
image.onload = function () {
resolve(image);
}
image.onerror = function () {
reject(new Error("could not load image at" + url));
}
})
}
//以下是对XMLHttpRequest对象的封装,用于发出一个针对JSON的数据请求,并返回一个Promise对象。
var getJSON = function (url) {
var promise = new Promise(function (resolve,reject) {
var client = new XMLHttpRequest();
client.open("GET",url);
client.onreadystatechange = handler();
client.responseType = "json";
client.setRequestHeader("Accept","application/json");
client.send();
function handler() {
if (this.readyState !== 4){
return;
}
if (this.status === 200){
resolve(this.response);
}else{
reject(new Error(this.statusText));
}
};
});
return promise;
}
getJSON("/posts.json").then(function (json) {
console.log("Contents: " + json);
},function (error) {
console.error("Chu cuole!")
})
Promise实例具有then方法,定义在原型对象Promise.prototype上,作用是为promise实例添加改变状态的回调函数, then方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数。
promise.prototype.catch方法是then(null,rejection)的别名,用于指定发生错误时的回调函数。用于捕获错误。
Promise.all方法用于将多个Promise实例包装成一个新的Promise实例。
接受一个数组作为参数,其中元素为Promise实例。
var p = Promise.all([p1,p2,p3]);
p的状态由p1,p2,p3决定,·1.只有p1,p2,p3的状态都变成Fulfilled,p的状态才会变成Fulfilled。此时三个实例的返回值组成一个数组,传给p的回调函数。2,只要p1,p2,p3有一个的状态变成Rejected,则p的实例状态变为rejected,此时第一个实例的返回值会传递给p的回调函数。
Promise.race()方法同样是将多个Promise实例包装成一个新的实例,和all一样,不同的是只要其中一个实例状态改变,P的状态就会改变,率先改变的promise实例返回值就会传给p的回调参数。
Promise.resolve()将对象转为Promise对象
如果参数是thenable对象,即具有then方法的,则会直接转为Promise对象,并执行then方法。
如果不具有then方法或者不是对象,则直接返回一个新的Promise对象,状态为Resolved..
Promise.reject()返回一个Promise实例,状态为Rejected。
还有两个附加方法,done,finally确保只要有一个方法有误都会抛出错误。
3,webpack原理,有啥好处
初始化 | 启动构建,读取合并参数,加载Plugin,实例化complier |
编译 | 从Entry发出,针对每个module串行调用对应loader,递归编译 |
输出 | 对编译后的module组成chunk,再转化为文件,输出到文件系统 |
特性:1,一切皆模块,2,高效的构建性能;
基础配置
/// webpack.config.js
const path = require('path');
module.exports = {
// js 执行入口文件
entry: './main.js',
output: {
// 将所有依赖的模块合并输出到一个 bundle.js 文件
filename: 'bundle.js',
// 将输出文件都放到 dist 目录下
path: path.resolve(__dirname, './dist'),
}
};
复制代码
执行 webpack --config webpack.config.js
,则会在dist
文件夹生成bundle.js
文件,这就是最基本的 webpack 配置。
Loader
Loader 主要是用于将模块代码转换为可在浏览器运行的代码。可以理解为翻译机。如将 Less 转换为 CSS,Typescript 转换为 Javascript 等。
Plugin
Plugin 主要是扩展 webpack 的功能,增强 webpack 的灵活性。如extract-text-webpack-plugin
,可以将包中的文本提取到单独的文件中,从bundle.js
提取 css 到单独的文件出来等。
DevServer
webpack-dev-server
,可以帮我们解决上面没提到但是在开发中遇到的痛点。
- 提供 HTTP 服务而不是使用本地文件预览;
- 监听文件的变化并自动刷新网页,做到实时预览:
- 支持 Source Map,以方便调试。
有一些功能:
1,babel,将es6-es5
2,typescript,将typescript转化为JavaScript
3,sass-loader,less-loader,用于CSS预处理器
4,监视更新,webpack -- watch或者webpack.config.js设置watch:true
5,自动刷新浏览器:使用webpack-dev-server启动webpack
5,模块热替换:webpack-dev-server-hot
6,检查代码:eslint-loader 检查js
7,加载SVG:raw-loader,新增svg-inline-loader,添加压缩SVG功能
8,压缩CSS,css-loader的minimize
4,es6新特性,let解决了什么问题,var是全局变量吗
在没有let前,var声明的作用域是一整个函数,而不是一块。
let属于块级作用域,只在它的代码块内,最近的一个{}内作用,避免的变量的泛滥;
var不是全局变量,JS中全局变量声明是不加var;
- let 变量是 block-scope. 用let声明的变量的作用域就是封闭块,而不是整个封闭函数。
5,js原型链
6,Web组件化
由以下四个部门组成,HTML templates,HTML Imports,Custom elements,Shadow DOM。
HTML templates
支持template标签和slot标签。slot标签支持动态替换模板中的HTML内容,它用name属性来作为唯一表示。template中的内容被插入到DOM之前,不会渲染,它可以放在document中的任何位置。
<div id="containers">
<template id="one">
<h1>this is template in index.html</h1>
</template>
</div>
<script>
const template = document.querySelector("#one");
const templatecontent = template.content;
const templateInstance = templatecontent.cloneNode(true);
document.querySelector("#containers").appendChild(templateInstance);
HTML Imports
改造一下上面的例子,将template的内容写到一个新的main.html文件中,然后通过link引入。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="import" href="./index.html">
</head>
<body>
<div id="containers"></div>
<script>
const template = document.querySelector('link[rel="import"]').import.querySelector("#one");
const temContent = template.content;
const temInstance = temContent.cloneNode(true);
document.querySelector("#containers").appendChild(temInstance);
</script>
</body>
</html>
Shadow DOM
Shadow DOM提供了一种健壮的封装方式来做到页面节点的隔离,避免全局样式冲突,这也是Web Component的核心优势。
<div id="containers"></div>
<p>This is 1 paragraph.</p>
const shadow = document.querySelector("#containers").attachShadow({mode: "open"});
shadow.innerHTML = '<p> This is 2 paragraph.';
shadow.innerHTML += "<style> p {color: red;}</style>"
Shadow DOM中设置的样式没有影响外部<p>标签的样式。
Custom elements
在custom element的构造函数中,可以指定多个不同的回调函数,它们将会在元素的不同生命时期被调用:
connectedCallback:当 custom element首次被插入文档DOM时,被调用。
disconnectedCallback:当 custom element从文档DOM中删除时,被调用。
adoptedCallback:当 custom element被移动到新的文档时,被调用。
attributeChangedCallback: 当 custom element增加、删除、修改自身属性时,被调用。
Web Components 为前端组件化提供了解决方案,但用惯了Vue这样的框架,还是会发现Web Components 的问题, 比如
- 浏览器的支持
- 对样式局部作用域的处理,往往js中包着一堆的CSS样式,略显臃肿
- 父子、兄弟组件的通信问题
- 属性都是字符串,需要额外的代码做转换和兼容
- 没有数据驱动,基本全是DOM操作
这些问题貌似都没有给出具体的解决方案,但是目前的好多优秀的polyfill已经做到很好了。
7,Vue组件通信
父组件向子组件通信
方法一:props
使用props,父组件可以使用props向子组件传递数据。
父组件vue模板father.vue
<template>
<child :msg="message"></child>
</template>
<script>
import child from './child.vue';
export default {
components: {
child
},
data () {
return {
message: 'father message';
}
}
}
</script>复制代码
子组件vue模板child.vue
<template>
<div>{{msg}}</div>
</template>
<script>
export default {
props: {
msg: {
type: String,
required: true
}
}
}
</script>复制代码
使用$children
使用$children可以在父组件中访问子组件。
子组件向父组件通信
使用vue事件
父组件向子组件传递事件方法,子组件通过$emit触发事件,回调给父组件。
父组件vue模板father.vue
<template>
<child @msgFunc="func"></child>
</template>
<script>
import child from './child.vue';
export default {
components: {
child
},
methods: {
func (msg) {
console.log(msg);
}
}
}
</script>复制代码
子组件vue模板child.vue
<template>
<button @click="handleClick">点我</button>
</template>
<script>
export default {
props: {
msg: {
type: String,
required: true
}
},
methods () {
handleClick () {
//........
this.$emit('msgFunc');
}
}
}
</script>
非父子组件、兄弟组件之间的数据传递
非父子组件通信,Vue官方推荐使用一个Vue实例作为事件的中节点。
Vue内部有一个事件机制。$on方法用来监听一个事件。$emit用来触发一个事件。
/*新建一个Vue实例作为中央事件总嫌*/
let event = new Vue();
/*监听事件*/
event.$on('eventName', (val) => {
//......do something
});
/*触发事件*/
event.$emit('eventName', 'this is a message.');复制代码
多层级父子组件通信:
之前在学习饿了么的开源组件库element的时候发现他们重新实现了broadcast以及dispatch的方法,以mixin的方式引入,具体可以参考《说说element组件库broadcast与dispatch》。但是跟Vue1.0的两个方法实现有略微的不同。这两个方法实现了向子孙组件事件广播以及向多层级父组件事件派发的功能。但是并非广义上的事件广播,它需要指定一个commentName进行向指定组件名组件定向广播(派发)事件。
其实这两个方法内部实现还是用到的还是$parent以及$children,用以遍历子节点或是逐级向上查询父节点,访问到指定组件名的时候,调用$emit触发指定事件。
复杂的单页应用数据管理
当应用足够复杂情况下,请使用vuex进行数据管理。
作者:染陌同学
链接:https://juejin.im/post/59ec95006fb9a0451c398b1a
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
8,实现一个瀑布流
来自https://segmentfault.com/a/1190000012621936
瀑布流,又称瀑布流式布局。是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。
1、首先瀑布流所有的图片应该保持宽度一致,高度是由内容决定。
通过定位的方式是我们实现瀑布流的最基本的原理,只要我们动态的设置它的top
值、left
值,就能让它排列。
2、定位后确定浏览器显示区域内,一行能放多少列图片盒子。
- 获取页面的宽度
- 获取图片盒子的宽度
- 显示的列数 = 页面宽度/图片盒子宽度
column = pageWidth / itemWidth
3、为了美观我们可以加上一个空隙
- 显示的列数 = 页面宽度/(图片盒子宽度+间隙);
column = pageWidth / (itemWidth + gap);
4、 确定列数之后,排列第一行
- 下面还有很多图片盒子,我们先要排列第
1
行,所以在for
循环里就要判断一下,当i
(所有图片盒子的索引) <column
(显示列数)的时候,说明在第1
行; - 知道在第
1
行之后,动态设置每个图片盒子的left
值就能排好第1
行。 left = i * ( itemWidth + gap );
第1行排列好之后,获取第1行所有图片盒子的高度
- 需要定义一个数组
arr
,将获取到的高度存在数组中,因为第2
行排列的时候需要考虑top
值,此时只能根据第1
行图片盒子的高度来设置; - 获取图片高度的时候要注意,程序必须写在入口函数
onload
里面,因为图片的加载特性是:等页面都加载完之后才去请求加载,所以不写在入口函数里可能会出现高度获取不到的情况。
6、排列第2行
- 获取到刚刚数组中,高度最小的那一列,将第
2
行的第1
个图片盒子放置在它的下方; - 此时的
left
值就是高度最小列的offsetLeft
;top
值就是:第1
行高度最小列的高度(为了布局美观可以加上上下间隙gap
)。 - 记录下高度最小列的索引
index
,后面计算会用到; - 设置完成之后,会发现后面所有的图片都叠在这个高度最小列的下面,原因就是此时的最小列高度没有改变,应该加上下面图片的高度,得出一个新高度。
7、改变最小列当前高度
- 此时的这一列高度其实已经发生改变了,所以需要将新高度赋值给数组
- 当前高度最小列的高度 = 当前高度最小列的高度 + 间隙 + 下面图片盒子的高度
8、触发resize事件
- 将整个设置样式的部分封装成一个函数,在
onload
里面注册一个resize
事件,只要页面一发生改变,就触发样式部分的代码。 - 实时改变
pageWidth
的宽度,这样瀑布流就会是一个响应式的效果了
9、懒加载效果
- 目前我们用的是
30
张图片,假如一个页面中有几百张图片的时候,我们不可能等到它都加载完再显示,所有这里引入一个懒加载的概念,我们规定第30
张为显示的最后一张图片,当滚动条滚动到30
张的时候,应该加载下一批图片。 - 即页面可视区高度+滚动条卷去的高度 = 第
30
图片的offsetTop
;的时候加载下面的图片。
9,如何判断数组与对象,
Array.isArray、instanceof,prototype.constructor
10,js基础数据类型
值类型:5,数值,字符串,布尔值,null,undefined
引用类型:3,对象,数组,函数
11,垂直居中,多种实现方式
行内元素,line-height
浮动元素:float,left50%,top50%,margin负边距偏移等
12,flex如何垂直居中,水平左右贴盒
参考自:https://juejin.im/post/5ac2329b6fb9a028bf057caf
垂直居中:设定flex的容器属性水平居中 justify-content:center,垂直居中:align-content:center;
水平左右贴合:justify-content:space-between