奇舞学院JavaScript视频-如何写好原生js
2018-02-13 23:22
330 查看
月影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. 过程耦合。如果三个状态改变顺序,需要改动较多代码。
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(){ //noprotect while(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获取style样式
- 原生js如何实现柱状图以及原生js柱状图结合ajax循环动态数据_JavaScript_柱状图(原生JavaScript做的柱状图)(03)_ajax获取数据
- 原生js如何做拖拽_javascript的拖拽怎么做?可拖拽的窗口(div)
- javascript原生移动云编程10 - 如何调用相机并上传下载图片视频
- 【奇舞js笔记】第0课——如何写好原生js代码
- JavaScript如何做表格即时编辑,原生js的表格即时编辑怎么做
- 原生js如何做一个链式运动,JavaScript怎么做鼠标跟随效果
- 蓝鸥原生JS:什么是cookie及如何设置cookie
- [JavaScript]如何实现一个JS脚本能在browser和NodeJs里都是用
- javascript正则表达式提取指定的字符 分享如何随机播放采集的优酷视频地址
- 如何运行时(动态)加载js脚本|JavaScript
- JS与Objective-C交互(网页与原生交互---使用JavaScriptCore)
- js 如何判断Javascript对象是否存在
- Android平台,如何调用javascript操作网页和js调用系统功能
- 面向对象的JavaScript(如何一步步成为js高手)
- javascript如何调用C#后代码中的过程 和ASP.NET调用JS乱码解决方案
- [JS]视频总结-第二部分_JavaScript基础
- Javascript 如何生成Less和Js的Source map
- JS原生实现视频弹幕Demo(仿)
- 原生JS中如何获取CSS属性中的值