JavaScript
JavaScript
JavaScript是一门弱类型脚本语言
合格后端人员,必须精通js
1、入门
1.1、基本语法
<script>
</script>
js代码写在script标签内
<script src="js/**.js"></script>
外部引入 另外写一个.js文件,通过文件路径引入,这里<scritpt>和</script>需成对出现,如果写成<script src=""/>有可能会有问题
js严格区分大小写
console.log(x)打印变量(浏览器控制台中调试用)
审查元素sources中可以给代码打断点调试
1.2、变量
var a = "";
变量名不可以用数字开头
局部变量建议都用let去定义而不用var
1.3、数据类型(入门)
基本类型(值类型)
数字(Number)
111 //整数
1.111 //浮点数
1e2 //科学计数法
-1 //负数
NAN //not a number
Infinity //无限大
字符串(String)
'aaa'
"aaa"
布尔(Boolean)
true
false
空(null)
null 空
未定义(undefined)
undefined 未定义
Symbol
引用数据类型
对象(Object)
var person{
name: "mingzi",
age: "2.5",
tags: ['js', 'java', '...']
}
对象使用大括号,数组使用中括号
每个属性之间用逗号隔开,最后一个不需要
如何取值:person.name
数组(Array)
一系列类型相同的对象
var arr = [1,2,"3",'4',true,null];//尽量使用这种,保证代码可读性
new Array(1,2,"3",'4',true,null);
数组越界会得到undefined
函数(Function)
1.4、运算符
逻辑运算
&& 与运算|| 或运算! 非运算
比较运算符
= 赋值== 等于(类型不一样,值一样,也true)=== 绝对等于(类型一样,值一样,才会true)
尽量不用==比较
NaN===NaN //false
NaN与所有数值都不相等,包括自己
只能通过isNaN(NaN)来判断是否NaN
let a = 1;let b = "1";let c = 1;console.log(a!=b) //falseconsole.log(a!==b) //trueconsole.log(b!=c) //falseconsole.log(b!==c) //trueconsole.log(a!=c) //falseconsole.log(a!==c) //false
比较过程
!= 比较时,若类型不同,会尝试转换类型;
!== 只有相同类型才会比较。
比较结果
!=返回同类型值比较结果 ;
!== 不同类型不比较,且无结果,同类型才比较;
浮点数问题
console.log((1/3) ===(1-2/3)) //false
尽量避免使用浮点数进行运算,存在精度问题
console.log(Math.abs(1/3-(1-2/3)) < 0.0000000001) //true
无限接近0算相等
1.5、严格检查模式
'use strict';
严格检查模式,预防js随意性导致产生的问题
必须写在JavaScript的第一行
2、数据类型(详细)
2.1、字符串
转移字符
"\'""\"""\n" 换行"\t" tab"\u4e2d" Unicode编码"\x41" AscII字符
多行字符串编写
用的是`不是单引号(波浪号那个键)
var msg = `啊啊啊啊啊啊`
模板字符串
let name = "shiro";let msg = `你好,${name}` //需要用`而不是单引号
字符串长度
str.length
字符串可变性

不可变
大小写转换
str.toUpperCase(); //大写str.toLowerCase(); //小写
字符位置获取(indexOf)
str.indexOf(searchvalue,fromindex);


截取字符串(substring)
str.substring(start,stop)


前闭后开,即substring(1,3)为[1,3)

只有一个参数时从参数位置开始截到最后
2.2、数组
Array,可以包含任意的数据类型
var arr = [1,2,3,4,5,6]
长度
arr.length
可以通过arr.length = 2来直接修改arr的长度,如果长度变大,会填充undefined值,如果改小,会丢失数据
indexOf
用法同字符串的indexOf
slice
同字符串的substring
splice
arr.splice(index,howmany,item1,.....,itemX)


