文章目录
大端 BE
小端 LE
html css
js
https://github.com/yygmind/blog
单线程
在JS中,所谓的异步任务,有三种:
第一,
鼠标键盘事件触发
,例如onclick、onkeydown等等
第二,
网络事件触发
,例如onload、onerror等等
第三,
定时器
,例如setTimeout、setInterval
setTimeout(“console.log(2)”, 0);
console.log(1);
反复执行这段代码,结果都是先打印1再打印2
因为,setTimeout是个异步任务,
第二个参数真正的含义是,在0毫秒之后,将代码插入任务队列,而不是在0毫秒之后执行。
当插入任务队列后,主线程会继续执行后续的代码,也就是打印结果1,如果此时当前的同步代码已经执行完毕,则主线程立刻会从任务队列中取出最新任务执行。再打印结果2
嵌入js脚本
<body>
<input type="button" onclick="javascript:document.write('按钮被谁点了一下')" value="点我一下">
</body>
也可以直接在地址栏输入
javascript:alert(1)
输出
window.alert() // 写入警告框
document.write() // 写入HTML文档中
innerHTML() // 写入HTML元素
console.log() // 控制台
<script>
document.getElementById("demo").innerHTML = "段落已修改";
</script>
换行
var x = "Hello \
World!";
严格模式
-
不允许使用未声明的变量
-
不允许删除变量,对象,删除函数 delete x
-
不允许变量重名
-
不允许使用
八进制,转义字符
-
不允许对只读属性赋值
"use strict"; var obj = {}; Object.defineProperty(obj, "x", {value:0, writable:false}); obj.x = 3.14
-
变量名不能使用==“eval” “arguments”字符串==
-
不能用with
比如,目前现在有一个这样的对象: var obj = { a: 1, b: 2, c: 3 }; 如果想要改变 obj 中每一项的值,一般写法可能会是这样: // 重复写了3次的“obj” obj.a = 2; obj.b = 3; obj.c = 4; 而用了 with 的写法,会有一个简单的快捷方式 with (obj) { a = 3; b = 4; c = 5; }
with弊端,导致数据泄漏
function foo(obj) { with (obj) { a = 2; } } var o1 = { a: 3 }; var o2 = { b: 3 } foo(o1); console.log(o1.a); //2 foo(o2); console.log(o2.a); //underfined console.log(a); //2,a被泄漏到全局作用域上
参考:https://blog.csdn.net/zwkkkk1/article/details/79725934
不能删除变量
delete prop
,会报错,只能删除属性
delete global[prop]
eval
不会在它的外层作用域引入变量
eval
和
arguments
不能被重新赋值
arguments
不会自动反映函数参数的变化
不能使用
arguments.callee
以及
arguments.caller
禁止this指向全局对象(顶层的
this
指向
undefined
,应该在顶层代码使用
this
)
不能使用
fn.caller
和
fn.arguments
获取函数调用的堆栈
增加了保留字(比如
protected
、
static
和
interface
)
变量
变量名不能是数字
var x = "Porsche" + 911 + 7;
Porsche9117
var x = 911 + 7 + "Porsche";
918Porsche
变量是window的属性
var person = new Object()
person.name = "z"
person.age = 1
console.log(person)
function add() {
console.log(this) // Window
console.log(this.person.name, this.person.age)// z 1
}
add()
变量提升
var a = 1
var b = "test"
console.log("a = ", a , b) // a = 1 test
// undifined,undifined 声明了但是没有定义
console.log(x, y)
var x, y = 5;
// undifined 5 声明了x但是没有定义x
console.log(x, y)
变量提升的作用域
-
声明提前仅能将声明提前到所在作用域的顶部
-
不能将变量提升至作用域之外,
function fn(){ console.log(a); //undefined var a=100; console.log(a); //100 }; fn(); console.log(a);// 报错未定义!
数据类型
typeof
var a = 3
console.log(typeof(a) === "number")
// 数组用
Arrays.isArray()
string typeof "小白" "string"
number typeof 18 "number"
boolean typeof true "boolean"
undefined typeof undefined "undefined"
null typeof null "object"
== 和 ===
== 比较
值
,===比较
值和类型
var s = "s"
var s2 = new String("s")
s === s2 // false
console.log(123 == 123n) // 要注意此时是true
console.log(123 === 123n) // false
console.log("123" == 123) // true
console.log("123" === 123) // false
console.log(null == undefined) // true
console.log(null === undefined) // false
// 注意:
NaN 的数据类型是 number
数组(Array)的数据类型是 object
日期(Date)的数据类型为 object
null 的数据类型是 object
未定义变量的数据类型为 undefined
constructor
"John".constructor // 返回函数 String() { [native code] }
(3.14).constructor // 返回函数 Number() { [native code] }
false.constructor // 返回函数 Boolean() { [native code] }
[1,2,3,4].constructor // 返回函数 Array() { [native code] }
{name:'John', age:34}.constructor // 返回函数 Object() { [native code] }
new Date().constructor // 返回函数 Date() { [native code] }
function () {}.constructor // 返回函数 Function(){ [native code] }
// 1
function isArray(myArray) {
return myArray.constructor.toString().indexOf("Array") > -1;
}
function isDate(myDate) {
return myDate.constructor.toString().indexOf("Date") > -1;
}
// 2
var test=new Array();
if (test.constructor==Array)
{
document.write("This is an Array");
}
if (test.constructor==Boolean)
{
document.write("This is a Boolean");
}
if (test.constructor==Date)
{
document.write("This is a Date");
}
if (test.constructor==String)
{
document.write("This is a String");
}
// 3
Object.prototype.toString.call('123') === '[object String]' // true
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call("abc");// "[object String]"
Object.prototype.toString.call(123);// "[object Number]"
Object.prototype.toString.call(true);// "[object Boolean]"
分类
-
原始数据类型
- boolean
- number
- string
- null
- undefined
- bigint
-
对象类型,有时也被称为
引用类型
- 简单对象类型(Object)
- 数组(Array)
-
函数(Function)
-
日期(Date)
-
正则表达式对象(RegExp)
,以
/
开始
/
结束 - 包装类型
-
NaN表示not a number
-
+Infinity表示正无穷大
-
-Infinity表示负无穷大
-
number表示的数字参与运算会存在数据
精度
问题,
52位
-
null空类型只能取一个值null,表示对象未初始化,它和空对象{}不一样
-
undefined未定义类型表示一个变量申明时
未初始化
-
bigint
表示任意精度的整数,末尾加上
n
-
不能
和普通数值类型混合运算,但可以比较 -
bigint
不能
用于Math对象中的方法
string
字符串转成数组
txt="a,b,c,d,e" // String
txt.split(","); // 使用逗号分隔
txt.split(" "); // 使用空格分隔
txt.split("|"); // 使用竖线分隔
var str = '改革春风吹满地,春天来了';
console.log(str.indexOf('春')); //默认从0开始查找 ,结果为2
console.log(str.indexOf('春', 3)); // 从索引号是 3的位置开始往后查找,结果是8
// charAt
var str = 'abcoefoxyozzopp';
var o = {};
for (var i = 0; i < str.length; i++) {
var chars = str.charAt(i); // chars 是 字符串的每一个字符
if (o[chars]) { // o[chars] 得到的是属性值
o[chars]++;
} else {
o[chars] = 1;
}
}
console.log(o);
// 2. 遍历对象
var max = 0;
var ch = '';
for (var k in o) {
// k 得到是 属性名
// o[k] 得到的是属性值
if (o[k] > max) {
max = o[k];
ch = k;
}
}
console.log(max);
console.log('最多的字符是' + ch);
其他方法和java的类似,indexOf 从1开始
属性:length,prototype
方法名 说明
concat(str1,str2,str3…) concat() 方法用于连接两个或对各字符串。拼接字符串
substr(start,length) 从 start 位置开始(索引号), length 取的个数。
slice(start,end) 从 start 位置开始,截取到 end 位置 ,end 取不到 (两个都是索引号)
substring(start,end) 从 start 位置开始,截取到 end 位置 ,end 取不到 (基本和 slice 相同,但是不接受负)
-
toUpperCase()
转换大写 -
toLowerCase()
转换小写
Number
- 默认情况下,JavaScript 数字为十进制显示
-
安全整数范围为
-(253 - 1)到
253 - 1
之间的整数,包含
-(253 - 1)和
253 - 1
。 - 但是你可以使用 toString() 方法 输出16进制、8进制、2进制。
var myNumber=128;
myNumber.toString(16); // 返回 80
myNumber.toString(8); // 返回 200
myNumber.toString(2); // 返回 10000000
Number.isInteger(10); // 返回 true Number.isInteger(10.5); // 返回 false
Number.isSafeInteger(10); // 返回 true
Number.isSafeInteger(12345678901234567890); // 返回 false
Number.isNaN()
var num = 5.56789;
var n=num.toExponential()
n 输出结果:
5.56789e+0
把数字格式化为指定的长度:
var num = new Number(13.3714);
var n=num.toPrecision(2);
n 输出结果:
13
把数字转换为字符串,结果的小数点后有指定位数的数字:
var num = 5.56789;
var n=num.toFixed(2);
n 输出结果:
5.57
属性:MAX_VALUE,MIN_VALUE,prototype,NEGATIVE_INFINITY 负无穷大,POSITIVE_INFINITY正无穷大,NAN
Array
-
数组Array,属性length获取个数,里面的元素可以
任意类型
- join(sep)拼接
- split(sep)分割字符串
- sort()
- push()向后添加
- remove(index, count),index位置开始删除count个元素
- isArray()
- 其他参考网上
var person = [];
person[0] = "John";
person[1] = "Doe";
person[2] = 46;
var x = person.length; // person.length 返回 3
var y = person[0]; // person[0] 返回 "John"
var person = [];
person["firstName"] = "John";
person["lastName"] = "Doe";
person["age"] = 46;
var x = person.length; // person.length 返回 0
var y = person[0]; // person[0] 返回 undefined
forEach(value, index, array)
<p>乘以: <input type="number" id="multiplyWith" value="10"></p>
<button onclick="numbers.forEach(myFunction)">点我</button>
<p>计算后的值: <span id="demo"></span></p>
<script>
var numbers = [65, 44, 12, 4];
function myFunction(item,index,arr) {
arr[index] = item * document.getElementById("multiplyWith").value;
demo.innerHTML = numbers;
}
</script>
属性:length,prototype
Date
<script>
var timestamp = new Date();
document.write('<font size="2" ><B>' + timestamp.getFullYear()
+ "年" + (timestamp.getMonth() + 1) + "月"
+ timestamp.getDate()+ "日"+ '</B></font><BR>');
var hours;
var mins;
var time;
hours = timestamp.getHours();
if (hours >= 12)
time = " 下午";
else
time = " 上午";
if (hours > 12) hours -= 12;
if (hours == 0) hours = 12;
mins = timestamp.getMinutes();
if (mins < 10) mins = "0" + mins;
document.write('<font size="2"><B>' + time
+ hours + ":"+ mins + '</B></font>');
</script>
var date = new Date();
console.log(date.getFullYear()); // 返回当前日期的年 2019
console.log(date.getMonth() + 1); //返回的月份小一个月 记得月份 +1
console.log(date.getDate); //返回的是几号
console.log(date.getDay()); //周一返回1 周6返回六 周日返回0
// 写一个 2019年 5月 1日 星期三
var date = new Date();
var year = date.getFullYear();
var month = date.getMonth() + 1;
var dates = date.getDate();
console.log('今天是' + year +'年' + month + '月' + dates +'日' );
// 封装一个函数返回当前的时分秒 格式 08:08:08
function getTimer() {
var time = new Date();
var h = time.getHours();
h = h < 10 ? '0' + h : h;
var m = time.getMinutes();
m = m < 10 ? '0' + m : m;
var s = time.getSeconds();
s = s < 10 ? '0' + s : s;
return h + ':' + m + ':' + s;
}
console.log(getTimer());
// 倒计时
function countDown(time) {
var nowTime = +new Date(); //没有参数,返回的是当前时间总的毫秒数
var inputTime = +new Date(time); // 有参数,返回的是用户输入时间的总毫秒数
var times = (inputTime - nowTime) / 1000; //times就是剩余时间的总的秒数
var d = parseInt(times / 60 / 60 / 24); //天数
d < 10 ? '0' + d : d;
var h = parseInt(times / 60 / 60 % 24); //小时
h < 10 ? '0' + h : h;
var m = parseInt(times / 60 % 60); //分
m < 10 ? '0' + m : m;
var s = parseInt(times % 60); //秒
s < 10 ? '0' + s : s;
return d + '天' + h + '时' + m + '分' + s + '秒';
}
console.log(countDown('2020-11-09 18:29:00'));
var date = new Date;
console.log(date); //现在时间
Math
- round 四舍五入
- random随机
- floor向下取整
- ceil向上取整
- 。。其他和java类似
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Math对象</title>
<style>
#display{ background-color:red; color:white; font-size:16px; font-weight:bold;}
</style>
</head>
<body>
<input type="text" id="myIn">
<input type="button" value="猜数" id="btn" />
<label id="display"></label>
<script>
var myrandom=Math.floor(Math.random()*100);
var myBtn=document.getElementById("btn");
var myInput=document.getElementById("myIn");
var myDisplay=document.getElementById("display");
myBtn.onclick=function(){
myValue=myInput.value;
if(isNaN(myValue))
myDisplay.innerHTML="请0-100输入数字";
else{
if(myValue>myrandom)
myDisplay.innerHTML="数据大了!";
else if(myValue<myrandom)
myDisplay.innerHTML="数据小了!";
else
myDisplay.innerHTML="恭喜您,猜对了!";
}
}
</script>
</body>
</html>
运算符
和java的类型类似
注意: type=“text”
<input type="text" name="pagenum" id="pagenum"><input type="button" value="翻页" onclick="o()">
<script>
function o() {
var pageText = document.querySelector("#pagenum");
var pageNum = pageText.value;
// 字符串转为 Number
console.log(window.parseInt(pageNum) + 1)
}
</script>
boolean
undifined -> false
null -> false
0 -> false
'' -> false
false -> false
// 其他的值都是真,但要注意 [],{}是真
Boolean
var b1=new Boolean(0);
var b2=new Boolean(1);
var b3=new Boolean("");
var b4=new Boolean(null);
var b5=new Boolean(NaN);
var b6=new Boolean("false");
document.write("0 为布尔值 "+ b1 +"<br>");
document.write("1 为布尔值 "+ b2 +"<br>");
document.write("空字符串是布尔值 "+ b3 + "<br>");
document.write("null 是布尔值 "+ b4+ "<br>");
document.write("NaN 是布尔值 "+ b5 +"<br>");
document.write("字符串'false' 是布尔值"+ b6 +"<br>");
0 为布尔值 false
1 为布尔值 true
空字符串是布尔值 false
null 是布尔值 false
NaN 是布尔值 false
字符串'false' 是布尔值true
浮点型
var x = 0.1;
var y = 0.2;
var z = x + y // z 的结果为 0.30000000000000004
if (z == 0.3) // 返回 false
var z = (x * 10 + y * 10) / 10; // z 的结果为 0.3
精度。。。
全局属性/函数
Infinity | 代表正的无穷大的数值 |
---|---|
NaN | 指示某个值是不是数字值 |
undefined | 指示未定义的值 |
decodeURI() |
解码某个编码的 URI。 |
---|---|
decodeURIComponent() | 解码一个编码的 URI 组件。 |
encodeURI() |
把字符串编码为 URI。 |
encodeURIComponent() | 把字符串编码为 URI 组件。 |
escape() | 对字符串进行编码。 |
eval() | 计算 JavaScript 字符串,并把它作为脚本代码来执行。 |
isFinite() | 检查某个值是否为有穷大的数。 |
isNaN() |
检查某个值是否是数字。 |
Number() |
把对象的值转换为数字。 |
parseFloat() |
解析一个字符串并返回一个浮点数。 |
parseInt() |
解析一个字符串并返回一个整数。 |
String() |
把对象的值转换为字符串。 |
unescape() | 对由 escape() 编码的字符串进行解码。 |
https://www.runoob.com/jsref/jsref-obj-global.html
Error
try {
adddlert("Welcome");
}
catch(err) {
document.getElementById("demo").innerHTML =
err.name + "<br>" + err.message;
}
类型转换
// 转成 字符串*****************************
String(x) // 将变量 x 转换为字符串并返回
String(123) // 将数字 123 转换为字符串并返回
String(100 + 23) // 将数字表达式转换为字符串并返回
x.toString()
(123).toString()
(100 + 23).toString()
String(false) // 返回 "false"
String(true) // 返回 "true"
false.toString() // 返回 "false"
true.toString() // 返回 "true"
// 字符串转成数字******************************
Number("3.14") // 返回 3.14
Number(" ") // 返回 0
Number("") // 返回 0
Number("99 88") // 返回 NaN
Number(false) // 返回 0
Number(true) // 返回 1
parseFloat() // 解析一个字符串,并返回一个浮点数
parseInt() // 解析一个字符串,并返回一个整数
var y = "5"; // y 是一个字符串
var x = + y; // x 是一个数字
var y = "5"; // y 是一个字符串
var x = + y; // x 是一个数字
d = new Date();
d.getTime() // 返回 1404568027739
// 自动类型转换*********************************
5 + null // 返回 5 null 转换为 0
"5" + null // 返回"5null" null 转换为 "null"
"5" + 1 // 返回 "51" 1 转换为 "1"
"5" - 1 // 返回 4 "5" 转换为 5
myVar = {name:"Fjohn"} // toString 转换为 "[object Object]"
原始值 | 转为number | 转为字符串 | 转为boolean |
---|---|---|---|
false | 0 | “false” | false |
true | 1 | “true” | true |
0 | 0 | “0” | false |
1 | 1 | “1” | true |
“0” |
0 | “0” |
true |
“000” = |
0 | “000” |
true = |
“1” | 1 | “1” | true |
NaN | NaN | “NaN” | false |
Infinity | Infinity | “Infinity” | true |
-Infinity | -Infinity | “-Infinity” | true |
“” |
0 |
“” |
false |
“20” | 20 | “20” | true |
“Runoob” | NaN | “Runoob” | true |
[ ] |
0 |
“” |
true |
[20] | 20 | “20” | true |
[10,20] | NaN | “10,20” | true |
[“Runoob”] | NaN | “Runoob” | true |
[“Runoob”,“Google”] |
NaN |
“Runoob,Google” |
true |
function(){} | NaN | “function(){}” | true |
{ } |
NaN |
“[object Object]” |
true |
null | 0 | “null” | false |
undefined | NaN | “undefined” | false |
简单 复杂类型
https://juejin.cn/post/6844903813858459656
简单类型又叫做基本数据类型或者值类型,复杂类型又叫做引用类型。
值类型:简单数据类型/基本数据类型,在存储时变量中存储的是值本身,因此叫做值类型
string ,number,boolean,undefined,null
引用类型:复杂数据类型,在存储时变量中存储的仅仅是地址(引用),因此叫做引用数据类型
通过 new 关键字创建的对象(系统对象、自定义对象),如
Object、Array、Date、Function、正则
等
- 简单数据类型存放到栈里面
- 复杂数据类型存放到堆里面
<script>
// 简单数据类型传参
function fn(a) {
a++;
console.log(a);
}
var x = 10;
fn(x);
console.log(x);
</script>
<script>
// 复杂数据类型传参
function Person(name) {
this.name = name;
}
function f1(x) { // x = p
console.log(x.name); // 2. 这个输出什么 ? 刘德华
x.name = "张学友";
console.log(x.name); // 3. 这个输出什么 ? 张学友
}
var p = new Person("刘德华");
console.log(p.name); // 1. 这个输出什么 ? 刘德华
f1(p);
console.log(p.name); // 4. 这个输出什么 ? 张学友
</script>
JS函数
- 在JavaScript中函数会被提前申明,无论在当前作用域中什么地方写的函数,他们全会被移动到当前作用域开始之处
使用
-
函数一等公民,没有重载的概念
-
函数没有写返回值,默认return undefined
-
调用的时候看是不是直接赋值,fun() 有返回值, fun 没有返回值
function show(a) {
console.log(a)
}
show(2, 2, 3, 4) // 只会打印第一个 2
function fn(){
console.log(arguments)
}
fn(1, 2, 3, 4);
// ******实现重载的操作*******
// 1.数据类型不同的方式
function show(a) {
if(typeof(a) === 'string') {
return a.length;
} else if (typeof(a) === 'number') {
return a + 1;
}
}
console.log(show(1))
console.log(show("a"))
// 2.数量
function info(a, b, c) {
if(b === undefined) {
console.log("一个值");
} else {
if(c === undefined) {
console.log("两个值");
} else {
console.log("三个值");
}
}
}
info(1)
info(1, 2)
info(1, 2, 3)
// 这种方式会有小bug,就是传入的是undefined的时候
info(1, undefined, 3) // 一个值
arguments
// 函数声明
function fn() {
console.log(arguments); //里面存储了所有传递过来的实参
console.log(arrguments.length); // 3
console.log(arrguments[2]); // 3
}
// 函数调用
fn(1,2,3);
匿名函数
定义之后无法再次调用,可以将匿名函数
赋值给某个变量或者作为参数
传给其他函数
// 方式一
console.log(function(a, b) {
return a + b;
}(1, 2));
// 方式二
var fun = function (a, b) {
return a + b;
}
console.log(fun(1, 2));
(function (){
console.log("666");
})()
脱离与绑定
一个对象的函数 赋给了另一个 变量,会从该对象 脱离
function person(name) {
this.name = name || "张三"
this.info = function() {
console.log(this)
console.log(this.name)
}
}
var p1 = new person()
var fun = p1.info
p1.info()
// 脱离对象
fun()
// 绑定回 原来的对象
var fun2 = fun.bind(p1)
fun2()
var p2 = new person("李四")
var fun3 = fun.bind(p2)
fun3();
函数预解析(函数提升)
函数提升: 函数的声明会被提升到当前作用域的最上面,但是不会调用函数。
fn(); //11
function fn() {
console.log('11');
}
解决函数表达式声明调用问题
对于函数表达式声明调用需要记住:函数表达式调用必须写在函数声明的下面
// 匿名函数(函数表达式方式):若我们把函数调用放在函数声明上面
fn();
var fn = function() {
console.log('22'); // 报错
}
//相当于执行了以下代码
var fn;
fn(); //fn没赋值,没这个,报错
var fn = function() {
console.log('22'); //报错
}
作用域
// 此处不能调用 carName 变量
function myFunction() {
var carName = "Volvo";
// 函数内可调用 carName 变量
}
// 此处不能调用 carName 变量
function myFunction() {
var carName = "Volvo";
// 函数内可调用 carName 变量
}
如果变量在函数内没有声明(没有使用 var 关键字),该变量为全局变量。
// 此处不能调用 carName 变量
function myFunction() {
var carName = "Volvo";
// 函数内可调用 carName 变量
}
局部变量在函数执行完毕后销毁
全局变量在页面关闭后销毁
作用域链
作用域的集合就是作用域链==(子集可以访问父集,父集不能访问子集)==
function fn(){
var a=10;
function fn1(){
var b=20;
alert(a) //10
function fn2(){
alert(b) //20
alert(a) //10 子集可以跨级访问父级
}
fn2()
}
fn1()
}
fn()
在运行期上下文的作用域链中,标识符所在的位置越深,读写速度就会越慢。因此在标识符解析的时候,
查找全局变量是最慢的
。所以,在编写代码的时候应
尽量少使用全局变量,尽可能使用局部变量
闭包**
优缺点,引起内存泄露的解决
JavaScript 支持嵌套函数。嵌套函数可以访问上一层的函数变量。
该实例中,内嵌函数
plus()
可以访问父函数的
counter
变量
function add() {
var counter = 0;
function plus() {counter += 1;}
plus();
return counter;
}
如果我们能在外部访问
plus()
函数,这样就能解决计数器的困境。
我们同样需要确保
counter = 0
只执行一次。
var add = (function () {
var counter = 0;
return function () {return counter += 1;}
})();
add();
add();
add();
// 计数器为 3
// add变量可以作为一个函数使用。非常棒的部分是它可以访问函数上一层作用域的计数器。这个叫作 JavaScript 闭包。它使得函数拥有私有变量变成可能。计数器受匿名函数的作用域保护,只能通过 add 方法修改。
JS对象
创建对象
- 利用字面量创建对象
- 利用 new Object创建对象
- 利用构造函数创建对象
// 常用
var person = {
firstName : "a",
lastName : "b",
id : 12,
fullName : function() {
return this.fristName + " " + this.lastName
}
}
// this 是person
console.log(person)
console.log(person.fullName)
var persons = {
name : "卢总",
hobbies : ['篮球', '唱K', 'rap'],
dog : {name : '吗捞', color : '黑色'},
eat : function(food) {
console.log('喜欢吃' + food)
}
}
console.log(persons.eat('shit'))
var star = {
name : 'pink',
age : 18,
sex : '男',
sayHi : function(){
alert('大家好啊~');
}
};
// 多个属性或者方法中间用逗号隔开
// 方法冒号后面跟的是一个匿名函数
var obj = new Object(); //创建了一个空的对象
obj.name = '张三丰';
obj.age = 18;
obj.sex = '男';
obj.sayHi = function() {
console.log('hi~');
}
//1.我们是利用等号赋值的方法添加对象
//2.每个属性和方法之间用分号结束
console.log(obj.name);
console.log(obj['sex']);
obj.sayHi();
要注意
脱离与绑定
// 用的少
function Dog(name, color) {
this.name = name;
this.color = color;
}
var dog = new Dog("卢总", "黑色")
console.log(dog)
new 过程**
- 在内存中创建一个新的空对象。
- 让 this 指向这个新的对象。
- 执行构造函数里面的代码,给这个新对象添加属性和方法
- 返回这个新对象(所以构造函数里面不需要return)
模拟js new 的过程
function _new(){
// 1、创建一个新对象
let target = {};
let [constructor, ...args] = [...arguments]; // 第一个参数是构造函数
// 2、原型链连接
target.__proto__ = constructor.prototype;
// 3、将构造函数的属性和方法添加到这个新的空对象上。
let result = constructor.apply(target, args);
if(result && (typeof result == "object" || typeof result == "function")){
// 如果构造函数返回的结果是一个对象,就返回这个对象
return result
}
// 如果构造函数返回的不是一个对象,就返回创建的新对象。
return target
}
let p2 = _new(Person, "小花")
console.log(p2.name) // 小花
console.log(p2 instanceof Person) // true
对象添加属性
obj.name = “Kitty”; //为对象增加属性
obj.showInfo = function () { //为对象添加方法
console.log(this.name + "," + this.age);
};
通过对象名.方法名的方式 调用改方法
obj.showInfo(); //调用对象的方法
-
Javascript中
不存在
公有方法和私有方法的
区别
-
一般使用
下划线
开头的变量名或函数名作为私有成员的标志,_name -
是否会被调用,只能
靠自觉
遍历对象
polyfill**
兜底
默认值
var person = {
name: "咋"
}
var person2 = {
name: "li",
age: 21
}
person.age = person.age || 18
person2.age = person2.age || 18 // 有值则用有的,没有则用||后面的值
console.log(person) // 18
console.log(person2)// 21
什么是兜底?
- es6的代码 使用 babel 变为es5的代码
var person = {
name: "咋"
}
person.info = function() {
console.log(this)
}
var person2 = {
name: "li",
age: 21
}
function show(person) {
person.info && person.info();
}
// 等同于
// function show(person) {
// if(person.info != undefined) {
// person.info()
// }
// }
show(person);
show(person2);// 没有,所以不会调用函数
prototype**
所有的 JavaScript 对象都会从一个 prototype(原型对象)中继承属性和方法:
-
Date
对象从
Date.prototype
继承 -
Array
对象从
Array.prototype
继承 -
Person
对象从
Person.prototype
继承
所有 JavaScript 中的对象都是
位于原型链顶端的 Object 的实例
JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在
该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索
,直到找到一个名字匹配的属性或到达原型链的末尾。
Date
对象,
Array
对象, 以及
Person
对象从
Object.prototype
继承
-
通过原型
向对象添加必要的属性和方法
-
这种方法添加的属性和方法属于对象,
每个对象实例的属性值和方法都是相同的
function person(name, age) {
this.age = age || 18
this.name = name || "李四"
this.info = function() {
console.log(this)
}
}
person.prototype.nationality = "China"
var a1 = new person()
console.log(a1.nationality)
a1.info();
var a = [1, 2, 3]
// polyfill
Array.prototype.crazy = Array.prototype.crazy || function() {
console.log("sss");
}
内置对象
https://www.runoob.com/jsref/prop-element-children.html
https://developer.mozilla.org/zh-CN/
js中的堆栈
demo1.
var a = 1;
var b = a;
b = 2;
// 这时a是? a=1
demo1中在变量对象中的数据发生复制行为时,系统会自动为新的变量分配一个新值。var b = a执行之后,b虽然重新赋值为2,但是他们其实已经是相互独立互不影响的值了。
demo2.
var m = { a: 1, b: 2 }
var n = m;
n.a = 2;
// 这时m.a的值呢? 2
栈内存
栈内存由编译器自动分配与释放。我们可以直接操作栈内存中的值。js中的基本数据类都有固定的大小,被分配到栈内存中。这些基本类型的值都是按值引用。
将一个基本类型的值赋值给另外一个基本类型时,会为这个新的值重新创建一个值并保存在栈内存中
堆内存
堆内存是
链表结构的类型
,可以动态分配大小,js引用类型占用内存空间的大小不固定,存储在堆内存中。由于JS不允许直接访问堆内存中的位置,因此我们不能直接操作js的引用类型。而是生成一个指针,并将它放到栈内存中,通过这个
指针来操作引用类型
垃圾回收
现在
浏览器基本都在使用标记-清除的算法来执行垃圾回收
。而不是使用引用计数的方式,因为引用计数方式无法释放循环引用结构的内存占用。 标记清除算法的核型概念是:从根部(在JS中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的。那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收
内存泄漏
对于持续运行的服务进程(daemon),必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。 对于不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)
常见的内存泄漏
- 不合理的计时器
- 被共享的闭包作用域
- 脱离dom引用
- 意外的全局变量
this
-
每次使用this都要
问自己这个this是谁
!!!!!! - 对象. this为该对象,每次点的时候 要注意
方法中
var person = {
firstName: "John",
lastName : "Doe",
id : 5566,
fullName : function() {
return this.firstName + " " + this.lastName;
}
};
this是person
单独使用
var x = this;
// 或者 严格模式下
"use strict";
var x = this;
this都是window对象
函数中
function myFunction() {
return this;
}
this是window对象
// 严格模式下
"use strict";
function myFunction() {
return this;
}
this是undefined
cookie
function setCookie(cname,cvalue,exdays){
var d = new Date();
d.setTime(d.getTime()+(exdays*24*60*60*1000));
var expires = "expires="+d.toGMTString();
document.cookie = cname+"="+cvalue+"; "+expires;
}
function getCookie(cname){
var name = cname + "=";
var ca = document.cookie.split(';');
for(var i=0; i<ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name)==0) { return c.substring(name.length,c.length); }
}
return "";
}
function checkCookie(){
var user=getCookie("username");
if (user!=""){
alert("欢迎 " + user + " 再次访问");
}
else {
user = prompt("请输入你的名字:","");
if (user!="" && user!=null){
setCookie("username",user,30);
}
}
}
in place
arr.sort()
arr.push()
// 1.push() 在我们数组的末尾,添加一个或者多个数组元素 push 推
var arr = [1, 2, 3];
arr.push(4, '秦晓');
console.log(arr);
console.log(arr.push(4, '秦晓'));
console.log(arr);
// push 完毕之后,返回结果是新数组的长度
// 2. unshift 在我们数组的开头 添加一个或者多个数组元素
arr.unshift('red');
console.log(arr);
// pop() 它可以删除数组的最后一个元素,一次只能删除一个元素
arr.pop(); //不加参数
// shift() 它剋删除数组的第一个元素,一次只能删除一个元素
arr.shift(); //不加参数
非 in place
会产生新的
str = str.replace(",", "-")
arr = arr.join("-");
arr.concat
遍历 ***
// 1
for (var i = 0; i < arr.length; i++) {
console.log(arr[i])
}
// 2
for (var element in arr) {
console.log(arr[element])
}
// 3
for (var element of arr) {
console.log(element)
}
// 4
arr.forEach(function(i) {
console.log(i);
})
// 4
arr.forEach((e) => console.log(e));
数组不是下标的情况
var arr2 = [1, 4, 3]
arr[5000] = 5000;
console.log(arr[5000]); // 5000
console.log(arr)
// (5001) [1, 2, 1, empty × 4997, 5000, name: "apple", taste: "dd"]
// of,forEach遍历不出来,只能用fori 和 in遍历
arr["aname"] = "apple"
arr["btaste"] = "dd"
console.log(arr["aname"])
console.log(arr["btaste"])
void
<a href="javascript:void(0);">点我没有反应的!</a>
<a href="#pos">点我定位到指定位置!</a>
<a href="javascript:void(alert('Warning!!!'))">点我!</a>
JSON
键的字符串的引号”” 可以
省略
var person = {
"name": "张三",
"age": 18,
"dog": {
dogname: "卢总",
dogcolor: "黄色",
},
arr: ["sdfsd","sdfsd",1 ,{tel:"123456", tel1: 123}]
}
console.log(person)
console.log(person.arr[3].tel)
属性值可以使用变量
属性的名字,使用变量值,使用[]
var name2 = "李四"
var fied = "name"
var p2 = {
[filed] :name2
}
console.log(p2)
如果
键的名
和
变量名
相同,键名可以省略
// 如果键的名i在和变量名相同,键名可以省略
var value = 3
var id = 5
var v1 = {
"value": value,
"id": id,
"info": function info() {
console.log(this)
}
}
console.log(v1)
// 等同于
var v2 = {
value,
id,
info() {
console.log(this)
},
}
console.log(v2)
删除
myObj = {
"name":"runoob",
"alexa":10000,
"sites": {
"site1":"www.runoob.com",
"site2":"m.runoob.com",
"site3":"c.runoob.com"
}
}
delete myObj.sites.site1
json数组
{
"name":"网站",
"num":3,
"sites":[ "Google", "Runoob", "Taobao" ]
}
for (i in myObj.sites) {
x += myObj.sites[i] + "<br>";
}
json.parse()
后端传给前端json,前端将数据显示
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<h2>json转为js对象</h2>
<p id="demo"></p>
<script>
var text = '{ "sites" : [' +
'{ "name":"Runoob" , "url":"www.runoob.com" },' +
'{ "name":"Google" , "url":"www.google.com" },' +
'{ "name":"Taobao" , "url":"www.taobao.com" } ]}';
obj = JSON.parse(text);
document.getElementById("demo").innerHTML = obj.sites[1].name + " " + obj.sites[1].url;
</script>
</body>
</html>
json.stringify()
前端对象转成json传给后端,
var obj = { "name":"runoob", "alexa":10000, "site":"www.runoob.com"};
JSON.stringify(arr);
数组转换 变成json传给后端
var arr = [ "Google", "Runoob", "Taobao", "Facebook" ];
var myJSON = JSON.stringify(arr);
异步
xhr,183
ajax
promise,具体看es6
jsonp
可以
跨域获取数据
$.getJSON('url', function(data){
...
})
正则
https://www.runoob.com/jsref/jsref-obj-regexp.html
修饰符:
修饰符 | 描述 |
---|---|
i |
执行对大小写不敏感的匹配。 |
g |
执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。 |
m | 执行多行匹配。 |
方法:
exec |
检索字符串中指定的值。返回找到的值,并确定其位置。 |
---|---|
test |
检索字符串中指定的值。返回 true 或 false。 |
toString |
返回正则表达式的字符串。 |
var str="Hello world!";
//查找"Hello"
var patt=/Hello/g;
var result=patt.exec(str);
document.write("返回值: " + result);
//查找 "RUNOOB"
patt=/RUNOOB/g;
result=patt.exec(str);
document.write("<br>返回值: " + result);
以上实例输出结果:
Returned value: Hello
Returned value: null
支持正则的String的方法
search |
检索与正则表达式相匹配的值。 | 1 | 4 |
---|---|---|---|
match |
找到一个或多个正则表达式的匹配。 | 1 | 4 |
replace |
替换与正则表达式匹配的子串。 | 1 | 4 |
split |
把字符串分割为字符串数组。 |
- 正则表达式 以/开始 /结束
- .表示 一个字符
- *表示 0或多个字符
- +表示 重复前面的字符,有一个或多个字符
- ^表示 开头 /^(aa)/ 以aa开头
-
$
表示 结尾 /(bc)$/ 以bc结尾
search找不到返回 -1, 找到则,表示从哪个下标开始
search找
完全匹配上
var str = "abc"
var reg1 = /abcdefg/
var reg2 = /abc/
console.log(str.search(reg1)) // -1 找不到
console.log(str.search(reg2)) // 0 从0下标开始
var str = "zzabczz"
var str2 = "zzabbczz"
var reg1 = /a.{1,2}c/ // 表示有ac中间有1个或两个字符
console.log(str.search(reg1)) // 2 从2下标开始
console.log(str2.search(reg1)) // 2
var reg2 = /^a.{1,2}c$/ // 表示开头是a ,结尾是c,中间一个或两个字符
var reg3 = /^a.*c$/ // 表示开头a,结尾b,重复前面的字符,有0个或多个字符
var str4 = "ac"
var reg4 = /^a.+c$/
console.log(str4.search(reg4)) // 0
var reg4 = /^a.+c$/ // 表示开头a,结尾b, +号是重复前面的字符,一个或多个
var str4 = "ac"
var reg4 = /^a.+c$/
console.log(str4.search(reg4)) // -1
// 表示开头a,结尾b,中间是a,或者b,或者c ,如果是连续的 直接用-
var reg5 = /^a[a|b|c]+c$/
var reg6 = /^a[a-c]+c$/
具体可参考网上的
事件
onclick,鼠标点击
<h1 onclick="this.innerHTML='Ooops!'">点击文本!</h1>
<!DOCTYPE html>
<html>
<head>
<script>
function changetext(id)
{
id.innerHTML="Ooops!";
}
</script>
</head>
<body>
<h1 onclick="changetext(this)">点击文本!</h1>
</body>
</html>
onmouseover,onmouseout
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<div onmouseover="mOver(this)" onmouseout="mOut(this)" style="background-color:#D94A38;width:120px;height:20px;padding:40px;">Mouse Over Me</div>
<script>
function mOver(obj){
obj.innerHTML="Thank You"
}
function mOut(obj){
obj.innerHTML="Mouse Over Me"
}
</script>
</body>
</html>
onmousedown、onmouseup
onload,可用于处理cookie
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body onload="checkCookies()">
<script>
function checkCookies(){
if (navigator.cookieEnabled==true){
alert("Cookies 可用")
}
else{
alert("Cookies 不可用")
}
}
</script>
<p>弹窗-提示浏览器 cookie 是否可用。</p>
</body>
</html>
onfocus
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<head>
<script>
function myFunction(x){
x.style.background="yellow";
}
</script>
</head>
<body>
输入你的名字: <input type="text" onfocus="myFunction(this)">
<p>当输入框获取焦点时,修改背景色(background-color属性) 将被触发。</p>
</body>
</html>
事件
操作元素
元素内容
var div = document.querySelector('div');
div.innerText = '<strong>今天是:</strong> 2019';
// 2. innerHTML 识别html标签 保留空格和换行的
div.innerHTML = '<strong>今天是:</strong> 2019';
<script>
function setInnerText(element, text) {
//判断浏览器是否支持这个属性
if (typeof element.textContent == "undefined") {//不支持
element.innerText = text;
} else {//支持这个属性
element.textContent = text;
}
};
</script>
innerHTML,innerText,textConte
元素属性
// img.属性
img.src = "xxx";
input.value = "xxx";
input.type = "xxx";
input.checked = "xxx";
input.selected = true / false;
input.disabled = true / false;
样式属性
行内样式
// element.style
div.style.backgroundColor = 'pink';
div.style.width = '250px';
类名样式
element.className
.style优先级高于className
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.firstChange {
background-color: red;
}
</style>
</head>
<body>
<div class="first">文本</div>
<script>
// 1. 使用 element.style 获得修改元素样式 如果样式比较少 或者 功能简单的情况下使用
var test = document.querySelector('div');
test.onclick = function () {
// this.style.backgroundColor = 'purple';
// this.style.color = '#fff';
// this.style.fontSize = '25px';
// this.style.marginTop = '100px';
// 让我们当前元素的类名改为了 change
// 2. 我们可以通过 修改元素的className更改元素的样式 适合于样式较多或者功能复杂的情况
// 3. 如果想要保留原先的类名,我们可以这么做 多类名选择器
// this.className = 'change';
this.className = 'firstChange';
}
</script>
</body>
</html>
排他
<body>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<button>按钮4</button>
<button>按钮5</button>
<script>
// 1. 获取所有按钮元素
var btns = document.getElementsByTagName('button');
// btns得到的是伪数组 里面的每一个元素 btns[i]
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = function() {
// (1) 我们先把所有的按钮背景颜色去掉 干掉所有人
for (var i = 0; i < btns.length; i++) {
btns[i].style.backgroundColor = '';
}
// (2) 然后才让当前的元素背景颜色为pink 留下我自己
this.style.backgroundColor = 'pink';
}
}
//2. 首先先排除其他人,然后才设置自己的样式 这种排除其他人的思想我们成为排他思想
</script>
</body>
自定义属性
<body>
<div id="demo" index="1" class="nav"></div>
<script>
var div = document.querySelector('div');
// 1. 获取元素的属性值
// (1) element.属性
console.log(div.id);
//(2) element.getAttribute('属性') get得到获取 attribute 属性的意思 我们程序员自己添加的属性我们称为自定义属性 index
console.log(div.getAttribute('id'));
console.log(div.getAttribute('index'));
// 2. 设置元素属性值
// (1) element.属性= '值'
div.id = 'test';
div.className = 'navs';
// (2) element.setAttribute('属性', '值'); 主要针对于自定义属性
div.setAttribute('index', 2);
div.setAttribute('class', 'footer'); // class 特殊 这里面写的就是class 不是className
// 3 移除属性 removeAttribute(属性)
div.removeAttribute('index');
</script>
</body>
h5自定义属性
H5规定自定义属性 ==data-==开头作为属性名并赋值,使用getAttribute或者
.dataset.
<body>
<div getTime="20" data-index="2" data-list-name="andy"></div>
<script>
var div = document.querySelector('div');
console.log(div.getAttribute('getTime'));
div.setAttribute('data-time', 20);
console.log(div.getAttribute('data-index'));
console.log(div.getAttribute('data-list-name'));
// h5新增的获取自定义属性的方法 它只能获取data-开头的
// dataset 是一个集合里面存放了所有以data开头的自定义属性
console.log(div.dataset);
console.log(div.dataset.index);
console.log(div.dataset['index']);
// 如果自定义属性里面有多个-链接的单词,我们获取的时候采取 驼峰命名法
console.log(div.dataset.listName);
console.log(div.dataset['listName']);
</script>
</body>
注册事件
<body>
<div onClick=show()> 注册事件的三种方法 <div>
<script>
//1.使用HTML属性注册
function show(){
alert("div被点击了")
}
//2.使用javaScipt的DOM注册事件
var div=document.querySelector("div")
//注意 事件类型以 on开头 后面对应的事件名为小写
div.onclick=function(){
alert("DOM注册点击div")
}
//3.使用addEventListener()方法注册事件
var div=document.querySelector("div")
//向div添加事件
div.addEventListener('click',()=>{
alert("Div 注册点击事件")
})
//移除div事件
div.removeEventListener("click",()=>{
alert("移除div事件")
})
<script>
<body>
鼠标事件
鼠标事件 | 触发条件 |
---|---|
onclick | 鼠标点击左键触发 |
onmouseover | 鼠标经过触发 |
onmouseout | 鼠标离开触发 |
onfocus |
获得鼠标焦点触发 |
onblur |
失去鼠标焦点触发 |
onmousemove | 鼠标移动触发 |
onmouseup | 鼠标弹起触发 |
onmousedown | 鼠标按下触发 |
禁止鼠标右键与鼠标选中
-
contextmenu
主要控制应该何时显示上下文菜单,主要用于程序员取消默认的上下文菜单 -
selectstart
禁止鼠标选中
<body>
<h1>我是一段不愿意分享的文字</h1>
<script>
// 1. contextmenu 我们可以禁用右键菜单
document.addEventListener('contextmenu', function(e) {
e.preventDefault(); // 阻止默认行为
})
// 2. 禁止选中文字 selectstart
document.addEventListener('selectstart', function(e) {
e.preventDefault();
})
</script>
</body>
鼠标事件对象 | 说明 |
---|---|
e.clientX |
返回鼠标相对于浏览器窗口可视区的
坐标 |
e.clientY |
返回鼠标相对于浏览器窗口可视区的
坐标 |
e.pageX |
返回鼠标相对于文档页面的
坐标 IE9+ 支持 |
e.pageY |
返回鼠标相对于文档页面的
坐标 IE9+ 支持 |
e.screenX | 返回鼠标相对于电脑屏幕的 X 坐标 |
e.screenY | 返回鼠标相对于电脑屏幕的Y 坐标 |
<body>
<script>
// 鼠标事件对象 MouseEvent
document.addEventListener('click', function(e) {
// 1. client 鼠标在可视区的x和y坐标
console.log('client---------------------');
console.log(e.clientX);
console.log(e.clientY);
// 2. page 鼠标在页面文档的x和y坐标
console.log('page---------------------');
console.log(e.pageX);
console.log(e.pageY);
// 3. screen 鼠标在电脑屏幕的x和y坐标
console.log('screen---------------------');
console.log(e.screenX);
console.log(e.screenY);
})
</script>
</body>
键盘事件
<script>
// 常用的键盘事件
//1. keyup 按键弹起的时候触发
document.addEventListener('keyup', function () {
console.log('我弹起了');
})
//3. keypress 按键按下的时候触发 不能识别功能键 比如 ctrl shift 左右箭头啊
document.addEventListener('keypress', function () {
console.log('我按下了press');
})
//2. keydown 按键按下的时候触发 能识别功能键 比如 ctrl shift 左右箭头啊
document.addEventListener('keydown', function () {
console.log('我按下了down');
})
// 4. 三个事件的执行顺序 keydown -- keypress -- keyup
// 5.利用Keycode返回的ASCII码值来判断用户按下的是哪个键
document.addEventListener("keyup", function (e) {
if (e.keyCode === 65) {
alert("你按下的是a键")
} else {
alert("你没有按下a键")
}
})
</script>
$('#keyword').keypress(function (key1) {
let key = key1.which; // e.which是按键的值
// 回车
if (key == 13) {
var keyword = $(this).val();
if (keyword && keyword != '') {
window.location.href = '/search?keyword=' + keyword;
}
}
});
键盘事件对象 属性 |
说明 |
---|---|
keyCode |
返回该 键 值的ASCII值 |
-
onkeydown
和
onkeyup
不区分字母大小写,
onkeypress
区分字母大小写。 - 在我们实际开发中,我们更多的使用keydown和keyup, 它能识别所有的键(包括功能键)
-
Keypress
不识别功能键,但是
keyCode
属性能区分大小写,返回不同的ASCII值
一般用 event.key,和 event.code
因为不同电脑,键盘的按键可能不一样
事件冒泡**
比如子元素和父元素都有点击事件,
点了子元素,然后做完会做父元素的点击事件
阻止事件冒泡:
-
event.stopPropagation()
但不会阻止默认事件==
阻止默认事件
- event.preventDefault()
<a href="http://www.baidu.com" @click="showInfo">click me</a>
showInfo(event) {
// 阻止默认行为
event.preventDefault();
alert("aa")
}
事件捕获**
先 father,再son
- document -> html -> body -> father -> son
<body>
<div class="father">
<div class="son">son盒子</div>
</div>
<script>
// dom 事件流 三个阶段
// 1. JS 代码中只能执行捕获或者冒泡其中的一个阶段。
// 2. onclick 和 attachEvent(ie) 只能得到冒泡阶段。
// 3. 捕获阶段 如果addEventListener 第三个参数是 true 那么则处于捕获阶段 document -> html -> body -> father -> son
var son = document.querySelector('.son');
son.addEventListener('click', function() {
alert('son');
}, true);
var father = document.querySelector('.father');
father.addEventListener('click', function() {
alert('father');
}, true);
</script>
</body>
先son,再father
- son -> father ->body -> html -> document
<body>
<div class="father">
<div class="son">son盒子</div>
</div>
<script>
// 4. 冒泡阶段 如果addEventListener 第三个参数是 false 或者 省略 那么则处于冒泡阶段 son -> father ->body -> html -> document
var son = document.querySelector('.son');
son.addEventListener('click', function() {
alert('son');
}, false);
var father = document.querySelector('.father');
father.addEventListener('click', function() {
alert('father');
}, false);
document.addEventListener('click', function() {
alert('document');
})
</script>
</body>
事件对象**
兼容性
- e = e || window.event;
eventTarget.addEventListener("click",fn)
function(event){
}
}
事件对象的属性和方法 | 说明 |
---|---|
e.target |
返回
事件的对象 标准 |
e.srcElement |
返回
事件的对象 非标准
|
e.type |
返回事件的
比如 click mouseover 不带 on |
e.canceIBubble |
该属性阻止冒泡 非标准
使用 |
e.returnValue |
该属性 阻止默认事件(默认行为)非标准
使用 比如不让链接跳转 |
e.preventDefault() | 该方法阻止默认事件(默认行为)标准 比如不让链接跳转 |
e.stopPropagation() | 标准的阻止事件冒泡 |
this与e.target**
通常情况下e.target 和 this 的指向是一致的
但是有一种情况不同,那就是在事件冒泡时(父子元素有相同事件,单击子元素,父元素的事件处理函数也会被触发执行)
这时候 this 指向的是元素,因为this使终指向的是事件绑定的元素
target 指向的是子元素 ,因为target使终指向的是事件触发的元素
<div>123</div>
<ul>
<li>abc</li>
<li>abc</li>
<li>abc</li>
</ul>
<script>
1. //target和this指向的一致
var div = document.querySelector('div');
div.addEventListener('click', function(e) {
// e.target 和 this指向的都是div
console.log(e.target);
console.log(this);
});
2. //在冒泡的情况下 target和this指向不同
var ul=document.querySelector("ul")
ul.addEventListener("click",function(e){
// 我们给ul 绑定了事件 那么this 就指向ul
console.log(this); // ul
// e.target 触发了事件的对象 我们点击的是li e.target 指向的就是li
console.log(e.target); // li
})
</script>
- this 是事件绑定的元素, 这个函数的调用者(绑定这个事件的元素)
- e.target 是事件触发的元素。
事件委托**
- 事件委托也称为事件代理,在 jQuery 里面称为事件委派
-
事件委托的原理
-
不是每个子节点单独设置事件监听器,而是事件监听器设置在其父节点上,然后利用冒泡原理影响设置每个子节点
-
<body>
<ul>
<li>知否知否,点我应有弹框在手!</li>
<li>知否知否,点我应有弹框在手!</li>
<li>知否知否,点我应有弹框在手!</li>
<li>知否知否,点我应有弹框在手!</li>
<li>知否知否,点我应有弹框在手!</li>
</ul>
<script>
// 事件委托的核心原理:给父节点添加侦听器, 利用事件冒泡影响每一个子节点
var ul = document.querySelector('ul');
ul.addEventListener('click', function(e) {
// alert('知否知否,点我应有弹框在手!');
// e.target 这个可以得到我们点击的对象
e.target.style.backgroundColor = 'pink';
// 点了谁,就让谁的style里面的backgroundColor颜色变为pink
})
</script>
</body>
以上案例:给 ul 注册点击事件,然后利用事件对象的 target 来找到当前点击的 li,因为点击 li,事件会冒泡到 ul 上, ul 有注册事件,就会触发事件监听器。
BOM
DOM
浏览器缓存
cdn缓存
js执行机制
单线程
ES6
ECMAScript 6.0 (2015)是JavaScript语言的下一代
标准
,弥补传统JavaScript语言的缺陷
完全支持ES6特性的浏览器比较少
- 但大部分浏览器都至少完整支持ES5
-
随着Node.js技术的出现,Babel等工具的使用可以使得ES6开发的程序可以
被“编译”为ES5版本的代码
,在任意浏览器上被运行,因此使用ES6才被大部分开发人员的接受
严格模式 ‘use strict’
https://developer.mozilla.org/en-US/
https://developer.mozilla.org/zh-CN/docs/learn
类
对象是一组无序的相关属性和方法的集合
类抽象了对象的公共部分,它泛指某一大类(class)
// 创建类
class name {
// class body
}
// 创建对象
var XX = new name();
<script>
// 1. 创建类 class 创建一个 明星类
class Star {
// constructor 构造器或者构造函数
constructor(uname, age) {
this.uname = uname;
this.age = age;
}
}
// 2. 利用类创建对象 new
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 20);
console.log(ldh);
console.log(zxy);
</script>
class Person {
constructor(name,age) {
// constructor 称为构造器或者构造函数
this.name = name;
this.age = age;
}
say() {
console.log(this.name + '你好');
}
}
var ldh = new Person('刘德华', 18);
ldh.say()
注意
: 方法之间不能加逗号分隔,同时方法不需要添加 function 关键字
继承
super
可以调用父类的构造函数,也可以调用父类的普通函数
// 父类
class Father {
}
// 子类继承父类
class Son extends Father {
}
<script>
// 父类有加法方法
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
}
// 子类继承父类加法方法 同时 扩展减法方法
class Son extends Father {
constructor(x, y) {
// 利用super 调用父类的构造函数
// super 必须在子类this之前调用
super(x, y);
this.x = x;
this.y = y;
}
subtract() {
console.log(this.x - this.y);
}
}
var son = new Son(5, 3);
son.subtract();
son.sum();
</script>
调用父类的构造函数
// 父类
class Person {
constructor(surname){
this.surname = surname;
}
}
// 子类继承父类
class Student entends Person {
constructor(surname,firstname) {
super(surname); //调用父类的 constructor(surname)
this.firstname = firstname; //定义子类独有的属性
}
}
注意:子类在构造函数中使用super,必须放到this前面(必须先调用父类的构造方法,在使用子类构造方法)
// 父类
class Father {
constructor(surname){
this.surname = surname;
}
saySurname() {
console.log('我的姓是' + this.surname);
}
}
// 子类继承父类
class Son entends Father {
constructor(surname,firstname) {
super(surname); //调用父类的 constructor(surname)
this.firstname = firstname; //定义子类独有的属性
}
sayFirstname() {
console.log('我的名字是:' + this.firstname);
}
}
var damao = new Son('刘','德华');
damao.saySurname();
damao.sayFirstname();
调用父类的普通函数
class Father {
say() {
return '我是爸爸';
}
}
class Son extends Father {
say(){
// super.say() super调用父类的方法
return super.say() + '的儿子';
}
}
var damao = new Son();
console.log(damao.say());
多个方法之间不需要添加逗号分隔
继承中属性和方法的查找原则:就近原则,先看子类,再看父类
三个注意点
- 在ES6中类没有变量提升,所以必须先定义类,才能通过类实例化对象
-
类里面的共有属性和方法一定要加
this
使用 -
类里面的this指向:
-
constructor 里面的
this
指向实例对象 -
方法里面的
this
指向这个方法的调用者
-
constructor 里面的
<body>
<button>点击</button>
<script>
var that;
var _that;
class Star {
constructor(uname, age) {
// constructor 里面的this 指向的是 创建的实例对象
that = this;
this.uname = uname;
this.age = age;
// this.sing();
this.btn = document.querySelector('button');
this.btn.onclick = this.sing;
}
sing() {
// 这个sing方法里面的this 指向的是 btn 这个按钮,因为这个按钮调用了这个函数
console.log(that.uname);
// that里面存储的是constructor里面的this
}
dance() {
// 这个dance里面的this 指向的是实例对象 ldh 因为ldh 调用了这个函数
_that = this;
console.log(this);
}
}
var ldh = new Star('刘德华');
console.log(that === ldh);
ldh.dance();
console.log(_that === ldh);
// 1. 在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象
// 2. 类里面的共有的属性和方法一定要加this使用.
</script>
</body>
静态成员和实例成员
// 构造函数中的属性和方法我们称为成员,成员可以添加
function Star(uname,age) {
this.uname = uname;
this.age = age;
this.sing = function() {
console.log('我会唱歌');
}
}
var ldh = new Star('刘德华',18);
// 实例成员就是构造函数内部通过this添加的成员 uname age sing 就是实例成员
// 实例成员只能通过实例化的对象来访问
ldh.sing();
Star.uname; // undefined 不可以通过构造函数来访问实例成员
// 静态成员就是在构造函数本身上添加的成员 sex 就是静态成员
// 静态成员只能通过构造函数来访问
Star.sex = '男';
Star.sex;
ldh.sex; // undefined 不能通过对象来访问
构造函数方法很好用,但是存在
浪费内存的问题
原型
构造函数通过原型分配的函数是所有对象所共享的,这样就
解决了内存浪费问题
JavaScript 规定,每一个构造函数都有一个prototype属性,指向另一个对象,注意
这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有
我们可以把那些不变的方法,直接定义在prototype 对象上,这样所有对象的实例就可以共享这些方法
- 一般情况下,我们的公共属性定义到构造函数里面, 公共的方法我们放到原型对象身上
原型是什么?
-
一个对象,我们也称为
prototype
为原型对象
原型的作用是什么?
-
共享方法
对象原型 __ proto __
对象都会有一个属性
proto
指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数prototype 原型对象的属性和方法,就是因为对象有_proto_原型的存在。
_proto_对象原型和原型对象 prototype 是等价的
_proto_对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype
-
Star.prototype 和 ldh.
proto
指向相同
<body>
<script>
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function() {
console.log('我会唱歌');
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
ldh.sing();
console.log(ldh);
// 对象身上系统自己添加一个 __proto__ 指向我们构造函数的原型对象 prototype
console.log(ldh.__proto__ === Star.prototype);
// 方法的查找规则: 首先先看ldh 对象身上是否有 sing 方法,如果有就执行这个对象上的sing
// 如果没有sing 这个方法,因为有 __proto__ 的存在,就去构造函数原型对象prototype身上去查找sing这个方法
</script>
</body>
constructor构造函数
对象原型(__ proto __) 和构造函数(prototype)原型对象 里面都有一个属性 constructor 属性, constructor 我们称为构造函数,因为它指回构造函数本身。
constructor主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数
一般情况下,
对象的方法都在构造函数(prototype)的原型对象中设置
如果有多个对象的方法,我们可以给原型对象prototype采取对象形式赋值,但是这样会覆盖构造函数原型对象原来的内容,这样修改后的原型对象constructor就不再指向当前构造函数了。此时,
我们可以在修改后的原型对象中,添加一个constructor指向原来的构造函数
<body>
<script>
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// 很多情况下,我们需要手动的利用constructor 这个属性指回 原来的构造函数
// Star.prototype.sing = function() {
// console.log('我会唱歌');
// };
// Star.prototype.movie = function() {
// console.log('我会演电影');
// }
Star.prototype = {
// 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
constructor: Star,
sing: function() {
console.log('我会唱歌');
},
movie: function() {
console.log('我会演电影');
}
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
</script>
</body>
原型链
查找规则
- 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性
- 如果没有就查找它的原型(也就是_proto_指向的prototype原型对象)
- 如果还没有就查找原型对象的原型(Object的原型对象)
- 依次类推一直找到Object为止(null)
- __ proto __对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
<body>
<script>
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function() {
console.log('我会唱歌');
}
var ldh = new Star('刘德华', 18);
// 1. 只要是对象就有__proto__ 原型, 指向原型对象
console.log(Star.prototype);
console.log(Star.prototype.__proto__ === Object.prototype);
// 2.我们Star原型对象里面的__proto__原型指向的是 Object.prototype
console.log(Object.prototype.__proto__);
// 3. 我们Object.prototype原型对象里面的__proto__原型 指向为 null
</script>
</body>
原型对象this指向
-
构造函数中的 this指向我们的实例对象
-
原型对象里面放的是方法,
这个方法里面的this指向的是这个方法的调用者
,也就是这个实例对象
<body>
<script>
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
var that;
Star.prototype.sing = function() {
console.log('我会唱歌');
that = this;
}
var ldh = new Star('刘德华', 18);
// 1. 在构造函数中,里面this指向的是对象实例 ldh
ldh.sing();
console.log(that === ldh);
// 2.原型对象函数里面的this 指向的是 实例对象 ldh
</script>
</body>
扩展内置对象
- 可以通过原型对象,对原来的内置对象进行扩展自定义的方法
- 比如给数组增加自定义求偶数和的功能
<body>
<script>
// 原型对象的应用 扩展内置对象方法
Array.prototype.sum = function() {
var sum = 0;
for (var i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
};
var arr = [1, 2, 3];
console.log(arr.sum());
console.log(Array.prototype);
var arr1 = new Array(11, 22, 33);
console.log(arr1.sum());
</script>
</body>
注意:数组和字符串内置对象不能给原型对象覆盖操作
Array.prototype = {}
,只能是
Array.prototype.xxx = function(){}
的方式
call()
调用这个函数,并且修改函数运行时的 this 指向
<body>
<script>
// call 方法
function fn(x, y) {
console.log('我希望我的希望有希望');
console.log(this); // Object{...}
console.log(x + y); // 3
}
var o = {
name: 'andy'
};
// fn();
// 1. call() 可以调用函数
// fn.call();
// 2. call() 可以改变这个函数的this指向 此时这个函数的this 就指向了o这个对象
fn.call(o, 1, 2);
</script>
</body>
原型链继承
一般情况下,对象的方法都在构造函数的原型对象中设置,通过构造函数无法继承父类方法
核心原理:
将子类所共享的方法提取出来,让
子类的 prototype 原型对象 = new 父类()
本质: 子类原型对象等于是实例化父类,因为父类实例化之后另外开辟空间,就不会影响原来父类原型对象
将子类的constructor重新指向子类的构造函数
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
function SubType() {
this.subproperty = false;
}
// 继承SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () { //注意 不能通过对象字面量的方式添加新方法,否则上一行无效
return this.subproperty;
};
let instance = new SubType();
console.log(instance.getSuperValue()); // true
特点:
- 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
- 父类新增原型方法/原型属性,子类都能访问到
- 简单,易于实现
缺点:
-
要想为子类新增属性和方法,必须要在
new SuperType()
这样的语句之后执行,不能放到构造器中 - 无法实现多继承
- 来自原型对象的所有属性被所有实例共享(来自原型对象的引用属性是所有实例共享的)(详细请看附录代码:)
- 创建子类实例时,无法向父类构造函数传参
构造函数继承
-
核心原理: 通过
call()
把父类型的 this 指向子类型的 this,这样就可以实现子类型继承父类型的属性
<body>
<script>
// 借用父构造函数继承属性
// 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age);
this.score = score;
}
var son = new Son('刘德华', 18, 100);
console.log(son);
</script>
</body>
特点:
-
子类实例
共享父类引用属性
的问题 -
创建子类实例时,
可以向父类传递参数
-
可以实现
多继承
(call多个父类对象)
缺点:
- 实例并不是父类的实例,只是子类的实例
- 只能继承父类的实例属性和方法,不能继承原型属性/方法
-
无法实现函数复用,
每个子类都有父类实例函数的副本,影响性能
组合继承
function Parent(name) {
this.name = name;
this.newArr = ["red", "blue", "green"];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name) {
Parent.call(this, name);
this.age = 26;
}
Child.prototype = new Parent();
//重写Child.prototype的constructor属性,使其执行自己的构造函数Child
Child.prototype.constructor = Child;
Child.prototype.sayAge = function() {
console.log(this.age);
};
var Person1 = new Child("heyushuo");
console.log(Person1);
Person1.newArr.push("yellow");
console.log(Person.newArr); //["red", "blue", "green","yellow"]
Person.sayName(); //heyushuo
var Person2 = new Child("kebi");
console.log(Person2.newArr); //["red", "blue", "green"]
Person.sayName(); //kebi
重点:
结合了两种模式的优点,
传参和复用
特点:
- 可以继承父类原型上的属性,可以传参,可复用。
- 每个新实例引入的构造函数属性是私有的。
缺点:
调用了两次父类构造函数(耗内存)
,创建的实例和原型上存在两份相同的属性即(name 和 newArr);
原型式继承
不自定义类型的情况下,临时创建一个构造函数,借助已有的对象作为临时构造函数的原型,然后在此基础实例化对象,并返回。本质上是object()对传入其中的对象执行了一次浅复制。
function object(o){
function F(){}
F.prototype = o;
return new F();
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
复制代码
重点:
用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。==object.create()==就是这个原理
特点:
类似于复制一个对象,用函数来包装
缺点:
- 所有实例都会继承原型上的属性
- 无法实现复用(新实例属性都是后面添加的)
-
效率较低,内存占用高(因为
要拷贝父类的属性
)
寄生式继承
与原型式继承比较接近的一种继承方式是寄生式继承,类似于寄生构造函数和工厂模式:创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。寄生式继承同样适合主要关注对象,而不在乎类型和构造函数的场景
function object(person) {
function F() {}
F.prototype = person
return new F()
}
function createAnother(original){
let clone = object(original); // 通过调用函数创建一个新对象
clone.sayHi = function() { // 以某种方式增强这个对象
console.log("hi");
};
return clone; // 返回这个对象
}
复制代码
重点:
就是给原型式继承外面套了个壳子。
优点:
没有创建自定义类型,因为只是套了个壳子返回对象(这个),这个函数顺理成章就成了创建的新对象。
缺点:
没用到原型,无法复用。
寄生组合式继承(常用)
寄生:在函数内返回对象然后调用
组合:1、函数的原型等于另一个实例。2、在函数中用apply或者call引入另一个构造函数,可传参
最常用的继承方式,也是最佳的,组合继承会调用两次父类构造函数,存在效率问题。其实本质上子类原型最终是要包含父类对象的所有实例属性,子类构造函数只要在执行时重写自己的原型就行了。基本思路是不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个副本。说到底就是使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型。
//核心代码
function object(person) {
function F(params) {}
F.prototype = person
return new F()
}
function inheritPrototype(SubType,SuperType) {
let prototype = object(SuperType.prototype) //生成一个父类原型的副本
//重写这个实例的constructor
prototype.constructor = SubType
//将这个对象副本赋值给 子类的原型
SubType.prototype = prototype
}
function SuperType(name) {
this.name = name;
this.colors = ["red","blue","green"];
}
SuperType.prototype.sayName = function() {
console.log(this.name);
};
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
//调用inheritPrototype函数给子类原型赋值,修复了组合继承的问题
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function() {
console.log(this.age);
};
复制代码
重点:修复了组合继承的问题
类的本质
- class 本质还是 function
-
类的所有方法都定义在类的
prototype
属性上 -
类创建的实例,里面也有
_proto_
指向类的
prototype
原型对象
let
var问题
var a = []
for(var i = 0; i < 3; i++) {
a[i] = function() {
console.log(i)
}
}
a[0]()
a[1]()
a[2]()
// 都是输出3
// 解决方案一:使用let
var a = []
for(let i = 0; i < 3; i++) {
a[i] = function() {
console.log(i)
}
}
a[0]()
a[1]()
a[2]()
// 输出 0 1 2
// 解决方案二:使用闭包
var a = []
for(var i = 0; i < 3; i++) {
(function (i) {
a[i] = function() {
console.log(i)
}
}
)(i) // 这里的i是 var i= 传进入的,然后这个做完之后 再i++,
}
a[0]()
a[1]()
a[2]()
// 闭包的原理
var name = 'why'
function abc(name) {
console.log(name)
}
name = 'who'
abc('aa') // 此时在调用abc之前,不管name是否被修改了,都不会影响abc('aa')的结果
// ---------------
var不受块作用域的限制,哪里都能用
let无变量提升
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>let</title>
</head>
<body>
<script>
document.write(v);
var v = 3;
document.write(v);
</script>
</body>
</html>
对比
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>let</title>
</head>
<body>
<script>
document.write(l);
let l = 3;
document.write(l);
</script>
</body>
</html>
作用域
特定块级作用域有效
-
window ,a
-
块:i
-
脚本:v ,t
意思就是 var的相当于全局,除了变量提升外,下面的i只能在特定位置即for里面。v和t只能在当前脚本内生效
var a = 1
const v = 1
let t = 1
for(let i = 0; i < 10; i ++) {
console.log(i)
}
var a = []
for(var i = 0; i < 10; i ++) {
a[i] = function() {
console.log(i)
}
}
a[6]() // 输出10,循环之后 var取值为最后一次赋值
var a = []
for(let i = 0; i < 10; i ++) {
a[i] = function() {
console.log(i)
}
}
a[6]() // 输出6
// 报错, 超出作用域
const arr = [1, 2, 3, 4]
const num = 0
for(let i = 0; i< arr.length; i ++) {
if(arr[i] == num) {
break;
}
}
console.log(i)
//
const arr = [1, 2, 3, 4]
const num = 0
for(var i = 0; i< arr.length; i ++) {
if(arr[i] == num) {
break;
}
}
console.log(i) // 4
暂时性死区
// 暂时性死区, 全局变量到局部变量之间 是不可以用变量的
let tmp = 3
if (true) {
tmp = 4 // 报错
let tmp = 123
console.log(tmp)
}
不可重复声明
const
const声明一个只读的常量:一旦声明,常量的值就不能改变
对象或数组
的内容可以改变,但是
引用
不能改变
const a = {name: "张三"}
a.name = "lisi"
console.log(a.name) // lisi,可以改变值
const a = 2
let b = a // 新的空间
b = 4
console.log(a) // 2
const一旦声明变量,就
必须初始化
,作用域和let一样,不存在变量提申,同样存在暂时性死区
解构赋值 ***
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)
数组
let [a, b, c] = [1, 2, 3]
console.log(a + " " + b + " " + c ) // 1 2 3
let [a, b, c] = [1]
console.log(a + " " + b + " " + c ) // 1 undefined undefined,解构赋值不成功,就等于undefined
对象
名字必须相对应,才能取到正确的值
let {foo, bar:zzz} = {foo: 'aaa', bar: "bbb"};
console.log(foo, zzz)
// 左边的zzz表示解构赋值之后的变量名称改成zzz
const node = {
loc: {
start: {line: 1,column: 5}
}};
let { loc: { start: { line }} } = node;
对象的解构赋值,将现有
对象的方法
,赋值到某个变量
const { log } = console
log('hello') // hello
字符串
const [a, b, c, d, e] = 'hello';
// a = h, b = e, c = l, d = l, e = o
let {length:len} = 'hello';
console.log(len) // 5
let {toString:s1} = 123;
let {toString:s2} = true;
console.log(typeof(s1)) // 返回的是一个函数
console.log((s1))
等号右边的 不是对象时,就将其转为
对象
交换数据
let x = 1;
let y = 2;
[x,y] = [y,x]
console.log(x, y) // 2, 1
函数
- 返回多个值
function e() {
return [1, 2, 3]
}
let [a, b, b] = e();
- 传入多个值
function fun([x, y, z]) {
console.log(x, y, z)
}
fun([1, 2, 3]) // 不常用
function fun({x, y, z}) {
console.log(x, y, z)
}
fun({x:1, y:2, z:3})// 用这个
字符串长度
使用[]包起来,因为出现unicode编码的时候,
console.log([..."x\uD83D\uDE80y"].length) // 3
字符串扩展
遍历
for(let c of "hello") {
console.log(c)
}
模板字符串
使用反引号,可当作普通字符串使用,或者在字符串中
嵌入变量或者任何JavaScript表达式
如果在模板字符串中需要使用反引号,则前面要用反斜杠转义
let str = `hello`
console.log(typeof(str)) // string
let str2 = `hell
o`;
console.log(str2) // 保留了 换行
function add(x, y) {
return x + y
}
let name = "zhangsan"
let age = 19
let str = `hello, I'm ${name}, I'm ${age}, ${add(age,2)}`;
console.log(str) // 插入变量,调用函数
// 生成html
window.onload = function() {
const json = {
username: "zhangsan",
registered: 5,
vip: false,
}
let html = `
<ul>
<li>用户名:${json.username}</li>
<li>注册念想${json.registered}</li>
<li>用户名:${json.vip ? "是" : "false"}</li>
</ul>
`
const div = document.getElementById("user-info")
div.innerHTML = html
}
// 方式二
function getUserInfo() {
const json = {
username: "zhangsan",
registered: 5,
vip: false,
}
let html = `
<ul>
<li>用户名:${json.username}</li>
<li>注册念想${json.registered}</li>
<li>用户名:${json.vip ? "是" : "false"}</li>
</ul>
`
const div = document.getElementById("user-info")
div.innerHTML = html
// return undefined,不写返回值默认是返回undefined的
}
window.onload = getUserInfo
// 当页面的 body加载完成之后
// 函数 没有括号,相当于将getUserInfo给 onload事件,没有返回值的函数赋值的时候不需要加上括号
字符串函数
和java类似
includes(substr)表示是否找到了参数字符串
startsWith(substr)表示参数字符串是否在原字符串的头部
endsWith(substr)表示参数字符串是否在原字符串的尾部
matchAll()方法返回一个正则表达式在当前字符串的所有匹配
str3.trimEnd();
str3.trimStart();
const str = "aaa"
console.log(str.repeat(0)) // 空字符串
console.log(str.repeat(2.3)) // 向下取整 2
let str2 = "abbbb"
str2 = str2.replace("b", "c")
// str2 = str2.replaceAll("b", "c")
console.log(str2)
函数扩展
参数默认值
function fetch(url, {method="GET", data={}}) {
console.log(method, data);
}
fetch("/api/userinfo", {method:"POST"})
fetch("/api/userinfo", {}) // ******
fetch("/api/userinfo") // 会报错
// 改进
function fetch(url, {method="GET", data={}} = {}) {
console.log(method);
console.log(data);
}
fetch("/api/userinfo")
// JSON -> string,string -> JSON
function fetch(url, {method="GET", data={}}) {
console.log(method);
let str = JSON.stringify(data); // JSON -> string
let json = JSON.parse(str) // string -> JSON
console.log(json.id);
}
fetch("/api/userinfo", {method:"POST", data:{id:3}})
区别
function m1({x = 0, y = 0} = {}) {
console.log(x, y)
}
function m2({x, y} = {x : 0, y : 0}) {
console.log(x, y)
}
m1({x: 3}) // 3 0
m2({x: 3}) // 3 undefined
m1({a: 1}) // 0 0
m2({b: 1}) // 不对应,解构赋值不成功则返回 undefined undefined
强制要有参数
只有
传入undefined 或者 不传入参数
才会报错
function throwIfMissing() {
throw new Error("Missing parameter")
}
function add(a = throwIfMissing()) {
console.log(a)
}
// add() // 异常
// add(undefined) // 异常
add(2)
add(null)
add(false)
函数属性
function第一次赋值给了谁 ,那么name就是谁
let f = function() {
}
let g = f
console.log(g.name) // f
console.log(g.length) // 0 参数个数
rest参数
只要能用of遍历的,都可以用 …rest展开
function add(a, b, rest) {
if(Array.isArray(rest) && rest.length > 0) {
console.log(rest)
}
}
add(1, 2, [3, 4]) // 要传数组
function add(a, b, ...rest) {
if(Array.isArray(rest) && rest.length > 0) {
console.log(rest)
}
}
add(1, 2, 3, 4)
console.log(add.length) // 2
惰性求值
// 惰性求值
function add(a, b = x + 1) {
console.log(b)
}
let x = 99
add(5) // 100
拼接URL
// 方式一
url 必须传,不传递 报错
method 默认get,
不传为{} ,data,json对象
get的 将参数拼接
function throwIfMissing() {
throw new Error("Missing parameter")
}
function m(url = throwIfMissing(), {method="GET", data={}} = {}) {
if(method === "GET") {
let str = JSON.stringify(data)
str = str.replace("{","?")
str = str.replaceAll("\"", "")
str = str.replaceAll(":", "=")
str = str.replaceAll(",","&")
str = str.replace("}","")
console.log(url + str)
} else if(method === "POST") {
console.log(url)
}
}
m("http://localhost:8080/index", {data:{id:1,id2:2}});
m("http://localhost:8080/index");
m("http://localhost:8080/index", {method:"POST"});
// 方式二
function request(url = throwIfMissing(), {method="GET", data={}} = {}) {
if(method.toUpperCase() === "GET") {
let params = ""
let index = 0
for(let key in data) {
let value = data[key]
params += `${(index > 0 ? "&" : "")}${key}=${value}`
index ++;
}
if(params !== "") {
url +="?" + params
}
}
console.log(url);
}
request("/fdsf",{data:{id:1,id2:2}})
箭头函数
简单例子:
const add = (
const add3 = (a, b) => {
let result = a + b;
if(result > 10) {
result = 10
}
return result;
}
console.log(add3(5, 9))
// 返回值
const add4 = (a, b) => Math.min(a + b, 10)
// 加了大括号没写返回值相当于 return undefined
const add4 = (a, b) => {Math.min(a + b, 10)}
// 等同于
const add4 = (a, b) => {Math.min(a + b, 10);return undefined}
// 返回对象,需要加上大括号
const add5 = (a, b) => ({name: "张三"})
// 无返回值
const add6 = (a, b) => void a + b
map filter reduce
非 in place
方法
let a = [1,2,3,4]
// 过滤
let b = a.filter((e) => e % 2 == 1)
// 映射
console.log(a.filter((e) => e % 2 == 1).map((e) => e ** 2)) // 1 9
// 累加
console.log(a.filter((e) => e % 2 == 1).reduce((acc,e) => acc + e))
箭头函数的注意点
-
函数体内的this对象,就是
定义时所在的对象
,即定义该函数时所在的作用域指向的对象 -
不可当作构造函数,即
不可以使用new命令
- 不可用arguments对象,该对象在函数体内不存在。如果要用,可用rest 参数代替
- 不可用yield命令,因此箭头函数不能用作 Generator 函数
function bar() {
setTimeout(() => console.log(this), 1000)
}
bar()
var A = {
name: "A",
say: () => {
console.log(this.name)
// 这里的this是window,输出 window
}
}
A.
箭头函数的this 就是往外一层找,看时候有this,一直往外找 直到找到
数组扩展
遍历
- currentValue : 数组当前项的值
- index: 数组当前项的索引
- arr: 数组对象本身
<body>
<script>
// forEach 迭代(遍历) 数组
var arr = [1, 2, 3];
var sum = 0;
arr.forEach(function(value, index, array) {
console.log('每个数组元素' + value);
console.log('每个数组元素的索引号' + index);
console.log('数组本身' + array);
sum += value;
})
console.log(sum);
</script>
</body>
过滤
<body>
<script>
// filter 筛选数组
var arr = [12, 66, 4, 88, 3, 7];
var newArr = arr.filter(function(value, index) {
// return value >= 20;
return value % 2 === 0;
});
console.log(newArr);
</script>
</body>
some
<body>
<script>
// some 查找数组中是否有满足条件的元素
var arr1 = ['red', 'pink', 'blue'];
var flag1 = arr1.some(function(value) {
return value == 'pink';
});
console.log(flag1);
// 1. filter 也是查找满足条件的元素 返回的是一个数组 而且是把所有满足条件的元素返回回来
// 2. some 也是查找满足条件的元素是否存在 返回的是一个布尔值 如果查找到第一个满足条件的元素就终止循环
</script>
</body>
数组索引
//返回数组元素索引号方法 indexOf(数组元素) 作用就是返回该数组元素的索引号
//它只发返回第一个满足条件的索引号
//如果找不到元素,则返回-1
var arr = ['red','green','blue','pink','blue'];
console.log(arr.indexOf('blue')); // 2
console.log(arr.lastIndexOf('blue')); // 4
<body>
<script>
// some 查找数组中是否有满足条件的元素
var arr1 = ['red', 'pink', 'blue'];
var flag1 = arr1.some(function(value) {
return value == 'pink';
});
console.log(flag1);
// 1. filter 也是查找满足条件的元素 返回的是一个数组 而且是把所有满足条件的元素返回回来
// 2. some 也是查找满足条件的元素是否存在 返回的是一个布尔值 如果查找到第一个满足条件的元素就终止循环
</script>
</body>
数组转字符串
// 1.toString() 将我们的数组转换为字符串
var arr = [1, 2, 3];
console.log(arr.toString()); // 1,2,3
// 2.join('分隔符')
var arr1 = ['green', 'blue', 'red'];
console.log(arr1.join()); // 不写默认用逗号分割
console.log(arr1.join('-')); // green-blue-red
console.log(arr1.join('&')); // green&blue&red
复制数组
// 1.
let a = [1, 2, 3]
let b = [...a]
// 2.
let [...a2] = a;
合并数组
let arr = [1, 2, 3]
let arr2 = [1, 2, 3]
let arr3 = [1, 2, 3]
let newArr = [...arr, ...arr2, ...arr3]
console.log(newArr) // 1 2 3 1 2 3 1 2 3
与解构赋值结合
const [first, ...rest] = [1, 2, 3, 4, 5];
// first: 1
// rest: [2, 3, 4, 5]
const [first, ...rest] = [];
// first: undefined
// rest: []
const [first, ...rest] = ["foo"];
// first: "foo"
// rest: []
列表变为数组
function fun(...nums) {
console.log(Array.isArray(nums));
}
fun(1, 2, 3) // true
其他方法
方法名 | 说明 | 返回值 |
---|---|---|
concat() | 连接两个或多个数组 不影响原数组 | 返回一个新的数组 |
slice() | 数组截取slice(begin,end) | 返回被截取项目的新数组 |
splice() | 数组删除splice(第几个开始要删除的个数) | 返回被删除项目的新数组,这个会影响原数组 |
对象方法
<body>
<script>
// 用于获取对象自身所有的属性
var obj = {
id: 1,
pname: '小米',
price: 1999,
num: 2000
};
var arr = Object.keys(obj);
console.log(arr);
arr.forEach(function(value) {
console.log(value);
// id
// pname
// price
// num
})
</script>
</body>
Object.defineProperty()
- obj : 目标对象
- prop : 需定义或修改的属性的名字
- descriptor : 目标属性所拥有的特性
<body>
<script>
// Object.defineProperty() 定义新属性或修改原有的属性
var obj = {
id: 1,
pname: '小米',
price: 1999
};
// 1. 以前的对象添加和修改属性的方式
// obj.num = 1000;
// obj.price = 99;
// console.log(obj);
// 2. Object.defineProperty() 定义新属性或修改原有的属性
Object.defineProperty(obj, 'num', {
value: 1000,
enumerable: true
});
console.log(obj);
Object.defineProperty(obj, 'price', {
value: 9.9
});
console.log(obj);
Object.defineProperty(obj, 'id', {
// 如果值为false 不允许修改这个属性值 默认值也是false
writable: false,
});
obj.id = 2;
console.log(obj);
Object.defineProperty(obj, 'address', {
value: '中国山东蓝翔技校xx单元',
// 如果只为false 不允许修改这个属性值 默认值也是false
writable: false,
// enumerable 如果值为false 则不允许遍历, 默认的值是 false
enumerable: false,
// configurable 如果为false 则不允许删除这个属性 不允许在修改第三个参数里面的特性 默认为false
configurable: false
});
console.log(obj);
console.log(Object.keys(obj));
delete obj.address;
console.log(obj);
delete obj.pname;
console.log(obj);
Object.defineProperty(obj, 'address', {
value: '中国山东蓝翔技校xx单元',
// 如果值为false 不允许修改这个属性值 默认值也是false
writable: true,
// enumerable 如果值为false 则不允许遍历, 默认的值是 false
enumerable: true,
// configurable 如果为false 则不允许删除这个属性 默认为false
configurable: true
});
console.log(obj.address);
</script>
</body>
第三个参数 descriptor 说明:以对象形式{ }书写
value:设置属性的值,默认为undefined
writeable: 值是否可以重写 true | false 默认为false
enumerable: 目标属性是否可以被枚举 true | false 默认为false
configurable: 目标属性是否可以被删除或是否可以再次修改特性 true | false 默认为false
函数进阶**
Set
用的少
操作
- add(value):添加某个值,返回 Set 结构本身
- delete(value):删除某个值,返回一个布尔值,表示删除是否成功
- has(value):返回一个布尔值,表示该值是否为Set的成员
- clear():清除所有成员,没有返回值
遍历
- keys() 返回键名的遍历器
- values() 返回键值的遍历器
- entries() 返回键值对的遍历器
- forEach() 使用回调函数遍历每个成员
const s = new Set()
let a = [2,2,2,3,3,3,4]
for(let i of a) {
s.add(i)
}
console.log(s) // 2 3 4
console.log(s.delete(5)) // false
console.log(s.has(2)) // true 哈希查找
console.log(s.clear())
// 遍历
for(let v of s.keys()) {
console.log(v)
}
// 遍历
let entries = s.entries()
for(let k of entries) {
console.log(k)
}
Map
-
键可以为任意类型
-
只有对同一个对象的引用,Map 结构才将其视为同一个键。Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键
-
set(key, value)
-
get(key)
-
has(key)
-
delete(key)
-
clear()
const m1 = new Map()
m1.set("name", "张三")
console.log(m1)
console.log(m1.get("name"))
// key只能是String
// 如果不是String,会自动转成String
console.log("****")
const m = new Map()
let o = {}
o["name"] = "lisi"
console.log(o) // {name: "lisi"}
console.log(o["name"]); // lisi
m.set(o, "fdsfsd")
console.log(m) // {Object => "fdsfsd"}
console.log(m.get(o)); // fdsfsd
console.log("******")
let p = {id:3}
o[p] = "zhangsan"
console.log(o[p]); // zhangsan
console.log(o); // {name: "lisi", [object Object]: "zhangsan"}
let p1 = {id:4}
o[p1] = "lisi"
console.log(o); // {name: "lisi", [object Object]: "lisi"}
console.log(o[p1]); // lisi
遍历 和set一样
map转为数组
const myMap = new Map()
.set(true, 7)
.set({foo: 3}, ['abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
数组转为map
new Map([
[true, 7],
[{foo: 3}, ['abc']]
])
// Map {
// true => 7,
// Object {foo: 3} => ['abc']
// }
map转为对象
如果所有 Map 的键都是字符串,它可以无损地转为对象。
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
const myMap = new Map()
.set('yes', true)
.set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }
- 如果有非字符串的键名,那么这个键名会被转成字符串,再作为对象的键名。
对象转为map
对象转为 Map 可以通过Object.entries()。
let obj = {"a":1, "b":2};
let map = new Map(Object.entries(obj));
此外,也可以自己实现一个转换函数。
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
objToStrMap({yes: true, no: false})
// Map {"yes" => true, "no" => false}
map转为JSON
一种情况是,Map 的键名都是字符串,这时可以选择转为对象 JSON。
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
}
let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'
另一种情况是,Map 的键名有非字符串,这时可以选择转为数组 JSON。
function mapToArrayJson(map) {
return JSON.stringify([...map]);
}
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'
json转为map
JSON 转为 Map,正常情况下,所有键名都是字符串。
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}
但是,有一种特殊情况,整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为 Map。这往往是 Map 转为数组 JSON 的逆操作。
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}
Promise
异步编程
, promise是对异步函数的封装
在JavaScript的世界中,所有的代码都是
单线程
执行的
-
由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行
-
例如,我们有一个函数test,它接受一个参数,然后判断这个参数是否小于1。
-
但不幸的是,它作出这个判断需要消耗很长的时间,浏览器不可能等待这个结果。
if(test(3)) { // do something } else { // do otherthing }
因此,JavaScript使用了异步的概念,使用回调来处理返回结果:
test(3, testSuccess, testFail); function testSuccess(){ } function testFail(){ } // 写法还有很多
不够直观,而且可能存在
回调地狱
,而Promise可以优雅的解决这些问题
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,
使得控制异步操作更加容易
var p = Promise(test);
p.then(testSuccess).catch(testFail);
function testSuccess(){
}
function testFail(){
}
// 术语
success:resolve
fail:reject
Promise对象代表
一个异步操作
- pending(进行中)
- fulfilled(已成功)
- rejected(已失败)
Promise对象的状态改变,只有两种可能
- 从pending变为fulfilled
- 从pending变为rejected
基本使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Promise</title>
</head>
<body>
<div id="test-promise-log"></div>
<script>
let logging = document.getElementById('test-promise-log');
// 输出log到页面:
function log(s) {
let p = document.createElement('p');
p.innerHTML = s;
logging.appendChild(p);
}
// 创建promise的时候 里面的异步操作就已经开始执行了
let p = new Promise(function (resolve, reject) {
let timeOut = Math.random() * 2;
setTimeout(function () {
if (timeOut < 1) {
resolve('200 OK');
}
else {
reject('timeout in ' + timeOut + ' seconds.');
}
}, timeOut * 1000);
});
log('about to test promise');
p.then(function (r) {
log('Done: ' + r);
}).catch(function (reason) {
log('Failed: ' + reason);
});
</script>
</body>
</html>
回调地狱
<script>
new Promise((resolve, reject) => {
setTimeout(() => {
console.log("hello world")
console.log("hello world")
console.log("hello world")
console.log("hello world")
console.log("hello world")
setTimeout(() => {
console.log("hello world")
console.log("hello world")
console.log("hello world")
console.log("hello world")
console.log("hello world")
setTimeout( () => {
console.log("hello world")
console.log("hello world")
console.log("hello world")
console.log("hello world")
console.log("hello world")
}, 1000)
}, 1000)
}, 1000)
})
</script>
解决方案
<script>
new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, 1000)
}).then(() => {
console.log("hello world")
console.log("hello world")
console.log("hello world")
console.log("hello world")
console.log("hello world")
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, 1000)
}).then(() => {
console.log("hello world")
console.log("hello world")
console.log("hello world")
console.log("hello world")
console.log("hello world")
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, 1000)
}).then(() => {
console.log("hello world")
console.log("hello world")
console.log("hello world")
console.log("hello world")
console.log("hello world")
})
})
})
</script>
promise缺点
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部
all方法
两个异步请求都返回结果时,再进行下一步操作
<script>
// 可迭代对象:比如数组
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p1')
}, 1000)
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p2')
}, 3000)
})
]).then(results => {
console.log(results[0])
console.log(results[1])
})
</script>
ajax XmlHttpRequest
不能跨域
async
为什么要使用
async
当然
Promise
也不是最终完美的异步解决方案,它的最大问题还是在于写法太违背人思维
在人们长期的同步函数调用的经验下,我们更喜欢同步函数调用的方式:
let result = test();
if(result) {
} else {
}
// 有没有可能test是一个异步函数,但我们不用异步函数,而用同步函数的写法?
// 所以使用 async await
async是Promise的语法糖 ,使异步代码易于编写和阅读
- async:申明一个 异步的function
- await:等待一个 异步任务执行完成的的结果
async function test() {
throw new Error("error") // async方法抛异常的时候是 reject, catch
}
test().then(function(data){
console.log(data)
}).catch(function(err){
console.log(err)
})
async function test() {
return "hello"; // async函数返回一个值的时候,Promise的resolve方法会负责传递这个值,then
}
test().then(function(data){
console.log(data)
}).catch(function(err){
console.log(err)
})
await
函数执行时一旦遇到await就会先返回,等到这个结果才会继续往下执行
await
只能放在
async
函数内部使用
async function test() {
return "hello"
}
async function test1() {
let res = await test()
console.log(res + "11")
return res
}
test1().then(function(res){
console.log(res)
})
fetch
GET请求
成功
async function request() {
let data = await fetch("http://192.168.136.95:8181/phone/index")
return data.json()
}
async function requestJson() {
let json = await request()
console.log(json)
}
requestJson() // 如果requestJson没有返回值,那么调用的时候不用加上thenl
失败
async function request() {
let data = await fetch("http://192.168.136.95:8181/phone/index")
throw new Error("error")
return data.json()
}
async function requestJson() {
try {
let json = await request()
console.log(json)
} catch(e) {
console.log(e)
}
}
requestJson()
模块
-
使用模块就是通过命名空间对各类业务对象进行一定的封装,
防止命名冲突
-
在ES6之前,JavaScript本身没有模块支持,但社区创造了令人印象深刻的解决方案。两个最重要的(也是不相容的)标准是:
AMD
和
CommonJS
-
ES6 module 结合了CommonJS和AMD的优点:类似CommonJS,具有简洁的语法,对
循环依赖的支持
;类似AMD,支持
异步加载和有条件的模块加载
- 简单来说:ES6 module 使用 export 导出模块的内容,并使用 import 导入模块的内容
以前,解决命名冲突
// 1.js 小明
(function() {
let flag = true
})()
// 3.js 小明
if(flag) {
console.log("aaa")
}
// 2.js 小红
(function() {
let flag = true
})()
// 0.js
<script src='./1.js'> </script>
<script src='./3.js'> </script>
<script src='./2.js'> </script>
// 虽然可以解决但是小明自己写的文件3.js就用不了1.js的flag了
// 自己实现模块化
// 此时module1 的结果为 true,就可以给3.js使用了
// 1.js
var module1 = (function() {
let flag = true
return flag
})()
// 3.js
if(module1.flag) {
console.log("aaa")
}
现在使用模块化就好了AMD,CommonJS
export
-
Named exports(命名导出;每个模块可有
多个
) -
Default exports(默认导出;每个模块只能
一个
)
可以导出(对外提供)模块的内容:
函数
、
对象
、
变量
等
命名导出
// 1)声明时导出
export var myVar1 = 'a';
export let myVar2 = 'b';
export const MY_CONST = 'c';
export function myFunc() {}
// 2)声明后导出
var myVar3 = 'a';
export { myVar3 };
// 3)别名导出
var myVar4 = 'a';
export { myVar4 as myVar };
默认导出
// 1)声明时导出
export default function() {}
// 2)别名设置为default导出
export default function name1() {}
export { name1 as default };
使用模块文件
在支持ES6模块的浏览器中,引入ES6 module 的JS文件时,使用属性 type=“module”
// 0.js
<script type="module" src="./1.js"></script>
<script type="module" src="./2.js"></script>
// 这样就不会有命名冲突了,但是1.js中不能直接用2.js中的东西,想要使用的话,2.js需要导出对应的东西
import
导入默认
// math.js
export default function cube(x) {
return x * x * x;
}
// app.js:导入默认导出的模块时,需要指定模块名称
import cube from './math.js';
console.log(cube(3)); // => 27
导入命名
import axios from ‘axios’; 从 node_modelus下导入
// math.js
export function add(a, b) {
return a + b;
}
// app.js:指定使用math模块的add命名导出
import { add } from './math.js';
console.log(add(1, 2)); // => 3
// 导入所有的命名导出作为math对象的成员
import * as math from './math.js';
console.log(math.add(1, 2)); // => 3
// math.js
export function add(a, b) {
return a + b;
}
// 立即执行
(function() {
console.log('hello math.js');
})();
// app.js
import { add } from './math.js'; // => hello math.js
Node.js
node.js是什么
http://nodejs.cn/api/
http://nodejs.cn/learn
-
Node.js 是一个开源与跨平台的
JavaScript 运行时环境
-
Node.js 在浏览器外运行
V8 JavaScript 引擎(Google Chrome 的内核)
,性能非常好 -
可以写
客户端
和
服务端
代码 - 大量的库,npm 开源包自由使用
与浏览器区别
-
JavaScript 发展的速度非常快,但是浏览器发展得慢一些,并且用户的升级速度也慢一些,因此有时在 web 上,不得不使用较旧的 JavaScript / ECMAScript 版本
可用
Babel 将代码转换为与 ES5 兼容的代码,再交付给浏览器
, Node.js 不需要这样做 -
Node.js 用
CommonJS 模块系统
,浏览器用
ES 模块标准
在 Node.js 中使用
require()
,要用import,则
package.js要加上 ,“type”: “module”
, 而在浏览器中则使用
import
-
在浏览器中,大多做的是 DOM 或其他 Web 平台 API(例如 Cookies)进行交互。在 Node.js 中是不存在的。 没有
document
、
window
、所有其他的对象
安装
linux 安装nodejs 使用nvm安装,
nvm
是一种流行的运行 Node.js 的方式。 例如,它可以轻松地
切换 Node.js 版本
,也可以安装新版本用以尝试并且当出现问题时轻松地
回滚
。这对于使用旧版本的 Node.js 来测试代码非常有用
windows中,用chocolatey安装,或者直接下载安装包
-
选择
LTS长期支持版本,稳定版
node -v
npm -v
都显示版本,则安装成功
配置淘宝源
win + x的 powershell下
npm config set registry "https://registry.npm.taobao.org"
运行JS
// 1.js
function hello() {
console.log("hello world")
}
hello()
// cmd , cd 进入js目录
node 1.js
node.js程序退出
当在控制台中运行程序时,可以使用
ctrl-C
将其关闭
编程的方式退出 Node.js 程序:
process.exit()
http://nodejs.cn/learn/how-to-exit-from-a-nodejs-program
Vscode
npm i jshint -g
npm i eslint -g
vscode创建.jshintrc
{
"esversion": 9,// 自动路由 需要11
"asi": true // 没写分号不提示报错
}
vue.js 脚手架
// 安装
npm i @vue/cli -g
// 查看是否成功
vue -V // 大写的V
模块
Node.js以模块来组织项目结构,项目中可以
引入其他模块的功能
-
require
- CommonJS标准
- 民间标准
- NodeJS官方使用
- 动态载入
- 通过赋值完成符号载入
-
import
- ES6标准
- 通用
- 未来希望
-
静态载入,
ES11
之后也支持动态载入 - 通过解构来完成符号载入
使用import和require导入模块时以如下顺序查找模块
-
内置模块
-
传递给 require 函数的是 NodeJS 内置模块名称,不做路径解析,返回
内部模块的导出对象
- 例如require(‘fs’)
-
传递给 require 函数的是 NodeJS 内置模块名称,不做路径解析,返回
-
node_modules目录
- node_modules 是Node.js中用于存放模块的目录
- 可以分为全局node_modules目录和项目局部node_modules目录
- 例如某个模块的绝对路径是/home/user/hello.js,
- 在该模块中使用 require(‘foo/bar’) 方式加载模块时
-
NodeJS 依次尝试使用以下路径:
- /home/user/node_modules/foo/bar
- /home/node_modules/foo/bar
- /node_modules/foo/bar
-
NODE_PATH环境变量
- 与 PATH 环境变量类似,NodeJS 允许通过 NODE_PATH 环境变量来指定额外的模块搜索路径。
-
例如定义了以下 NODE_PATH 环境变量:
- NODE_PATH=/home/user/lib:/home/lib
-
当使用 require(‘foo/bar’)的方式加载模块时,则 NodeJS 依次尝试以下路径。
- /home/user/lib/foo/bar
- /home/lib/foo/bar
入口文件
一个Node.js项目一般以
main.js
作为入口文件
项目中可以会包含多个不同的模块
- 模块可以 以.js文件组织
- 也可以 以不同的文件夹组织(更常见)
- 例如:
/home/user/lib/
cat/
head.js
body.js
main.js
util/
index.js
以文件组织的模块,直接导入文件即可
var body = require('./body');
// 或
import body from "./body"
以目录组织的模块,若模块中的文件名为index.js,那么只需要导入到目录级别即可
var util = require('./util');
// 或
import util from "./util"
注意:
@表示 src目录
包描述文件
如果想自定义入口模块的文件名和存放位置,就需要在包目录下包含一个 package.json 文件,并在其中指定入口模块的路径
例如:
- /home/user/lib/
- cat/
+ doc/
- lib/
head.js
body.js
main.js
+ tests/
package.json
其中package.json内容为
{
"name": "cat",
"main": "./lib/main.js"
// node.js会根据package.json下的main找到入口模块所在的位置
}
包描述文件扩展
package.json除了可以对
项目进行描述
之外,还有
对其他模块的依赖
创建package.json
- 手动创建
-
或者通过
npm init
命令生成规范的package.json文件(
推荐
)
- name是项目
- main是 入口文件
- script是 node 脚本
- browserslist 浏览器说明
- … 其他参考官方API
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
此配置意味着需要支持使用率超过 1%,浏览器的最新的 2 个主版本,但不含 IE8 及更低的版本
指定依赖包:
-
dependencies:在生产环境中需要依赖的包
通过npm install -S 或者 –save - devDependencies:在开发和测试
- 通过npm install -D
package.lock.json 描述
包的版本
npm
npm是
node.js安装包管理工具
,能解决 NodeJS 代码部署上的很多问题
常见的使用场景有以下几种:
- 允许用户从 NPM 服务器下载别人编写的三方包到本地使用
- 允许用户从 NPM 服务器下载并安装别人编写的命令行程序到本地使用
- 允许用户将自己编写的包或命令行程序上传到 NPM 服务器供别人使用
包版本
npm list
npm list -g
npm list 软件包的名称
安装
npm i 包
一般是安装仅有本项目使用的一些库文件
将安装包放在 ./node_modules 下(运行 npm 命令时所在的目录),如果没有 node_modules 目录,会在当前执行 npm 命令的目录下生成 node_modules 目录
npm i 包 -g
-
一般用于安装需要全局运行的工具
-
将安装包放在 /usr/local 下或者你 node 的安装目录
-
可以直接在命令行里任意目录中使用。这是使用全局安装的主要原因
-
可以使用下面的命令来查看全局的包安装的位置:
npm prefix -g
-
添加依赖
npm install <包> --save # 或 npm i <包> -S # dependencies,项目打包时,需要发布到生产环境的
-
添加开发依赖
npm install <包> --save-dev # 或 npm i <包> -D # devDependencies,开发 测试
//全局安装信息
npm ls -g
//列出当前项目中的包
npm ls
卸载 更新
// 卸载
npm uninstall 包名
// 更新
npm update 包名
// 更新所有包
npm update
初始化项目
cd 进入js目录
npm init
在工程目录下
npm i qs -S
安装less
npm i less less-loader -D
将less编译为css
lessc style/style.less style/style.css
复制项目
进入copy目录,只要有package-lock.json,和package.json。npm i即可安装依赖 ,可以将开发者所依赖的包全部下载重现
导入qs
package.json
{
"name": "first_node",
"version": "1.0.0",
"description": "a demo project",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Chuang",
"license": "ISC",
"type": "module", // 加上这个
"dependencies": {
"qs": "^6.10.1"
},
"devDependencies": {
"less": "^4.1.1",
"less-loader": "^10.0.1"
}
}
import qs from 'qs';
let data = {id: 3, page: 1, pageCount: 10, name: "a&b c"}
let params = qs.stringify(data)
console.log(params)
// id=3&page=1&pageCount=10&name=a%26b%20c
let recovery = qs.parse(params)
console.log(recovery)
// { id: '3', page: '1', pageCount: '10', name: 'a&b c' }
npm脚本
package.json文件里面定义脚本命令
{
// ...
"scripts": {
"build": "node build.js"
}
}
这些定义在package.json里面的脚本,就称为
npm脚本
npm run build
// 等同于执行
node build.js
npm脚本的优势
- 目的相关脚本,可以集中在一个地方
-
npm run
可以查看所有可调用的脚本名 -
不同项目的脚本命令,只要功能相同,就可以有同样的对外接口。用户不需要知道怎么测试你的项目,只要运行
npm run test
即可 -
可以利用 npm 提供的很多辅助功能
src=”./1.js”>
// 这样就不会有命名冲突了,但是1.js中不能直接用2.js中的东西,想要使用的话,2.js需要导出对应的东西
#### import
##### 导入默认
```javascript
// math.js
export default function cube(x) {
return x * x * x;
}
// app.js:导入默认导出的模块时,需要指定模块名称
import cube from './math.js';
console.log(cube(3)); // => 27
导入命名
import axios from ‘axios’; 从 node_modelus下导入
// math.js
export function add(a, b) {
return a + b;
}
// app.js:指定使用math模块的add命名导出
import { add } from './math.js';
console.log(add(1, 2)); // => 3
// 导入所有的命名导出作为math对象的成员
import * as math from './math.js';
console.log(math.add(1, 2)); // => 3
// math.js
export function add(a, b) {
return a + b;
}
// 立即执行
(function() {
console.log('hello math.js');
})();
// app.js
import { add } from './math.js'; // => hello math.js
Node.js
node.js是什么
http://nodejs.cn/api/
http://nodejs.cn/learn
-
Node.js 是一个开源与跨平台的
JavaScript 运行时环境
-
Node.js 在浏览器外运行
V8 JavaScript 引擎(Google Chrome 的内核)
,性能非常好 -
可以写
客户端
和
服务端
代码 - 大量的库,npm 开源包自由使用
与浏览器区别
-
JavaScript 发展的速度非常快,但是浏览器发展得慢一些,并且用户的升级速度也慢一些,因此有时在 web 上,不得不使用较旧的 JavaScript / ECMAScript 版本
可用
Babel 将代码转换为与 ES5 兼容的代码,再交付给浏览器
, Node.js 不需要这样做 -
Node.js 用
CommonJS 模块系统
,浏览器用
ES 模块标准
在 Node.js 中使用
require()
,要用import,则
package.js要加上 ,“type”: “module”
, 而在浏览器中则使用
import
-
在浏览器中,大多做的是 DOM 或其他 Web 平台 API(例如 Cookies)进行交互。在 Node.js 中是不存在的。 没有
document
、
window
、所有其他的对象
安装
linux 安装nodejs 使用nvm安装,
nvm
是一种流行的运行 Node.js 的方式。 例如,它可以轻松地
切换 Node.js 版本
,也可以安装新版本用以尝试并且当出现问题时轻松地
回滚
。这对于使用旧版本的 Node.js 来测试代码非常有用
windows中,用chocolatey安装,或者直接下载安装包
-
选择
LTS长期支持版本,稳定版
node -v
npm -v
都显示版本,则安装成功
配置淘宝源
win + x的 powershell下
npm config set registry "https://registry.npm.taobao.org"
运行JS
// 1.js
function hello() {
console.log("hello world")
}
hello()
// cmd , cd 进入js目录
node 1.js
node.js程序退出
当在控制台中运行程序时,可以使用
ctrl-C
将其关闭
编程的方式退出 Node.js 程序:
process.exit()
http://nodejs.cn/learn/how-to-exit-from-a-nodejs-program
Vscode
npm i jshint -g
npm i eslint -g
vscode创建.jshintrc
{
"esversion": 9,// 自动路由 需要11
"asi": true // 没写分号不提示报错
}
vue.js 脚手架
// 安装
npm i @vue/cli -g
// 查看是否成功
vue -V // 大写的V
模块
Node.js以模块来组织项目结构,项目中可以
引入其他模块的功能
-
require
- CommonJS标准
- 民间标准
- NodeJS官方使用
- 动态载入
- 通过赋值完成符号载入
-
import
- ES6标准
- 通用
- 未来希望
-
静态载入,
ES11
之后也支持动态载入 - 通过解构来完成符号载入
使用import和require导入模块时以如下顺序查找模块
-
内置模块
-
传递给 require 函数的是 NodeJS 内置模块名称,不做路径解析,返回
内部模块的导出对象
- 例如require(‘fs’)
-
传递给 require 函数的是 NodeJS 内置模块名称,不做路径解析,返回
-
node_modules目录
- node_modules 是Node.js中用于存放模块的目录
- 可以分为全局node_modules目录和项目局部node_modules目录
- 例如某个模块的绝对路径是/home/user/hello.js,
- 在该模块中使用 require(‘foo/bar’) 方式加载模块时
-
NodeJS 依次尝试使用以下路径:
- /home/user/node_modules/foo/bar
- /home/node_modules/foo/bar
- /node_modules/foo/bar
-
NODE_PATH环境变量
- 与 PATH 环境变量类似,NodeJS 允许通过 NODE_PATH 环境变量来指定额外的模块搜索路径。
-
例如定义了以下 NODE_PATH 环境变量:
- NODE_PATH=/home/user/lib:/home/lib
-
当使用 require(‘foo/bar’)的方式加载模块时,则 NodeJS 依次尝试以下路径。
- /home/user/lib/foo/bar
- /home/lib/foo/bar
入口文件
一个Node.js项目一般以
main.js
作为入口文件
项目中可以会包含多个不同的模块
- 模块可以 以.js文件组织
- 也可以 以不同的文件夹组织(更常见)
- 例如:
/home/user/lib/
cat/
head.js
body.js
main.js
util/
index.js
以文件组织的模块,直接导入文件即可
var body = require('./body');
// 或
import body from "./body"
以目录组织的模块,若模块中的文件名为index.js,那么只需要导入到目录级别即可
var util = require('./util');
// 或
import util from "./util"
注意:
@表示 src目录
包描述文件
如果想自定义入口模块的文件名和存放位置,就需要在包目录下包含一个 package.json 文件,并在其中指定入口模块的路径
例如:
- /home/user/lib/
- cat/
+ doc/
- lib/
head.js
body.js
main.js
+ tests/
package.json
其中package.json内容为
{
"name": "cat",
"main": "./lib/main.js"
// node.js会根据package.json下的main找到入口模块所在的位置
}
包描述文件扩展
package.json除了可以对
项目进行描述
之外,还有
对其他模块的依赖
创建package.json
- 手动创建
-
或者通过
npm init
命令生成规范的package.json文件(
推荐
)
- name是项目
- main是 入口文件
- script是 node 脚本
- browserslist 浏览器说明
- … 其他参考官方API
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
此配置意味着需要支持使用率超过 1%,浏览器的最新的 2 个主版本,但不含 IE8 及更低的版本
指定依赖包:
-
dependencies:在生产环境中需要依赖的包
通过npm install -S 或者 –save - devDependencies:在开发和测试
- 通过npm install -D
package.lock.json 描述
包的版本
npm
npm是
node.js安装包管理工具
,能解决 NodeJS 代码部署上的很多问题
常见的使用场景有以下几种:
- 允许用户从 NPM 服务器下载别人编写的三方包到本地使用
- 允许用户从 NPM 服务器下载并安装别人编写的命令行程序到本地使用
- 允许用户将自己编写的包或命令行程序上传到 NPM 服务器供别人使用
包版本
npm list
npm list -g
npm list 软件包的名称
安装
npm i 包
一般是安装仅有本项目使用的一些库文件
将安装包放在 ./node_modules 下(运行 npm 命令时所在的目录),如果没有 node_modules 目录,会在当前执行 npm 命令的目录下生成 node_modules 目录
npm i 包 -g
-
一般用于安装需要全局运行的工具
-
将安装包放在 /usr/local 下或者你 node 的安装目录
-
可以直接在命令行里任意目录中使用。这是使用全局安装的主要原因
-
可以使用下面的命令来查看全局的包安装的位置:
npm prefix -g
-
添加依赖
npm install <包> --save # 或 npm i <包> -S # dependencies,项目打包时,需要发布到生产环境的
-
添加开发依赖
npm install <包> --save-dev # 或 npm i <包> -D # devDependencies,开发 测试
//全局安装信息
npm ls -g
//列出当前项目中的包
npm ls
卸载 更新
// 卸载
npm uninstall 包名
// 更新
npm update 包名
// 更新所有包
npm update
初始化项目
cd 进入js目录
npm init
在工程目录下
npm i qs -S
安装less
npm i less less-loader -D
将less编译为css
lessc style/style.less style/style.css
复制项目
进入copy目录,只要有package-lock.json,和package.json。npm i即可安装依赖 ,可以将开发者所依赖的包全部下载重现
导入qs
package.json
{
"name": "first_node",
"version": "1.0.0",
"description": "a demo project",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Chuang",
"license": "ISC",
"type": "module", // 加上这个
"dependencies": {
"qs": "^6.10.1"
},
"devDependencies": {
"less": "^4.1.1",
"less-loader": "^10.0.1"
}
}
import qs from 'qs';
let data = {id: 3, page: 1, pageCount: 10, name: "a&b c"}
let params = qs.stringify(data)
console.log(params)
// id=3&page=1&pageCount=10&name=a%26b%20c
let recovery = qs.parse(params)
console.log(recovery)
// { id: '3', page: '1', pageCount: '10', name: 'a&b c' }
npm脚本
package.json文件里面定义脚本命令
{
// ...
"scripts": {
"build": "node build.js"
}
}
这些定义在package.json里面的脚本,就称为
npm脚本
npm run build
// 等同于执行
node build.js
npm脚本的优势
- 目的相关脚本,可以集中在一个地方
-
npm run
可以查看所有可调用的脚本名 -
不同项目的脚本命令,只要功能相同,就可以有同样的对外接口。用户不需要知道怎么测试你的项目,只要运行
npm run test
即可 - 可以利用 npm 提供的很多辅助功能