月影JavaScript视频学习笔记 第零课

Q1: 列表渲染的不同版本 优劣

版本1(初级前端)

let list = document.querySelector('#user-list');
let items = document.querySelectorAll('#user-list > li');list.addEventListener('click', function(e){if(e.target.tagName === 'LI'){let item = e.target;items.forEach(function(item){item.style.background = 'inherit';item.style.color = 'inherit';});item.style.background = 'black';item.style.color = 'white';console.log(item.innerHTML);}
});

优点:用了事件委托,对于html中增删改列表元素时不受影响。

缺点:直接在js中操作了dom元素css的样式,如果ui改了需求,这里的代码也要改。

职责没有划分清楚。js应该尽量操作状态(比如active样式来操作),而不是直接操作dom。

ps:本人真的是这么做的,看来真的还是个初级前端。T_T

版本2(中规中矩)

let list = document.querySelector('#user-list');
let items = list.querySelectorAll('li');function addClass(el, cls){removeClass(el, cls);el.className += (' ' + cls).trim();
}function removeClass(el, cls){let pattern = new RegExp('(?:^|\s+)' + cls + '(?:\s+|$)', 'g');el.className = el.className.replace(pattern, ' ').trim();
}list.addEventListener('click', function(e){items.forEach(function(item){removeClass(item, 'active');});if(e.target.tagName === 'LI'){let item = e.target;addClass(item, 'active');console.log(item.innerHTML);}
});

优点:只是添加active的状态,具体样式交给css,ui需求更改的话,不用更改js。

扩展:js代码可以更少,可以直接借助原生的radio。

版本3(专业)

<ul id="user-list"><li><input name="items" id="item0" type="radio" value="张三"/><label for="item0">张三</label></li><li><input name="items" id="item1" type="radio" value="李四"/><label for="item1">李四</label></li><li><input name="items" id="item2" type="radio" value="王五"/><label for="item2">王五</label></li><li><input name="items" id="item3" type="radio" value="赵六"/><label for="item3">赵六</label></li>
</ul>
body{background-color: white;font-size: 24px;
}#user-list{line-height: 1.5em;
}#user-list input{display: none;
}#user-list label{display: block;
}.#user-list > li:hover{background-color: rgba(0,0,0,0.3);
}#user-list input:checked+label{background-color: black;color: white;
}
let list = document.querySelector('#user-list');list.addEventListener('click', function(e){if(e.target.tagName === 'INPUT'){let checkedItem = list.querySelector('input:checked');console.log(checkedItem.value);}
});

版本三扩展性很强,职责划分也很明确,比如单选改多选,只需要改html的结构。

ps:几大框架框架的优点:避免写出很烂的代码,但是是用比较暴力的方式,禁止直接操作dom。

jQuery:给dom操作封装了很多语法糖,所以很容易写出很多质量不太好的代码。

所以,还是要好好学原生的js。

Q2:API设计(红绿灯)