push, pop
同栈的push和pop,往尾部压入和弹出元素
unshift, shift
和push,pop类似,往头部压入(unshift)和弹出(shift)元素
元素反转
arr.reverse()
concat
arr(4) [2, 5, "2", 2]arr.concat([2,34,5])(7) [2, 5, "2", 2, 2, 34, 5]arr(4) [2, 5, "2", 2]
并不会改变原来的数组,只返回一个新数组
join
arr(4) [2, 5, "2", 2]arr.join("-")"2-5-2-2"
打印数组,用特定字符连接
多维数组

2.3、对象
JavaScript中所有的键都是字符串,值是任意对象
键值对描述属性 xxx: xxx
var 对象名 = { 属性名: 属性值, 属性名: 属性值}
对象赋值

使用不存在对象时,不会报错,只会拿到undefined
person.ssundefined
动态删减属性
delete person.nametrueperson{age: 9, gender: "boy"}
动态添加

判断属性是否存在这个对象中
"age" in persontrue//继承的方法也会返回true"toString" in persontrue
判断一个属性是否这个对象自身拥有
person.hasOwnProperty("age")trueperson.hasOwnProperty("toString")false
2.4、流程控制
if
while
for
都和java一样
for in
let age =[1,2,3,4,5,6,7,1,2,3,1,2,3];for(let num in age){ console.log(age[num]);}
遍历age里的值
for of
let age =[1,2,3,4,5,6,7,1,2,3,1,2,3];for(let num of age){ console.log(num);}
效果与上面for in 一样,for in 获取得到下标,for of直接得到值
forEach
let age =[1,2,3,4,5,6,7,1,2,3,1,2,3];age.forEach(function (value){ console.log(value);})
2.5、Map
let map = new Map([["shiro",100],["shirooil", 90]]);let score = map.get("shiro");console.log(score);///100
map.set("bbb",80);map.delete("bbb");
2.6、Set
无序不重复的集合
let set = new Set([3,1,1,1,1,1]);

