js本身是一门语言,概念和其它语言相通。唯和html相关的部分比较特殊,单独学下这部分。
不懂为什么大部分教程打包一起讲,都又从字符串概念讲起。
==了解== 熟悉 掌握 精通
概念和语法
DOM 文档对象模型
DOM,Document Object Model,文档对象模型。
网页加载的时候,浏览器会构建dom树。
可以增删查改html,都是在document
函数下操作。
查
ID查找:getElementById
类名查找:getElementsByClassName
标签名查找:getElementsByTagName
注意:id的element是单数,下面两个是复数。
可以嵌套:
var ele = document.getElementById("myId").getElementsByTagName("p"); // 查找myId下的p标签
后面update的用法,CSS 选择器。querySelector
和querySelectorAll
,前者选第一个,后者选所有。更加宽泛,不框定类型。推荐这一种。
注意这种方式,是选class还是选id,就完全根据引号里的输入确定,所以比上面的方式,要在前面加标记,#
、.
这些。
查id(一个):var ele = document.querySelector("#myId");
查类(所有):var ele = document.querySelectorAll(".myClass");
改
假设已经拿到上面特定的元素ele,现在想改。
改内容:ele.innerHTML = 新内容";
改属性:ele.src="landscape.jpg";
改样式:ele.style.color="blue";
// color也可以换
增
一般逻辑是,创建一个新元素,然后把它加到已存在的元素/dom树上,变为它的一个子节点。
创建:
创建一个元素节点:var node = document.createElement("p");
创建一个文本节点:var text = document.createTextNode("试试就逝世");
添加:
有两个函数,appendChild()
和 insertBefore()
。一个是添加到最后一个,一个是添加到指定元素之前。
注意都是添加为被加节点的子节点;
注意appendChild是默认加到最后,所以参数只有一个,insertBefore要指定参照元素,所以有两个参数。
文本加入p:node.appendChild(text);
父元素:parent = document.getElementById("div1");
// 添加到最后
p加到现存div里;parent.appendChild(text);
// 添加到任意位置
找到一个参照元素:ele = document.getElementById("otherP");
添加到参照元素之前:parent.insertBefore(node, ele)
这里被加的元素写第一个。
删
需要知道父节点,函数是removeChild
。但是可以通过该元素本身来确定。
要删的:var node = document.getElementById("p1");
删:node.parentNode.removeChild(node);
还可以换元素:replaceChild
直接用上面的html结构好了,将parent里的ele换为自己创建的node
parent.replaceChild(node, ele)
这里想要换上去的元素写第一个。
HTMLCollection & NodeList
做了一大轮功课发现,都没有什么准话,只是大略上的分类。以下都是大略:
htmlcollection一般是动态引用,但nodelist不一定是静态引用。
动态意思是,更改一小下都会重新计算,所以比较慢,所以最好把结果缓存起来。
querySelector
,childNodes
这些一般是nodelist;getElementXXX
这些一般是htmlcollection。
它俩都可以像list一样通过index访问,y = nlist[1];
,所以可以用for遍历。
但叫做类数组,而不是list。所以都不能用 foreach
这些函数。
都有 length 属性,nlist.length
这样引用。
var i;
for (i = 0; i < myNodelist.length; i++) {
myNodelist[i].style.backgroundColor = "red";
}
可以用array 把nodelist转成普通数组:Array.from(nodeList)
BOM 浏览器对象模型
BOM,Browser Object Model,文档对象模型。
HTML DOM 的 document 也是 window 对象的属性:window.document.getElementById("header");
可以直接在window上定义属性,当作全局变量,在函数外部访问。
window定义的可以直接删除,正常全局变量不行。没定义的可以判断是不是存在,也不会报错。
一些属性:
下面没说的都可以不使用 window 这个前缀
win尺寸:
window.innerHeight
| document.body.clientWidth
屏幕:
指不包括窗口任务栏这些的纯屏幕。
<script>
document.write("可用宽度: " + screen.availWidth);
</script>
打印可以用document.write() 学到了。
当前页地址:
url:location.href
主机域名:location.hostname
页面路径和文件名:location.pathname
主机端口:location.port
web 协议:location.protocol
history:
前进后退:history.forward()
| history.back()
别的方法:history.go(n)
// n为-1后退,1前进,0刷新
弹窗:
警告框:alert()
确认框:confirm()
提示框:prompt()
弹窗使用 反斜杠 + “n”(\n) 来设置换行
记时:
一直循环:setInterval()
定时执行:setTimeout()
显示记时,按钮停止:
<p id="demo"></p>
<button onclick="myStopFunction()">停止</button>
<script>
var myVar=setInterval(function(){myTimer()},1000);
function myTimer(){
var d=new Date();
var t=d.toLocaleTimeString();
document.getElementById("demo").innerHTML=t;
}
function myStopFunction(){
clearInterval(myVar);
}
</script>
事件
这部分很多文章都写的云里雾里。我自己理解讲下。
首先,我理解事件,就是在页面上发生的事情。用户和浏览器的交互,点击、打字等,以及其它类型的页面发生的改动。有一个之前我模糊的概念,这里面有两种函数,一个是事件发生的动作,有可能需要自定义函数,也有可能有默认函数及属性;一个是浏览器需要监听、绑定这些发出的动作,这里又需要一个函数。这两个是不同的。
事件绑定
要让用户的动作给程序知道,必须将事件和已有程序绑定起来。
事件发生的动作,有不同的方式来绑定。
一是对html属性,可以在元素的属性里直接绑定。
<button id="btn" onclick="console.log('试试就逝世');">Click</button>
上面的元素是<button>
,它的属性是onclick
,在onclick
这个属性上绑定click事件,这个事件就是console这个函数,打印一行字。
二是dom绑定。
上文说到的button元素比较特殊,有这些提供绑定的属性。如果直接想在页面上绑定事件,比如window或document下输出,就要使用on-event handle处理。说人话就是自己写一个function,不用给的onclick这种属性,然后绑定到DOM上。
window.onload = function(){
document.write("试试就逝世");
};
dom绑定也可以处理上面的实体元素。
var btn = document.getElementById('btn');
btn.onclick = function(){
console.log('试试就逝世');
};
三是使用事件监听器addEventListener()
要处理的元素对象.addEventListener("事件名称", 事件处理器函数, boolen[捕获/冒泡])
要处理的元素对象,比如buttom,或者div等其它元素。
事件名称,是预设好的,比如点击"click"、加载"load"、滚动"scroll"等等。
事件处理器函数,和上面二一样,自己定义的函数function。
最后的可选参数,选择执行机制是捕获还是冒泡,默认是冒泡。下面再解释。
这个方法是最推荐的,一是最不推荐的。
它的一个好处是,同一个要处理的对象,可以绑定多次事件,就处理多个函数。上面的只能绑定一个。
执行机制和事件代理
用户的动作被程序捕获之后,就可以处理了。但这里需要引入一个概念,如何传递这个事件消息,如何确定这个事件动作的管辖范围。
像地图一样,从宏观到精准定位这个元素,比如要控制一个表单其中一个格子,依次分别是:
window → document → html → body → table → tbody → tr → td
两种执行机制就是这条path的来回,从宏观到精确叫捕获,从精确回宏观到冒泡。
上面的监听事件就在这条path的途中截获。IE只有 →,addEventListener两回都有。
这样带依赖的传递,可以利用它提升处理性能。具体是使用addEventListener的事件委托。
如果要给巨量元素添加事件,可以凭着事件冒泡,直接将事件加给它们的父节点。 这样只注册父元素一个就行了,不至于句柄保存太多,让事件把内存撑满,而且调用、解绑的时候也比较方便。
性能差的循环绑定:
var boxes = document.getElementsByTagName('boxex');
for(var i=0, j=boxes.length; i<j; i++){
var text = boxes[i];
text.onclick = function(){
alert('someone like u');
}
text.parentNode.removeChild(text);
text.onclick()
}
性能好的依赖绑定:
var table = document.getElementById('event-table');
table.addEventListener('click', function(e){
//获取当前被点击的元素
var target = e.target;
//这里可以先针对不同的元素进行不同的获取参数的方法,稍后添加
var text = target.getAttribute('data');
//如果不满足条件,尽早跳出过程
if(!text){return;}
//阻止符合条件的元素的冒泡以及默认行为
e.preventDefault(),e.stopPropagation();
switch(text){
case 'A':
alert('a');
break;
case 'B':
alert('b');
break;
case 'x':
//同样执行删除,但是会干净好多
target.parentNode.removeChild(target);
alert(target.onclick);
break;
}
}, false);
其它零碎概念
变量: var let 和 const
是ES版本所致,ES6新增let和const。
var 是全局变量,在块域内定义,块域外仍可以用。
let 是局部变量,只在块域内有效。但如果在全局定义它,它就也是全局的。
要注意它们在for循环括号里声明的区别。
在html代码中使用,var 定义的变量x,可以用window.x调用。let不行。
var有变量提升的特性,即可以先使用后声明。let不行。
const:
固定常量声明。
和let都是块级作用域。
但是不能修改。
但是如果声明数组,数组的值可以改。
函数: 匿名函数 箭头函数 this 和 bind
js的函数也是对象。(为什么要说也)
首先,普通函数:
function funcName(params) {
return params + 2;
}
函数表达式的方式,即函数没名字,直接被赋给一个变量。一般给这个变量起的名字,和普通函数的函数名一样。所以在很多教程里面,尤其是对比的时候,把这俩名字写一样,弄到后面复杂的就给我弄混了。实际上,此种方式的函数本身没名字,即是匿名函数。只是要赋给一个变量,可以起跟函数名一样的名字。
var varName = function (params) {
return params + 2;
}
这种方式比较省空间。还有一些别的区别,先撂下。
函数表达式可以化简成箭头函数:
const fn = params => { return params + 2 }
这里面params是参数。非一个参数,0个,多个的,加括号;只一个表达式的,省大括号:() => console.log(111)
。其它语法先撂下。
箭头函数不绑定this。即不改变this本来的绑定。 一般函数里面定义的this.变量,就变成指向这个函数的this了。
如果fa函数里面定义了一个this.变量,同时嵌套了一个child函数,调用了this.变量,那么此时该变量就相相当于child函数.变量。而这个child函数没new,没被变量引用,也没绑定bing、call、apply,一般child的this指向window,相当于child.变量=window.变量。如果想引用的是 fa函数.变量,就错了。
如果这个child函数换成箭头函数,再调用this.变量,这个this就还指向fa函数,相当于fa.变量。
this:
普通函数定义并调用,相当于该函数.call(window, args)
即call,是显式强调了它绑定的this,普通函数没声明别的,就是window。
var person = {
name:"aa",
say: function(word) {
console.log(this + "say:" + word)
}
}
这种样式的变量person,相当于一个class,里面的参数name和say相当于python的
class:
__init__(self):
self.name = ""
def say(self, word):
console.log(self.name + "say:" + word)
然后调用这个person的函数就:
person.say('blabla')
等同于 person.say.call(person, 'blabla')
剩下的碰到了再学。
continue、循环和回调函数
在JavaScript中,continue关键字只能在循环中使用,用于跳过当前循环迭代。然而,continue不能在Array.prototype.map()、Array.prototype.filter()或Array.prototype.forEach()等数组方法的回调函数中使用,因为这些方法并不是真正的循环。
例如Array.prototype.map()方法,是一个函数式编程方法,它会对数组的每个元素调用一个函数,并返回一个新的数组。这个函数需要返回一个值,这个值会被添加到新的数组中。如果想跳过某个元素,需要返回一个特殊的值(例如null),然后在后面使用Array.prototype.filter()方法来过滤掉这个值。
const posts = filenames.map(filename => {
// 一些处理
if (!fs.existsSync(filename)) {
return null;
}
return { data };
}).filter(Boolean); // 过滤掉null值
其它
模块化
模块化的意思就是,把一个文件里的功能拆成不同的子文件,统一调用,方便统筹。
javascript 模块化我竟然不知道的东西:
– 都在html里script标签引入之后,js 里面定义的函数和变量就可以直接用了。
– 所以多个需要直接被script标签引入的js文件里的变量和函数名不能互相重复。
– 多个 JS 文件之间存在依赖关系,必需严格的保证 JS 的加载顺序。
→ 解决
使用模块化规范。有很多标准,这些不同的标准有不同的语法支持。默认先说 es6。
最主要的特性就是,用export把常量、函数、文件、模块等打包export出去,需要用的地方import进来。
规则:
– export、import可以有多个,export default仅有一个
– export与export default均可用于导出常量、函数、文件、模块等
– 通过export方式导出,在导入时要加{ },export default则不需要– export能直接导出变量表达式,export default不行
其它注意的:
import 也可以用来只导入文件
<script type="module">
import "./module.js";
</script>
<!--相当于:-->
<script type="module" src="./module.js"></script>
如果js里直接是调用函数,就可以直接用这种方式import,如下:
(function() {
console.log('IIFE');
})();
import './fn.js';
ES6和commonJS 区别
es6是异步加载,编译时输出接口;commonJS是同步加载,运行时加载。
es6输出:export { count, add };
commonJS输出:module.exports = { count, add };
js框架
jQuery
是一个JavaScript函数库,封装了一些JS的任务,用起来更方便。
比如js中获取元素:document.getElementById()
jQuery获取元素:$()
jQuery怎么用
- 官网下载它的库,放进项目包,like [path]/jquery.min.js。
- 然后在html中用 引用
- 或者从其它js文件中
import $ from '../libs/jquery.min';
- 从CDN载入
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>