奇舞学院学习笔记之JavaScript一页通

如何写好原生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 里有个特殊的取值NaNNaN 与任何值(包括自身)都不想等

Boolean 类型

  • 只有两个取值:truefalse
  • 0' 'nullundefined 被隐式地转换为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.EPSILONInfinity 无穷大、Number.MAX_VALUENumber.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的是包装类型)
    • 对象的拷贝
  • 类型与构造器
    • newconstructor
    • prototype 原型链(一层层往上找某 属性)这里写图片描述
    • instanceOf
    • ES6 class
  • 内置类型
    • Object:根
    • Function:JS中最核心的
    • Promise:处理异步
    • Array:数组
    • Date:日期
    • Regex:正则表达式
    • Error
    • Math:数学
    • ArrayBuffer
    • DataView
    • Map:数据类型
    • Set:数据类型,用来去重
    • TypedArray:数据类型
    • Proxy
  • 对象的高级属性
    • 为什么不建议修改Object.protytypeArray.prototype 呢?在类型(或对象)上添加方法的代价,会被所有的实例所继承下来。解决方法:Object.defineProperty,不会被for in 检索出来,若想让其检索出来需要加enumerable:true;
    • gettersetter
    • 属性描述符
    • 数据绑定视图
  • 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>

这里写图片描述

applycallbind

  • JavaScript的this
    • JS的this 是由函数求值时的调用者决定的
  • apply, call, bind
    • 通过applycall 指定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
      • 。。。

实例一:

<!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 构造器与函数模版
  • 过程抽象和高阶函数
  • 过程抽象与函数式编程
    • 过程抽象的定义
      • 过程抽象与数据抽象的区别
      • 这里写图片描述

函数式编程

  • 什么是函数式编程
  • 函数式编程与前端代码有什么关系

    • 函数的纯度和“提纯”
    • UncurringCurrying & 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>

这里写图片描述

  • 宽高和定位

    • 这里写图片描述

    • clientWidthclientHeight:内容的容器内可见宽高

    • offsetWidthoffsetHeight:内容的盒子(不包括margin)宽高
    • scrollWidthscrollHeight:内容真正的宽高
    • 实例:
<!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设计
    • 控制流设计
    • 实例:图片轮播
  • 图片轮播
  • -

动画基础(上)

动画基础(下)

服务器与HTTP基础

常用设计模式和组件开发

浅谈前端工程化

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值