瑟瑟发抖的po一段自己的代码。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>traffic</title><style>li {list-style: none;display: block;width: 20px;height: 20px;background: #aaa;border-radius: 10px;margin-bottom: 10px;}ul.stop>li:first-child {background: #ff0000;}ul.run>li:last-child {background: #00ff00;}ul.wait>li:nth-child(2) {background: #ffff00;}</style>
</head>
<body><ul class="stop" id = "traffic"><li></li><li></li><li></li></ul><script>const state = ['stop', 'wait', 'run'];let i = 0;setInterval('somefunc()' ,2000);function somefunc() {i++;traffic.className = state[i % state.length];}</script>
</body>
</html>

当时遇到一个坑,setInterval第一个参数需要加上引号,不然只会执行一遍。

或者不加引号,但同时去掉括号:setInterval(somefunc ,2000);

版本二中的写法,就相当于不加括号同时不加引号的函数表达式(注意,此处不是函数声明,声明只能是具名函数,且以function开头)。

版本1(差评)

const traffic = document.getElementById('traffic');(function reset(){traffic.className = 'wait';setTimeout(function(){traffic.className = 'stop';setTimeout(function(){traffic.className = 'pass';setTimeout(reset, 2000)}, 2000)}, 2000);
})();

缺点:1. 过程耦合。如果三个状态改变顺序,需要改动较多代码。

  1. Callback Hell。如果需求灯数增加到很多,就会出现这种情况。

版本二(程序员都会的抽象方式,离及格线还差一点)

这个似乎就是我写的版本。。。

const traffic = document.getElementById('traffic');var stateList = ['wait', 'stop', 'pass'];var currentStateIndex = 0;setInterval(function(){var state = stateList[currentStateIndex];traffic.className = state;currentStateIndex = (currentStateIndex + 1) % stateList.length;
}, 2000);

优点:数据抽象出来了。

缺点:封装性不好。应该封装起来,并将stateList和currentStateIndex暴露出去。

ps:。。。好吧,完全忘了封装的事情。离及格线还差一点的初学新手就是我了。。。

版本三(中规中矩,七八十分)

const traffic = document.getElementById('traffic');function start(traffic, stateList){var currentStateIndex = 0;setInterval(function(){var state = stateList[currentStateIndex];traffic.className = state;currentStateIndex = (currentStateIndex + 1) % stateList.length;}, 2000);
}start(traffic, ['wait', 'stop', 'pass']);

优点:有意识的设计了api,暴露了该暴露的东西,封装了该封装的东西。

缺点:可复用性差。

版本四(过程抽象)

前面二三是状态、数据抽象,四是过程抽象,函数式编程,所以可以提高复用性。

先解释一下这中间的几个我不太懂的点。

1.rest参数 (直接引用的阮一峰老师的博客)

function poll(...fnList){...}

ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入该数组中。

比如下面这个例子就是讲2,5,3三个参数存入名为values的数组中。

function add(...values) {let sum = 0;for (var val of values) {sum += val;}return sum;
}add(2, 5, 3) // 10

上面代码的add函数是一个求和函数,利用 rest 参数,可以向该函数传入任意数目的参数。

下面是一个 rest 参数代替arguments变量的例子。

// arguments变量的写法
function sortNumbers() {return Array.prototype.slice.call(arguments).sort();
}// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();

上面代码的两种写法,比较后可以发现,rest 参数的写法更自然也更简洁。

arguments对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用Array.prototype.slice.call先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。下面是一个利用 rest 参数改写数组push方法的例子。

function push(array, ...items) {items.forEach(function(item) {array.push(item);console.log(item);});
}var a = [];
push(a, 1, 2, 3)

注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

// 报错
function f(a, ...b, c) {// ...
}

函数的length属性,不包括 rest 参数。

(function(a) {}).length  // 1
(function(...a) {}).length  // 0
(function(a, ...b) {}).length  // 1

函数的length是用来计算函数参数个数的,rest参数不包括在内。

2.apply和bind

func.call(thisObj,arg1,arg2…)、func.apply(thisObj,[obj1,obj2…])

第一个参数改变当前的this,第二个参数为函数传入的参数。

call和apply的差别,仅在于后续参数是数组还是多个写开。

func.bind(thisObj, arg1, arg2)

基本与call相同,差别在于bind返回的是绑定对象和参数后的函数,而非像call和apply那样立即执行,bind之后要赋给一个新的变量,再执行。

执行是还可以接着传参。

function foo(arg1, arg2) {console.log(arg1, arg2); // 1, 2
}
var bar = foo.bind(null, 1);
bar(2,3);

bind第一个参数为null,表示不改变函数this指向,这么写可以达到先传参但不立即执行的效果

下面我们来看月影给出的第四个版本

const traffic = document.getElementById('traffic');function poll(...fnList){let stateIndex = 0;return function(...args){let fn = fnList[stateIndex++ % fnList.length];return fn.apply(this, args);}
}function setState(state){traffic.className = state;
}let trafficStatePoll = poll(setState.bind(null, 'wait'),setState.bind(null, 'stop'),setState.bind(null, 'pass'));setInterval(trafficStatePoll, 2000);

bind是js原生支持的位数不多的高阶函数,返回一个函数,并可以预传参。

再将这三个函数传入poll中,然后每2000ms,循环调用

function(...args){let fn = fnList[stateIndex++ % fnList.length];return fn.apply(this, args);
}

返回的这个函数,每次调用都会对对stateIndex加1,因为内部有stateIndex++;fnList和stateIndex和这个函数在同一作用域内。

这里的args为调用返回函数式传入的参数,也就是trafficStatePoll调用时传入的参数,这个过程没有用到。

可以注意到,poll函数是和这个红绿灯具体过程无关的一个通用的轮流执行函数。

比如:

function a(){return 1};
function b(){return 0};var toggle = poll(a,b);console.log([toggle(), toggle(), toggle()]); // [1, 0, 1]

poll函数可以用来执行toggle、循环动画等等事情。

ps:实际上,版本三就可以啦,但是版本四比较有意思。

缺点:需求来了。。。要求红黄绿灯的时间分别是3s,2s,1s。。。。。。似乎只有版本1可以。。。

版本五(解决版本一中的回调地狱和过程耦合)

在po版本五的代码之前,我们先了解一下promise和then。

如果then里面是个promise,那么要等到resolve以后才会到下一个then,如果不是那么立即执行,完了之后就到then里面去。

const traffic = document.getElementById('traffic');function wait(time){return new Promise(resolve => setTimeout(resolve, time));
}function setState(state){traffic.className = state;
}function reset(){Promise.resolve().then(setState.bind(null, 'wait')).then(wait.bind(null, 1000)).then(setState.bind(null, 'stop')).then(wait.bind(null, 2000)).then(setState.bind(null, 'pass')).then(wait.bind(null, 3000)).then(reset);
}reset();

也可以用async和await。

const traffic = document.getElementById('traffic');function wait(time){return new Promise(resolve => setTimeout(resolve, time));
}function setState(state){traffic.className = state;
}async function reset(){//noprotectwhile(1){setState('wait');await wait(1000);setState('stop');await wait(2000);setState('pass');await wait(3000);}
}reset();

版本六(class)

const trafficEl = document.getElementById('traffic');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.statePromise = Promise.resolve();this.stateList.forEach((stateFn) => {this.statePromise = this.statePromise.then(()=>{return new Promise(resolve => {stateFn(subject, resolve);});});});if(this.autoReset){this.statePromise.then(this.reset.bind(this));}
}TrafficProtocol.prototype.start = function(){this.reset();
}var traffic = new TrafficProtocol(trafficEl, true);traffic.putState(function(subject, next){subject.className = 'wait';setTimeout(next, 1000);
});traffic.putState(function(subject, next){subject.className = 'stop';setTimeout(next, 2000);
});traffic.putState(function(subject, next){subject.className = 'pass';setTimeout(next, 3000);
});traffic.start();

