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 选择器。querySelectorquerySelectorAll,前者选第一个,后者选所有。更加宽泛,不框定类型。推荐这一种。

注意这种方式,是选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不一定是静态引用。

动态意思是,更改一小下都会重新计算,所以比较慢,所以最好把结果缓存起来。

querySelectorchildNodes 这些一般是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>