如何写好原生JavaScript
基础注意点
- JavaScript负责行为,改变状态,不是用来改变样式的。js:动态脚本语言。
- CSS负责样式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>各司其职</title>
<style>
body{
background: white;
font-size: 14px;
}
#user-list, #user-list1 {
width: 200px;
line-height: 1.5em;
}
#user-list > li:hover{
background-color: rgba(0, 0, 0, 0.7);
}
#user-list > li.active{
background-color: red;
color: white;
}
#user-list1 input{
display: none;
}
#user-list1 label{
display: block;
}
#user-list1 > li:hover{
background-color: rgba(0, 0, 0, 0.3);
}
#user-list1 input:checked+label{
background-color: black;
color: white;
}
</style>
</head>
<body>
<ul id="user-list">
<li>赵</li>
<li>钱</li>
<li>孙</li>
<li>李</li>
</ul>
<ul id="user-list1">
<li>
<input type="radio" name="items" id="item0" value="zhou">
<label for="item0">zhou</label>
</li>
<li>
<input type="radio" name="items" id="item1" value="wu">
<label for="item1">wu</label>
</li>
<li>
<input type="radio" name="items" id="item2" value="zheng">
<label for="item2">zheng</label>
</li>
<li>
<input type="radio" name="items" id="item3" value="wang">
<label for="item3">wang</label>
</li>
</ul>
<script>
// 版本二
let list = document.querySelector('#user-list');
let items = list.querySelectorAll('li');
list.addEventListener('click', function (e) {
items.forEach(function (item) {
item.className = '';
});
if(e.target.tagName === 'LI'){
let item = e.target;
item.className = 'active';
console.log(item.innerHTML);
}
});
// 版本三
let list1 = document.querySelector('#user-list1');
list1.addEventListener('click', function (e) {
if(e.target.tagName === 'INPUT'){
let checkedItem = list1.querySelector('input:checked');
console.log(checkedItem.value);
}
});
</script>
</body>
</html>
思考1:
- 写JavaScript操作DOM要注意什么?
- JavaScript与HTML、CSS的职责如何分离?
- 把复杂性放在哪一头,为什么?
思考2: API的设计?
<script>
// 版本一
// 问题:过程耦合 + Callback Hell
const traffic1 = document.getElementById('traffic1');
(function reset() {
traffic1.className = 'wait';
setTimeout(function () {
traffic1.className = 'stop';
setTimeout(function () {
traffic1.className = 'pass';
setTimeout(reset, 2000);
}, 2000)
}, 2000);
})();
// 版本二
// 优点:着手于状态的改变
// 问题:依赖于外部变量 stateList,currentStateIndex,这两个变量应该封装起来,暴露出来。封装性不好
const traffic2 = document.getElementById('traffic2');
var stateList = ['wait', 'stop', 'pass'];
var currentStateIndex = 0;
setInterval(function () {
traffic2.className = stateList[currentStateIndex];
currentStateIndex = (currentStateIndex + 1) % stateList.length;
}, 2000);
// 版本三
// 问题:可复用性差
const traffic3 = document.getElementById('traffic3');
function start(traffic, stateList) {
let currentStateIndex = 0;
setInterval(function () {
traffic3.className = stateList[currentStateIndex];
currentStateIndex = (currentStateIndex+1) % stateList.length;
}, 2000);
}
start(traffic3, ['wait','stop','pass']);
// 版本四
// 过程抽象,函数式编程,抽象出 poll 方法
const traffic4 = document.getElementById('traffic4');
function poll(...fnList) {
let stateIndex = 0;
return function (...args) {
let fn = fnList[stateIndex++ % fnList.length];
return fn.apply(this, args);
}
}
function setState4(state) {
traffic4.className = state;
}
let trafficStatePoll = poll(setState.bind(null, 'wait'),
setState4.bind(null, 'stop'),
setState4.bind(null, 'pass'));
setInterval(trafficStatePoll, 2000);
// 版本五
// 优点:对等待时间抽象
const traffic5 = document.getElementById('traffic5');
function wait(time) {
return new Promise(resolve => setTimeout(resolve, time));
}
function setState5(state) {
traffic5.className = state;
}
function reset() {
Promise.resolve()
.then(setState5.bind(null, 'wait'))
.then(wait.bind(null, 1000))
.then(setState5.bind(null, 'stop'))
.then(wait.bind(null, 2000))
.then(setState5.bind(null, 'pass'))
.then(wait.bind(null, 3000))
.then(reset);
}
reset();
// 版本六
// 形成一个协议
// 面向对象、函数式、Promise、灵活可扩展
const traffic6 = document.getElementById('traffic6');
function TrafficProtocol(el, reset) {
this.subject = el;
this.autoReset = reset;
this.stateList = [];
}
TrafficProtocol.prototype.putState = function (fn) {
this.stateList.push(fn);
};
TrafficProtocol.prototype.reset = function () {
let subject = this.subject;
this.statePromist = Promise.resolve();
this.stateList.forEach((stateFn) => {
this.statePromist = this.statePromist.then(()=>{
return new Promise(resolve => {
stateFn(subject, resolve);
});
});
});
if(this.autoReset){
this.statePromist.then(this.reset.bind(this));
}
};
TrafficProtocol.prototype.start = function () {
this.reset();
};
var trafficE = new TrafficProtocol(traffic6, true);
trafficE.putState(function (subject, next) {
subject.className = 'wait';
setTimeout(next, 1000);
});
trafficE.putState(function (subject, next) {
subject.className = 'stop';
setTimeout(next, 2000);
});
trafficE.putState(function (subject, next) {
subject.className = 'pass';
setTimeout(next, 3000);
});
trafficE.start();
</script>
- 思考3: JavaScript的“效率”?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>面试题:在一个数组中找和为10的数组对</title>
</head>
<body>
<script>
// 版本一:两层循环 O(n^2)
let list1 = [11,4,9,3,-1,-3,6,7,9,13];
function map1(list){
let ret = [], len = list.length;
for(let i=0; i<len; i++){
for(let j=i+1; j<len; j++){
if(list[i]+list[j]===10){
ret.push([list[i],list[j]])
}
}
}
return ret;
}
console.log(JSON.stringify(map1(list1)));
// 版本二:先排序
function map2() {
let ret = [];
list = list.sort((a,b)=>a-b);
for(let i=0, j=list.length-1; i<j;){
let a = list[i], b = list[j];
if(a[i]+b[j]===10){
ret.push([a,b]);
i++;
j--;
}else if(a+b<10){
i++;
}else{
j--;
}
}
return ret;
}
console.log(JSON.stringify(map(list)));
</script>
</body>
</html>
- 小结
- 不应该用JS直接操作DOM
- API的设计,通用型,抽象度,可扩展性
- 程序要用合适的算法
JavaScript概览
JavaScript 简史
JavaScript 的特点
- 动态 + 弱类型
- 解释型或实时编译(JIT)型语言
- 面向对象
- 函数式特征
- 灵活和可扩展性
- 反射与元编程
- 高性能
- 单线程异步非阻塞
JavaScript 主要能做什么
- 通过DOM:改变网页文档元素和属性
- 通过BOM:操作浏览器API
- 事件机制:响应用户行为
- XHR、Fetch、WS:发送和接受数据
- Storage:保存数据和状态
- Timer、Promise:执行异步任务
- ArrayBuffer、TypedArray:处理数据
- File API:操作文件
JavaScript 系语言
谁在使用JavaScript
- 浏览器
- 服务端,如 node.js
- 硬件开发,如 树莓派
- 操作数据库,如mongoDB、CouchDB
- 写原生应用,如ReactNative
- 开发游戏,如COCOS
本博客主要讲什么
- 符合ECMA-262最新规范的JavaScript,不回避新特征
- 基本类型、原生类型、浏览器API、Node API(服务端)
- 大部分语言特性(特别是ESS及之前的版本在chrome40以上已完全支持)
- 重点讲JavaScript的独有特性及其应用场景,以实际工作常见和常用的为主。
- 重点讲函数、面向对象、过程抽象和函数式,总之能发挥JS动态特性的常见模式
- 会涉及到语言基础、运行环境(如浏览器环境、Node)
- 会涉及到部分后段、HTTP请求相关内容,这些内容对高端工程师高质量完成工作也是很有必要的
本博文不讲的内容
- 过时的不符合规范的特性,比如ES3和之前版本里被废弃和修正的东西
- 基础通用的编程语言特性,比如常见的基本if、for、while、switch语句
- 一些稍微不那么复杂、可以自学、工作中也会用到的内置类型,比如 Date
- 具体框架的使用,可以看文档
- 建议阅读 MDN文档
兼容性怎么办?
- 速查:速查JS兼容
- 方案1:shim & polyfill
- 方案2:library 实际项目里对于兼容IE8一下浏览器建议使用 jQuery
- 方案3:Bable/JSX/TypeScript
代码风格
- 代码的格式(包括缩进、大括号、分号、换行、空行、空格)
- 广义的:
- 目录结构和文件名
- 变量命名
- 文档规范
- 一起遵守规则:
- 2 spaces:两个空格
- Single quotes for strings:字符串用单引号
- No unused variables:不要有不使用的变量
- Semicolons:写分号
- Never start a line with
( [ '
- No space after keywords: if 和 括号 之间没有空格
- No space after function name:函数名和括号之间没有空格
- Always use
===
instead of==
:用三个等号判断
变量、值与类型
关于类型
关于JS类型的几点说明
- JS是 动态类型 + 弱类型 的语言
- JS的变量、属性在运行期间决定类型
- JS存在隐式类型转换
- JS有一系列识别类型的反射方法
ECMA的语言类型
typeof
- A function is a callable object
变量声明
根据规范,JS有三种变量声明的方法:
var
:声明一个变量,可选择将其初始化为一个值let
:声明一个块级作用域变量,可选择将其初始化为一个值。(ES6)const
:声明一个只读的常量.(ES5)
使用
var
的注意事项:var
不支持块级作用域var
存在变量提升(Variable hoisting)。变量提升作用域。如:var a=10;
相当于var a; a=10;
使用
let
的注意事项:- 块级作用域。出了块就是
undefined
。 - 同一作用域中不允许重复声明。
- 暂存死区(Temporal dead zone,TDZ)。在一个变量
a
声明之前使用该变量会报错ReferenceError:a is not defined
,不会有变量提升,且typeof(a)
为undefined
。 - 循环中的
let
作用域。
let
有词法作用域。- 实例:
- 块级作用域。出了块就是
// 版本一
// 会因变量提升而出问题
var buttons = document.getElementsByTagName('button');
for(var i=0; i<buttons.length; i++){
buttons[i].onclick =
evt => console.log('你点击了第' + i + '个按钮!')
}
// 版本二:利用let的块级作用域
for(let i=0; i<buttons.length; i++){
buttons[i].onclick =
evt => console.log('你点击了第' + i + '个按钮!')
}
// 版本三:利用闭包的特性解决
var buttons = document.querySelectorAll('button');
for(var i=0; i<buttons.length; i++){
(function (i) {
buttons[i].onclick =
evt => console.log('你点击了第' + i + '个按钮!');
})(i);
}
- 浏览器兼容性 。
- 使用
const
的注意事项
const
声明的标识符所绑定的值不可以再次改变引用。但是还是可以修改对象里的内容,若想不允许修改可以使用Object.freeze({...})
。- ES6
const
的其他行为和let
一样
如果不声明会怎样?
- 严格模式:
use strict
。会报错ReferenceError: a is not defined
- 非严格模式:不会报错
抽象相等
- 严格相等与非严格相等
null == undefined
null !== undefined
- 非严格比较:会进行类型转换
number
里有个特殊的取值NaN
,NaN
与任何值(包括自身)都不想等
Boolean
类型
- 只有两个取值:
true
和false
。 0
、' '
、null
、undefined
被隐式地转换为false
,其他转为true
。- 建议采用严格比较,可以通过
!!
讲非boolean
值转为boolean
值。 - 布尔操作符
&&
和||
并不会转换类型(尤其注意!!) - 如:
&&
和||
的短路特性- 比较操作符总是返回
boolean
类型 Boolean
类型的运用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>小动画</title>
<style>
div{
width: 50px;
height: 50px;
background: red;
border: 1px solid red;
border-radius: 50%;
}
</style>
</head>
<body>
<div class="ball"></div>
<script>
function applyAnimate(el, duration, options, easing) {
var startTime = Date.now();
if(typeof el === 'string'){
el = document.querySelector(el);
}
duration = duration || 1000;
options = options || {
property: 'x',
distance: 100
};
easing = easing || function(p){return p;};
requestAnimationFrame(function update() {
var now = Date.now();
var p = (now - startTime)/duration;
var ep = easing(p);
if(typeof options !== 'function'){
var attr = options.property, distance = options.distance;
var translate = [];
if(attr.indexOf('x') >= 0){
translate.push('translateX(' + distance * ep + 'px)');
}
if(attr.indexOf('y') >= 0){
translate.push('translateY(' + distance * ep + 'px)');
}
el.style.transform = translate.join(' ');
}else{
options(el, ep, p);
}
if(p <= 1){
requestAnimationFrame(update);
}
});
}
document.querySelector('.ball').onclick = function () {
applyAnimate('.ball');
}
</script>
</body>
</html>
Number
类型
- 数值范围:
- 整数:-2^53 ~ 2^53
- 小数精度
Number.EPSILON
、Infinity
无穷大、Number.MAX_VALUE
、Number.MIN_VALUE
- 浮点数精度问题
- 二进制、八机制、十进制、十六进制
- +0 和 -0
String
- 引号规范。在JS中的字符串建议用单引号。
- 转义字符
- 字符串与字符
- 字符串可以拆成字符数组
var charArray = Array.from(str);
或var charArray = str.split('');
str.charCodeAt(4)
String.fromCharCode(116, 103)
- 字符串与类型转换
- -
- 常用字符串操作
- 字符串查找和替换:
str.indexOf(subStr)
- 字符串大小写:
str.toUpperCase();
,str.toLowerCase();
- 字符串连接:
str1+''+str2
- 截取子串:
str.slice(起始位置,结束位置);
str.substr(起始位置,长度);
- 逆序字符串:
str.split('').reverse.ioin('');
- 逆序字符串:
- 字符串匹配:
- 字符串查找和替换:
- 模版字符串
Object
- 对象的属性
- 属性名规则:可以是有效字符串或者任意可转换为有效字符串的类型
- 属性的访问和遍历
- 值和引用
- 值类型与引用类型
- 基本类型对应的包装类型(不带
new
的是值类型,带new
的是包装类型) - 对象的拷贝
- 类型与构造器
new
和constructor
prototype
原型链(一层层往上找某 属性)instanceOf
ES6 class
- 内置类型
Object
:根Function
:JS中最核心的Promise
:处理异步Array
:数组Date
:日期Regex
:正则表达式Error
Math
:数学ArrayBuffer
DataView
Map
:数据类型Set
:数据类型,用来去重TypedArray
:数据类型Proxy
- 对象的高级属性
- 为什么不建议修改
Object.protytype
和Array.prototype
呢?在类型(或对象)上添加方法的代价,会被所有的实例所继承下来。解决方法:Object.defineProperty
,不会被for in
检索出来,若想让其检索出来需要加enumerable:true;
。 getter
和setter
- 属性描述符
- 数据绑定视图
- 为什么不建议修改
class
(未)Symbol
(未)
- ES6新特性
- 生成唯一表示
- 用作对象的key
Symbol.for
Symbol
的基本用法- 系统设置
Symbol
函数
函数声明、函数表达式
- 注意函数声明提升
- 实例:函数声明提升,变量声明提升,但是此时并未赋值
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>函数声明提升与赋值</title>
</head>
<body>
<script>
console.log([typeof add, typeof sub]);
function add(x, y) {
return x+y;
}
var sub = function (x, y) {
return x-y;
};
console.log([add(2,5), sub(2,6)]);
</script>
</body>
</html>
- 箭头函数表达式
- 实例:ES6新增:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>箭头函数表达式</title>
</head>
<body>
<script>
let double = x => x*2;
let add = (x, y) => {
return x + y;
};
console.log([double(12), add(3,7)]);
</script>
</body>
</html>
匿名函数
- 在函数表达式和回调函数中常见
- 匿名函数与
arguments.callee
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>匿名函数实现倒计时效果</title>
</head>
<body>
<script>
function countDown(count, interval, callback) {
if(count<=0) { return; }
callback(count);
setTimeout(function () {
if(--count > 0){
setTimeout(arguments.callee, interval);
}
callback(count);
}, interval);
}
countDown(6, 1000, t => console.log('我是'+t));
console.log('出发!');
</script>
</body>
</html>
函数参数
- 具名的参数,
function.length
是一个类数组的函数,但不是数组。 - 可变参数与
arguments
- ES6
rest
参数,是一个真正的数组。 - 参数默认值
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>可变参数与arguments</title>
</head>
<body>
<div style="width: 50px; height: 50px;">这里!!</div>
<p style="width: 50px; height: 50px;">这里!!</p>
<script>
function add() {
// var args = [].slice.call(arguments); 一种写法
// var args = Array.prototype.slice.call(arguments); 一种写法
// trueA = Array.from(FakeA) ==> 这个函数把伪数组FakeA 转换成数组 TrueA。
let args = Array.from(arguments); // 一种写法
return args.reduce((a,b) => a+b);
}
console.log(add(11,2,3,4));
// javascript 函数设计中经常会让参数允许有不同的类型
// el: element 给element的某个属性property赋值value
function setStyle(el, property, value) {
if(typeof el === 'string'){
el = document.querySelector(el);
}
if(typeof property === 'object'){
for(var key in property){
setStyle(el, key, property[key]);
}
}else{
el.style[property] = value;
}
}
console.log(setStyle.length);
setStyle('div', 'background', 'red');
setStyle('p', {
'fontSize':'16px',
'backgroundColor':'blue'
});
// ES6:rest参数
// ...args 这其实就是一个数组
let add6 = (...args) => args.reduce((a,b) => a+b);
console.log(add(1,2,3,4));
console.log(add.length);
function deepCopy(des, src) {
if(!src || typeof src !== 'object'){
return des;
}
for(var key in src){
let obj = src[key];
if(obj && typeof obj === 'object'){
des[key] = des[key] || {};
deepCopy(des[key], obj);
}else{
des[key] = src[key];
}
}
return des;
}
function merge(des, ...objs) {
return [des, ...objs].reduce((a,b) => deepCopy(a,b))
}
console.log('ES6 rest参数:' + JSON.stringify(merge({x:1}, {y:2}, {z:3})));
</script>
</body>
</html>
作用域、闭包、this
- 重要:ES5用函数来构建作用域
- ES没有块级作用域
- IIFE:立即执行函数
什么是闭包
- 实例一
- 实例二
实例一:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>闭包作用域</title>
</head>
<body>
<script>
// 因为变量提升而导致所有时候:i=5
for(var i=0; i<5; i++){
setTimeout(function () {
console.log(i);
}, 100);
}
// 方法一: 闭包:立即执行函数
for(var i=0; i<5; i++){
(function (i) {
setTimeout(function () {
console.log(i);
}, 500);
})(i);
}
// 方法二: ES5 bind方法
for(var i=0; i<5; i++){
setTimeout((function (i) {
console.log(i);
}).bind(null, i), 1000);
}
// 方法三: ES6:块级作用域
for(let i=0; i<5; i++){
setTimeout(function () {
console.log(i);
}, 2000);
}
</script>
</body>
</html>
- 实例二:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>闭包与私有数据</title>
</head>
<body>
<script>
// 版本一
var myClass = (function () {
var privateData = 'privateData';
function Class() {
this.publicData = 'publicData';
}
Class.prototype.getData = function () {
return privateData;
};
return Class;
})();
var myObj = new myClass();
console.log([myObj.publicData, myObj.privateData, myObj.getData()]);
// 版本二:ES6 块级作用域
let Class2;
{
let privateD = 'privateD';
Class2 = function () {
this.publicD = 'publicD';
};
Class2.prototype.getD = function () {
return privateD;
};
}
var myO = new Class2();
console.log([myO.publicD, myO.privateD, myO.getD()]);
</script>
</body>
</html>
apply
、call
、bind
- JavaScript的
this
- JS的
this
是由函数求值时的调用者决定的
- JS的
apply
,call
,bind
- 通过
apply
,call
指定this
调用函数 - ES5的
bind
bind
的重要意义和高级用法
- 通过
- 实例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>call, apply, bind</title>
<style>
body{
transition: all 0.5s;
}
body.state1{
background-color: red;
}
body.state2{
background-color: lightskyblue;
}
</style>
</head>
<body>
<script>
function add(x, y) {
return x+y;
}
// call
console.log(add.call(null, 1, 2));
// apply
console.log(add.apply(null, [3,4]));
// bind 若将全部参数都传入,则延迟调用,会返回一个function
console.log(add.bind(null, 5, 6));
// bind 部分调用
let add1 = add.bind(null, 7);
console.log(add1(8));
function setBodyState(state) {
document.body.className = state;
}
setBodyState.call(null, 'state1');
setTimeout(setBodyState.bind(null, 'state2'), 1500);
</script>
</body>
</html>
关于异步回调
- JS 的异步
- Timer
- 实例一
- 事件
- 获取数据API
Promise
- 其他异步模型
- 异步串行模型
through2--gulp
使用 - 异步串行模型
connect--express、koa
使用 - ES6
generators
- ES2015
async/wait
- 。。。
- 异步串行模型
- Timer
实例一:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用timer异步执行</title>
<style>
#ball{
display: inline-block;
background-color: gray;
width: 50px;
height: 50px;
border-radius: 50%;
text-align: center;
line-height: 50px;
color: white;
}
#ball:hover{
cursor: pointer;
}
#ball.warn{
animation: 1s 7s blink linear 3 forwards normal;
}
@keyframes blink {
0% {
background: #FFFFFF;
}
50% {
background: #f00;
}
100% {
background: #FFFFFF;
}
}
</style>
</head>
<body>
<div id="ball">1</div>
<script>
const ball = document.getElementById('ball');
ball.addEventListener('click', function () {
var startTime = Date.now();
var tId = setInterval(function () {
var t = 10-Math.round((Date.now()-startTime)/1000);
ball.innerHTML = Math.max(t, 0);
if(t<=0){
clearInterval(tId);
}
}, 1000);
ball.className = 'warn';
});
</script>
</body>
</html>
函数的动态构建
Function
构造器与函数模版- 过程抽象和高阶函数
- 过程抽象与函数式编程
- 过程抽象的定义
- 过程抽象与数据抽象的区别
- 过程抽象的定义
函数式编程
- 什么是函数式编程
函数式编程与前端代码有什么关系
- 函数的纯度和“提纯”
Uncurring
、Currying
&Partial Application
纯函数
- 定义:一个函数若输入参数确定则输出结果是唯一确定的,那么它就是纯函数。
- 好处:无状态、无副作用、幂等、无关时序。
- 过程抽象可以提升函数纯度
- 等价函数
- 可以实现拦截和监控
- 举例说明
- 过程抽象与函数式编程
- 函数异步化与串行执行
reduce
同步 &pipe
异步throttle
避免重复点击;debounce
避免重复点击multicast
批量操作DOM
元素阅读:
- 书:《计算机程序的构造和解释》
- JavaScript与函数式编程
- 函数式编程术语解析
- 什么是纯函数
- 函数式编程离我们还有多远
- 高阶函数对系统的“提纯”
如何封装好的函数
- 明确职责
- 限制副作用(尽量使用纯函数,限制操作DOM的函数的个数)
- 过程的优化
- 掌握抽象度
总结
- 函数的本质:封装过程,开放接口
- 理解过程抽象和函数式编程
- Tip:好的程序设计方式是面向接口编程,而不是面向实现编程
DOM & BOM
- DOM 文档树
DOM事件
- 事件流
- 默认行为
- 事件代理
DOM事件有两个阶段:
- Capture Phase:捕获阶段
- Bubbling Phase:冒泡阶段
实例一:事件流
- 实例二:组织默认行为–点击图片放大
- 实例三:事件代理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>事件流</title>
<script src="../../../materials/jquery-3.1.1.js"></script>
<style>
#outer{
width: 200px;
height: 200px;
background-color: forestgreen;
opacity: 0.7;
display: flex;
justify-content: center;
align-items: center;
}
#inner{
display: inline-block;
width: 100px;
height: 100px;
background-color: #3df;
opacity: 0.7;
}
#outer:hover, #inner:hover{
opacity: 0.2;
}
</style>
</head>
<body>
<div id="outer">
<div id="inner"></div>
</div>
<pre id="output">
output:
</pre>
<script>
const [outer, inner] = document.querySelectorAll('#outer', '#inner');
const output = document.getElementById('output');
function print(msg) {
output.innerHTML += '\n' + msg;
}
[document.body, outer, inner].forEach(el=>{
el.addEventListener('click', evt=>{
let t = el.tagName, i = el.id;
print(t + i + ' clicked!');
}, false);
})
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>点击放大图片</title>
<style>
#fullview{
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
#fullview.hide{
display: none;
}
#fullview img{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
#fullview>a{
color: white;
float: right;
margin-right: 12px;
font-size: 16px;
cursor: pointer;
text-decoration: none;
}
</style>
</head>
<body>
<div id="imgview">
<a href="../../../images/pic/00.png">
<img src="../../../images/pic/00.png" width="200px">
</a>
</div>
<div id="fullview" class="hide">
<img src="../../../images/pic/00.png" alt="">
</div>
<script>
const imageView = document.getElementById('imgview');
const fullView = document.getElementById('fullview');
imageView.addEventListener('click', evt=>{
evt.preventDefault();
fullView.className = '';
});
fullView.addEventListener('click', evt=>{
evt.target.className = 'hide';
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>事件代理</title>
<style>
button{
font-size: 16px;
}
li{
cursor: pointer;
user-select: none;
}
li.selected{
color: red;
}
</style>
</head>
<body>
<button>new</button>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
</ul>
<script>
const btn = document.querySelector('button');
const list = document.querySelector('ul');
const items = list.getElementsByTagName('li');
btn.addEventListener('click', evt=>{
let item = document.createElement('li');
item.innerHTML = items.length+1;
list.appendChild(item);
});
// 不推荐这种做法,因为无法给新增的li添加事件
// Array.from(items).forEach(item =>{
// item.addEventListener('click', evt=>{
// evt.target.className = evt.target.className?'':'selected';
// });
// });
// 推荐做法
list.addEventListener('click', evt=>{
let el = evt.target;
if(el.tagName === 'LI'){
el.className = el.className?'':'selected';
}
});
</script>
</body>
</html>
宽高和定位
clientWidth
、clientHeight
:内容的容器内可见宽高offsetWidth
、offsetHeight
:内容的盒子(不包括margin)宽高scrollWidth
、scrollHeight
:内容真正的宽高- 实例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>宽高和定位</title>
<style>
html, body {
margin: 0;
padding: 0;
}
p {
margin: 0;
padding: 0;
}
#outer {
display: inline-block;
width: 200px;
height: 200px;
margin: 10px;
background: gray;
text-align: center;
border: 5px solid lightseagreen;
}
#inner {
display: inline-block;
width: 100px;
height: 100%;
background: lightcoral;
overflow: scroll;
border: 5px solid lightseagreen;
}
</style>
</head>
<body>
<p id="atext">This is a paragraph</p>
<div id="outer">
<div id="inner">
<p>aaa</p>
<p>aaa</p>
<p>aaa</p>
<p>aaa</p>
<p>aaa</p>
<p>aaa</p>
<p>aaa</p>
<p>aaa</p>
<p>aaa</p>
<p>aaa</p>
<p>aaa</p>
<p>aaa</p>
<p>aaa</p>
<p>aaa</p>
<p>aaa</p>
<p>aaa</p>
<p>aaa</p>
</div>
</div>
<script>
setTimeout(function(){
// 无法获取继承的值
const p = document.getElementById('atext');
console.log('width' + 'height' +
JSON.stringify([p.style.width, p.style.height]));
// 通过getComputedStyle
const styleP = window.getComputedStyle(p);
console.log('width' + 'height' + JSON.stringify([styleP.width, styleP.height]));
console.log('clientWidth ' + 'clientHeight ' + JSON.stringify([p.clientWidth, p.clientHeight]));
console.log('offsetWidth ' + 'offsetHeight ' + JSON.stringify([p.offsetWidth, p.offsetHeight]));
console.log('scrollWidth ' + 'scrollHeight ' + JSON.stringify([p.scrollWidth, p.scrollHeight]));
console.log('---------');
console.log('clientWidth ' + 'clientHeight ' + JSON.stringify([outer.clientWidth, outer.clientHeight]));
console.log('offsetWidth ' + 'offsetHeight ' + JSON.stringify([outer.offsetWidth, outer.offsetHeight]));
console.log('scrollWidth ' + 'scrollHeight ' + JSON.stringify([outer.scrollWidth, outer.scrollHeight]));
console.log('---------');
console.log('clientWidth ' + 'clientHeight ' + JSON.stringify([inner.clientWidth, inner.clientHeight]));
console.log('offsetWidth ' + 'offsetHeight ' + JSON.stringify([inner.offsetWidth, inner.offsetHeight]));
console.log('scrollWidth ' + 'scrollHeight ' + JSON.stringify([inner.scrollWidth, inner.scrollHeight]));
}, 100);
</script>
</body>
</html>
- UI组件设计
- 结构设计
- API设计
- 控制流设计
- 实例:图片轮播
- 图片轮播
- -