非常灵活,可以随意的用putState来增减状态。

面向对象,函数编程,但是比较复杂,实现难度比较大。

Q3:js的效率问题

给定一个很大的数组,数组里面有许多整数,用 JavaScript 实现一个函数,要求:

将数组中之和为 10 的每一对数配对并找出,返回这些数配对后的数组。

例如:[11, 3, 8, 9, 7, -1, 1, 2, 4…]

得到:[[11,-1],[3,7],[8,2],[9,1]…]

先po上本人的

// O(n)尝试 因为要么i++,要么j--,所以是O(n)。
function map(testArr) {const test = testArr.sort((a, b) => {return a - b});let i = 0;let j = test.length - 1;let res = [];while(i !== j) {if(test[i] + test[j] === 10){res.push([test[i], test[j]]);i++;j--;} else if(test[i] + test[j] >= 10) {j--;} else {i++;}}return res;
}

月影大大的版本二和本人的好像差不多诶,惊喜惊喜。

let list = [11, 4, 9, 3, -1, -3, 6, 7, 9, 13, 8];function map(list){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 + b === 10){ret.push([a,b]);i++;j--;}else if(a + b < 10){i++;}else{j--;}}return ret;
}console.log(JSON.stringify(map(list)));

原来for循环还可以这么写,和while效果差不多。

其它解法:可以用hash表暂存,用空间换时间。