3、函数
3.1、定义函数方式一:
function add(a,b){ return a+b;}add(1,2) //3
3.2、定义函数方式二:
let a = function(a,b){//匿名函数,把结果赋值给a return a+b;}a(1,2) //3
如果没有执行return,会返回undefined
3.3、参数问题
JavaScript可以传任意个参数,也可以不传参数
假设不存在参数,如何解决?
可以手动抛出异常
let a = function(a,b){ if(typeof a != "number") throw "not a number"; return a+b;}

3.4、argument
可以通过argument获取传进来的所有参数,argument是一个数组

3.5、rest
通过rest获取传进来的除了已定义好的以外的所有参数,rest必须放在最后,且需...标识
let a = function(a,b,...rest){ console.log(arguments); console.log(rest);}

函数、变量作用域
内部函数可以访问外部函数成员,外部函数不可访问内部函数成员
function a(){ let x = 1; function b(){ let y = x +1; console.log(y);//2 } b() let z = y+1;//undefined}
在函数内定义的值,在函数外就没用了,互不相关
function a(){ let x = 1; console.log(x);//先输出1再通过b输出2 b();}function b(){ let x = 2; console.log(x);//只输出2}
JavaScript中函数查找变量从自身函数开始,由内向外查找,假设外部存在同名的变量,则内部函数会屏蔽外部函数的变量
function a(){ let x = 1; function b(){ let x = 2; console.log("b"+x);//b2 } console.log("a"+x);//a1 b(); }
使用var会先定义后赋值,即使用到的变量在使用程序句的后面,
但使用let的时候就不行了
let必须先声明,在使用。而var先使用后声明也行,只不过直接使用但没有定义的时候,其值是undefined。var有一个变量提升的过程,当整个函数作用域被创建的时候,实际上var定义的变量都会被创建,并且如果此时没有初始化的话,则默认为初始化一个undefined
function a(){ var x = "x"+y; console.log(x);//xundefined var y = 'y'; }//上下两个函数等价 function a(){ var y; var x = "x"+y; console.log(x);//xundefined y = "y"; }
提升变量的作用域,js执行引擎,自动提升了声明,但不会提升变量的赋值
全局变量
var x = 1;function f(){ console.log(x);}console.log(x);
全局对象window
var x =1;alert(x);//弹出1alert(window.x);//弹出1,所有全局变量默认绑定在window对象下
可以看出window被省略了,推断出alert也是window对象下的
window.alert(window.x);//弹出1var b = window.alert;b(x); //弹出1var a = window.alert(window.x);a;//弹出1
Js实际只有一个全局作用域,任何变量,假设没有在函数作用范围内找到,就会向外查找,如果在全局作用域都没有找到,就会报错
规范
所有全局变量都会绑定到window上,如果不同js文件使用了相同的全局变量,会导致冲突
如何减少全局变量冲突?
var shiro = {};//唯一全局变量shiro.name = "shirooil";//定义变量shiro.age = "22";

把变量全部放入自己定义的唯一命名空间中,减少全局变量冲突
局部作用域问题
for(var i = 0; i<10; i++) console.log(i);console.log(i+1);//i出了for循环还可以使用,不应该出现这个问题

var全局变量导致的问题,出了作用域还可以使用,因此使用let来解决局部作用域的问题
for(let i = 0; i<10; i++) console.log(i);console.log(i+1);不可以继续使用了

常量
ES6之前,定义常量的方法:全大写变量名,建议不要修改
ES6之后,引入了常量关键字 const
3.5、方法
方法:把函数放在对象里,对象只包含属性和方法两个东西
var shiro = { name : "shiro", birth : 1999, age: function (){ var now = new Date().getFullYear(); return now - this.birth; }}shiro.name //"shiro"shiro.age() //22//方法一定要带(),否则返回的是一段函数
function get_Age(){ var now = new Date().getFullYear(); return now - this.birth;}var shiro = { name : "shiro", birth : 1999, age: get_Age}shiro.age() //22get_Age() //NaN//this默认指向调用它的对象,如果直接调用则调用的window的对象console.log(get_Age.apply(shiro,[])); //22console.log(get_Age.apply(shiro)); //22console.log(get_Age.apply(lwr,[])); //25
apply可以控制this的指向,[]表示无参数,可省略
4、内部对象
标准对象
undefinedtypeof 123"number"typeof "123""string"typeof NaN"number"typeof null"object"typeof []"object"typeof {}"object"typeof undefined"undefined"typeof Math.abs"function"
typeof null"object"
为什么null的类型是object而不是null
这是JS语言本身的一个bug。
不同的对象在底层都表示为二进制,在js中二进制前三位都为0的话会被判断为object类型,null的二进制表示全是0,自然前三位也是0,所以执行typeof时返回"object"

4.1、Date
var now = new Date(); //Tue Jul 20 2021 14:46:41 GMT+0800 (中國標準時間)now.getFullYear(); //年now.getMonth(); //月 0~11 0代表1月 11代表12月now.getDate(); //日 now.getDay(); //周几now.getHours(); //时now.getMinutes(); //分now.getSeconds(); //秒 now.getTime(); //时间戳 1626763601756new Date(1626763601756); //Tue Jul 20 2021 14:46:41 GMT+0800 (中國標準時間)
时间戳全世界统一,从1970年1月1日0点开始毫秒级计算
4.2、Json
-
JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。
-
采用完全独立于编程语言的文本格式来存储和表示数据。
-
简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。
-
易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。
JS中一切皆为对象,任何JS支持的类型都可以用JSON来表示
格式:
- 对象{}
- 数组[]
- 键值对 key:value
var shiro = { name : "shiro", age : 2}var jsonshiro = JSON.stringify(shiro);shiro //{name: "shiro", age: 2}jsonshiro //"{\"name\":\"shiro\",\"age\":2}"var shiro_from_json = JSON.parse("{\"name\":\"shiro\",\"age\":2}")shiro_from_json //{name: "shiro", age: 2}
5、面向对象
JS的面向对象和其他的面向对象有区别
-
类:模板 原型对象
-
对象:具体的实例
在js中对于类和对象需要换下思维方式
原型:相当于继承
var shiro = { name: "shiro", run: function (){ console.log(this.name + "跑"); }}var lwr = { name: "lwr"}var bird = { name: "bird", fly: function (){ console.log(this.name + "飞"); }}bird.__proto__ = shiro;//原型对象 bird的原型是shirolwr.__proto__ = bird;//lwr的原型是birdconsole.log(bird.run()) //bird跑console.log(lwr.run()) //lwr跑console.log(lwr.fly()) //lwr飞
class
ES6之后才有class
var shiro = { name: "shiro", run: function (){ console.log(this.name + "跑"); } } class Student{ constructor(name) { this.name = name; } } class lwr extends Student{ constructor(name, age) { super(name); this.age = age; } get_age(){ console.log(this.age + "岁"); } } var lwr1 = new lwr("shiro", 11); lwr1.get_age(); //11岁

本质还是原型
原型链

6、操作BOM对象(重点)
JS和浏览器关系:JS诞生就是为了能够让他在浏览器中运行
BOM:浏览器对象模型
navigator
navigator对象可以获得浏览器信息,但是容易被人为修改,所以不推荐使用navigator对象的属性来判断和编写代码
location(重要)
location代表当前页面的URL信息

location.assign("")会跳转到其他网页
location.assign("https://www.bilibili.com")
一般用来设置新的定位
document
document代表当前的页面
可以获取具体的文档树节点
<ol id="order_list"> <li>嘻嘻</li> <li>哈哈</li></ol><script> var list = document.getElementById("order_list");</script>

document.cookie //获取cookie

服务器端可以设置cookie: httpOnly来防止cookie被别人获取
7、操作DOM对象(重点)
获取节点
<div class="father1" id="father"> <h1>标题</h1><p id ="p1">啊啊</p><p class="p2">啊啊</p></div><script> var h1 = document.getElementsByTagName("h1");var p1 = document.getElementById("p1");var p2 = document.getElementsByClassName("p2");var father = document.getElementById("father");var father1 = document.getElementsByClassName("father1");
这是原生代码,之后都用jQuery();

getElementById 是通过id来获取元素,id在HTML中是唯一的,所以获取到的只有一个元素。
getElementsByTagNam 是通过标签名来获取元素,一种标签在HTML中可以有多个,所以获取到的是多个元素,且返回是以集合的形式返回。
getElementsByClassName 是通过类名来获取元素,同名的类在HTML中也能存在多个,所以获取到的也是多个元素,同样是以集合的形式返回。

father.children; //获取父节点下的所有节点,返回一个集合
更新节点
h1[0].innerText = "啊啊啊"//innerText只可以改变文本p1.innerHTML= "<h1>嘻嘻</h1>";//innerHTML可以改变标签等(可以解析HTML标签)//注意获取节点时拿到的是集合还是单个元素p1.style.color = "red";//变红色字体,记得属性是字符串
删除节点
步骤:先获取父节点,通过父节点删除子节点
<div class="father1" id="father"> <h1>标题</h1> <p id ="p1">啊啊</p> <p class="p2">啊啊</p></div><script> var h1 = document.getElementsByTagName("h1"); var p1 = document.getElementById("p1"); var p2 = document.getElementsByClassName("p2"); var father = document.getElementById("father"); father.removeChild(h1[0]);//切记集合和单个元素区别 father.removeChild(p1); father.removeChild(father.children[0]);///还可以直接用下标删除,但删除是动态的,切记下标会跟着改变</script>
插入节点
如果dom节点为空,直接innerHTML就可以增加元素,如果dom节点已经存在元素,就不能这样,否则会覆盖
追加
<p id="outer">外面</p><div class="father1" id="father"> <h1>标题</h1> <p id ="p1">啊啊</p> <p class="p2">啊啊</p></div><script> var h1 = document.getElementsByTagName("h1"); var p1 = document.getElementById("p1"); var p2 = document.getElementsByClassName("p2"); var father = document.getElementById("father"); var p3= document.getElementById("outer"); father.appendChild(p3);

实际效果会把原来的p3节点删除,所以这应该是移动节点的操作,如果要添加一个节点且原节点不删除的话,需要先克隆再移动克隆出来的新节点
var p4 = p3.cloneNode(true);father.appendChild(p4);
cloneNode() 方法克隆所有属性以及它们的值。
如果您需要克隆所有后代,请把 deep 参数设置 true,否则设置为 false。
也可以创建一个新标签插入
var newp = document.createElement('p'); //创建一个新的p标签newp.id = "newp"; //给id赋值newp.innerText = "嘻嘻嘻嘻嘻"; //给newp文本赋值father.appendChild(newp);
var body_Get = document.getElementsByTagName("body");body_Get[0].style.background ="red";
可以获取body的标签来修改属性
var myStyle = document.createElement('style');myStyle.innerHTML = "\n" + " body{\n" + " background-color: red;\n" + " }"document.getElementsByTagName("head")[0].appendChild(myStyle);
效果同上修改body属性,可以看出js的添加节点可以干很多事情
insertbefore
father.insertBefore(p4,p1);//把p4这个节点插到p1之前
8、操作表单(验证)
var username = document.getElementById("username");
实时获得用户名输入框里输入的值
MD5加密要引入
<script src = "https://cdn.bootcss.com/blueimp-md5/2.10.0/js/md5.min.js"></script>
<body> <form id = "form" action="#"> <p> <span>用户名:</span> <input type="text" id="username" name="username"> </p> <p> <span>密码:</span> <input type="password" id="password" > </p> <input type="hidden" id="md5-password" name="password"> <button type="submit" onclick="aaa()">提交</button> </form><script> function aaa(){ var username = document.getElementById("username"); var pwd = document.getElementById("password"); var md5pwd = document.getElementById("md5-password"); md5pwd.value = md5(pwd.value); }</script></body>
表单需要有name的标签才能提交,所以这段代码里实际提交的是隐藏的id为md5-password的数据
9、jQuery
中文手册:https://jquery.cuishifeng.cn/
含有大量JS代码的库
引入
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
公式
$(selector).action()
<body> <a href="" id="test">点我</a><script> $('#test').click(function (){ alert("1"); })</script></body>
<script> //JS //标签选择器 document.getElementsByTagName(); //id选择器 document.getElementById(); //类选择器 document.getElementsByClassName(); //jQuery 顺序同上 css中的所有选择器jquery都能用 $('p').click(); $('#id').click(); $('.class').click(); </script>
事件
<script> $(document).ready(function (){ })//网页加载完后执行的函数,简化后成下面这行 $(function (){//和上一句同义 })</script>
鼠标事件
<style> #divMove{ width: 500px; height: 500px; border: 1px solid red; }</style><body>mouse: <span id ="mouseMoves"></span><div id="divMove"> 在这里移动鼠标试试</div><script> $(function (){ $('#divMove').mousemove(function (e){ $('#mouseMoves').text('x:'+e.pageX+'y:'+e.pageY); }); })</script></body>
操作Dom
节点文本操作
<ul id="ul1"> <li id ="li1">啊</li> <li>啊啊</li></ul><script> $('#ul1 li[id=li1]').text();//“啊” 获得值 $('#ul1 li[id=li1]').text("啊啊");//设置值 $('#ul1 li[id=li1]').html();//“啊” 获得值 $('#ul1 li[id=li1]').html("<strong>啊啊</strong>");//设置值</script>
CSS操作
$('#ul1 li[id=li1]').css("color","red");// 字变红
元素的显示和隐藏,本质:使display:none;切换
$('#ul1 li[id=li1]').show();$('#ul1 li[id=li1]').hide();
拓展
闭包
闭包:一个函数和它的周围状态的引用捆绑在一起的组合
如果面试官问到闭包
关键词:词法作用域
加分项:执行上下文机制 V8垃圾回收机制
//函数作为返回值function test(){ const a = 1; return function(){ console.log(a) }}const fn = test()const a = 2fn()//1//函数作为参数function test(fn){ const a = 1 fn()}const a = 2function fn(){ console.log(a)//2}test(fn)
执行上下文
javascript 中“执行上下文”的类型?
-
全局执行上下文:只有一个,也就是浏览器对象(即window对象),this指向的就是这个全局对象。
-
函数执行上下文:有无数个,只有在函数被调用时才会被创建,每次调用函数都会创建一个新的执行上下文。
-
Eval函数执行上下文:js的eval函数执行其内部的代码会创建属于自己的执行上下文, 很少用而且不建议使用。
词法作用域和动态作用域的区别
词法作用域(静态作用域)是在书写代码或者说定义时确定的,而动态作用域是在运行时确定的。
词法作用域关注函数在何处声明,而动态作用域关注函数从何调用,其作用域链是基于运行时的调用栈的。
var a = 2;function foo() { console.log( a );}function bar() { var a = 3; foo();}bar();//如果是词法作用域,输出2。如果是动态作用域,输出3
作用域
let声明的变量是块级作用域,var声明的变量是函数作用域
// 函数function a(){ var i = 0; console.log(i) // i = 0;}console.log(i) // i is not define// 块{ var j = 0; console.log(j) // j = 0}console.log(j) // j = 0;
// 和var声明变量的例子比较{ let i = 0; console.log(i) // i = 0;}console.log(i) // error
重定义this(call()、apply()、bind())

Promise
Promise属于JavaScript引擎内部任务,而setTimeout则是浏览器API,而引擎内部任务优先级高于浏览器API任务,所以有此结果。
判断页面加载完成
js
// (1)、页面所有内容加载完成执行window.onload = function(){ }// (2)、ie9以上版本监听事件if('addEventListener' in document){ document.addEventListener('DOMContentLoaded', function(){ }, false)//false代表在冒泡阶段触发,true在捕获阶段触发}// (3)、页面加载完毕document.onreadystatechange = function(){ if(doucument.readyState == 'complete'){ // 页面加载完毕 }}注:js方法没有依赖方法简单,但方法(2)存在兼容性问题。
Jquery
$(function(){})$(document).ready(function(){ // document 不写默认document})注:jquery方法兼容性好,并且实在dom资源加载完毕的情况下执行,(不包括图片视频资源)
Vue
mounted () { this.$nextTick(() => { // 确保dom异步加载完毕 })}
Array.prototype.slice.call()方法详解
Array.prototype.slice.call(arguments)能将具有length属性的对象转成数组,除了IE下的节点集合(因为ie下的dom对象是以com对象的形式实现的,js对象与com对象不能进行转换)
var a={length:2,0:'first',1:'second'};//类数组,有length属性,长度为2,第0个是first,第1个是secondconsole.log(Array.prototype.slice.call(a,0));// ["first", "second"],调用数组的slice(0);var a={length:2,0:'first',1:'second'};console.log(Array.prototype.slice.call(a,1));//["second"],调用数组的slice(1);var a={0:'first',1:'second'};//去掉length属性,返回一个空数组console.log(Array.prototype.slice.call(a,0));//[]function test(){ console.log(Array.prototype.slice.call(arguments,0));//["a", "b", "c"],slice(0) console.log(Array.prototype.slice.call(arguments,1));//["b", "c"],slice(1)}test("a","b","c");
将函数的实际参数转换成数组的方法
方法一:var args = Array.prototype.slice.call(arguments);
方法二:var args = [].slice.call(arguments, 0);
方法三:
var args = []; for (var i = 1; i < arguments.length; i++) { args.push(arguments[i]);}
转数组通用函数
var toArray = function(s){ try{ return Array.prototype.slice.call(s); } catch(e){ var arr = []; for(var i = 0,len = s.length; i < len; i++){ //arr.push(s[i]); arr[i] = s[i]; //据说这样比push快 } return arr; }}
浙公网安备 33010602011771号