奇舞学院JavaScript视频-如何写好原生js相关推荐

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

    如何写好原生JavaScript 基础注意点 JavaScript负责行为,改变状态,不是用来改变样式的.js:动态脚本语言. CSS负责样式 <!DOCTYPE html> <ht ...

  2. 前端每日实战:163# 视频演示如何用原生 JS 创作一个多选一场景的交互游戏(内含 3 个视频)...

    效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/LXMzRX 可交互视频 此视频是可 ...

  3. 自己写的原生js轮播图插件

    时间有限,功能有待完善 <!DOCTYPE html> <html lang="en"><head><meta charset=" ...

  4. 奇舞学院学习笔记之CSS一页通

    CSS概念与简单选择器 版本 CSS Level 1 CSS Level 2(CSS2.1规范) CSS Level 3 Color Module Level 3 Selectors Level 3 ...

  5. 奇舞学院及高程学习笔记-函数篇

    函数 函数声明与函数表达式 函数声明提升与变量声明提升 函数声明:以function开头的函数定义. js在执行代码之前会先读取函数声明和变量声明,函数声明整个提升,变量赋值在执行时再执行. cons ...

  6. 前端每日实战:164# 视频演示如何用原生 JS 创作一个数独训练小游戏(内含 4 个视频)...

    效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/mQYobz 可交互视频 此视频是可 ...

  7. java原生的ajax怎么写,用原生js实现 ajax方法

    原标题:用原生js实现 ajax方法 作者介绍:咔拉宝宝前端工程师,致力于首个情景式购物商城咔拉商城的前端搭建,在咔拉职场栏目作为程序猿担当,与读者朋友们共同探讨前端开发的那些事. 一.ajax介绍: ...

  8. HTML5训练营,360奇舞特训营(一):HTML

    昨天去上了第一节360奇舞前端培训的第一节课,见着了仰慕已久的月影大大,心里超级超级激动.昨天上课的老师是超厉害的赵文博老师,主要内容是HTML相关.以前一直觉得HTML自己学得还好,昨天上完课后就感 ...

  9. 奇舞周刊第 460 期:详聊前端异常原理

    记得点击文章末尾的" 阅读原文 "查看哟~ 下面先一起看下本期周刊 摘要 吧~ 奇舞推荐 ■ ■ ■ 详聊前端异常原理 随着近年来前端监控体系建设日益完善,前端工程师对异常更加关注 ...

最新文章

  1. palapaweb怎样开启服务_过年期间如何做到系统服务安全运行?
  2. 苹果7【】闪存测试软件,不厚道!iPhone7大容量版竟采用TLC闪存
  3. CSS3: border-radius边框圆角详解
  4. Lucene索引库的维护功能实现
  5. Avalonia跨平台入门第十四篇之ListBox折叠列表
  6. Chrome不显示OPTIONS请求的解决方法2021版chrome90
  7. Python 获取服务器的CPU个数
  8. Java国际化资源绑定-----示例
  9. 谷歌android go 销量,谷歌Android Go进入尴尬期:目前仅十多个App专门适配
  10. Android RecyclerView封装下拉刷新与上拉加载更多
  11. 为CentOS7/RHEL7安装EPEL 仓库(repo)
  12. ZendFramework-2.4 源代码 - 整体架构(类图)
  13. 学习嵌入式必须学习32单片机吗?
  14. 整车电子电气架构EEA
  15. 计算机桌面壁纸在哪个文件夹,Win10桌面背景在哪个文件夹 Win10桌面背景所在文件夹介绍...
  16. OV7670 FIFO 30W摄像头介绍(三) --- STM32F103驱动OV7670代码介绍
  17. 看到这些网络骗局信息,请千万留个心眼
  18. Cross Domain Person Re-Identification With Large Scale Attribute Annotated Datasets参考文献解读
  19. 【B2B2C多用户】WSTMart商城系统 V2.0.6更新版发布
  20. Excel—PAPAYA电脑教室

热门文章

  1. 关于高并发限流那些事
  2. 全球及中国汽车驱动IC行业发展策略及投资可行性研究报告2022-2028年
  3. 远场语音技术简介-001
  4. 华师网络计算机在线作业答案,华师在线计算机基础在线作业题目和答案
  5. 青蛙跳 LintCode青蛙过河问题
  6. 14款奔驰R400升级ACC自适应巡航系统,增加您的行车安全性
  7. 计算机专业大一上学期期末总结
  8. 华为笔试2021-08-18
  9. 【力扣刷题 | 第十九天】
  10. 全国计算机python考试难吗_计算机二级python考试难吗