JS的三大组成部分
- ECMAScript,描述了该语言的语法和基本对象。(关键)
- 文档对象模型(DOM),描述处理网页内容的方法和接口。
即控制页面的一些节点,去做一些事件,如点击事件等; - 浏览器对象模型(BOM),描述与浏览器进行交互的方法和接口。
浏览器提供的默认的一些东西,如alter弹出框,prompt等
js的运行环境:
node:
浏览器:基于chorm的v8,即内置有js的解析器,很多的浏览器都具有js的解释器。
js的特点
1.解释型语言
2.弱类型语言
即变量的类型取决于值的类型,例:var a=2;那a就是一个number类型,a=true;那a就是一个Boolean型。变量的数据类型在初始化的时候决定,但是可以随时改变。
3.顺序解释执行,按照代码的解析顺序执行。
写在前面的先执行,js是一个单线程语言
4. 既可以作为前端脚本语言,也可以作为后端语言,取决于应用平台(浏览器/操作系统)和使用的框架(dom、jquery/http、mysql、file)
5. 区分大小写
js的引入
引入顺序很重要
推荐:先把结构即html的节点加载出来,再加载数据js
window.οnlοad=function(){}就是确保以上的实现,监听事件,类似于生命周期函数,保证页面加载后,再执行js。
js的基本语法
在js里是区分大小写的
输出语句
console.log(“输出语句”);
关键字与保留字
命名规范
1.单个单词直接小写,多个单词拼接建议采用驼峰命名
2.不能以数字开头,支持数字字母下划线以及$组成
3.不能以关键字以及保留字命名
变量
- var
可以重复声明
可以变量提升
不存在块级作用域,即函数里面的if等是块级作用域 - let
- const
变量声明提前:在用var声明变量后,尽管在后面才声明的变量,但在前面调用的时候不会出现错误,会被提前到当前作用域的最前面。
例:
console.log(a);
var a=23;
会输出undefined;
数据类型
基本数据类型/简单数据类型:字符串,number,Boolean,null,symbol,undefined
1.字符串 “字符串”;模板字符串 `a`
2..undefined :当前对象未定义,未赋值。
undefined它继承/派生于null undefined==null //true undefined === null // flase
3.number:
进制也是number,会自动转换成10进制输出。
数字监测:
如果你的字符串也是一个数字字符串,会自动转换成数字。
var a = 10 / 'a' ; 输出:NaN
var b = 10 / '10'; 输出:1
var a = 9/0; // 输出:Infinity
number的范围:
console.log( Number.MAX_VALUE);
console.log(Number.MAX_VALUE);
4.Boolean:true,false
5.null:存放的内容为空
引用数据类型/复杂数据类型:对象object,数组arry,函数function
1.对象object:键值对
注意:在js中,数组的检测类型也是一个对象
对象 : a={name:"zhangsan",val:'12'};
调用:a.name
2.数组:
数组可以接受任意的数据类型的值,
a=[1,'abc',{name:'zhangsan'},function a(){}]
调用:a[下标],a[2].name
3.函数function:
function 函数名称(形式参数){} 相当于 var 函数名称=function(形式参数){};
调用:函数名称(实际参数)
5.Symbol:
typeof判断变量类型
typeof a;
注意:除了null没有,其他function,symbol,undefined等数据类型都能被判断,且数组会被判断成object。当判断null时会判断出object,因为js的数据类型都是32位的二进制数,
他的判断以前三位的二进制数为准,object与null的前三位都是000,所以会出现null被判断object的现象。
作用域与一些案例
- 全局变量:
如果一个变量未使用var进行声明,那么该变量会是全局变量global;与this不一样 - 局部变量:
变量声明提前:在用var声明变量后,尽管在后面才声明的变量,但在前面调用的时候不会出现错误,会被提前到当前作用域的最前面。函数会被提升,函数比var优先提升
例:
console.log(a);
var a=23;
会输出undefined;
如果局部变量与全局变量的名字一样,那全局变量会变成局部变量
如果在局部作用域中即函数,那么this就是global,如果在外面,那么this就是this与global不一样,在外部也需要加this关键字才能访问到同样用this定义的变量,否则会出错
案例1:
function foo(){
if(true){
var a = 3; //会发生声明提前的现象,var a;相当于会发生在if的上面;
console.log("inner",a);//inner 3
}
console.log("outer",a);//inner 3 //没有块级作用域
}
foo();
console.log(a);//error! 函数作用域:局部作用域,a没有在该作用域里面定义,在函数中定义的是一个局部变量,无法在外面被访问到。
// var 的变量声明提前知会提升到当前作用域的最前面
案例2:
//如果在函数中定义变量没有加var或其他,该变量为全局变量global
function test(){
message = "hello";
}
test();
console.log(message); //可以访问
案例3:
//用var操作符定义的变量将成为定义该变量的作用域中的局部变量
//全局作用域
function b() {
a = 10;//由于里面没有定义a变量,a变量在外面有定义,那么,该改变的就是外面的变量a,即全局变量。
return;
}
var a = 1;//声明被提前,var a;被提前到最前面,即function的前面
b();
console.log(a);//10
案例4:
x = 1;//window.x global.x
console.log(x); //1
function y() {
console.log(x); //undefined-》由于后面有var x,即var x;会被声明提前到函数的最前面。
//没有加this关键字,将被默认访问的是局部变量var x;
console.log(this.x);//1
var x = 2;
console.log(x); //2
}
y();
console.log(x);//1
案例5:
//函数作用域:局部作用域
var a = 1;
function b() {
a = 10;
return;
//a函数声明,函数声明同样会被提前,提前变量a,将a认为是函数b作用域的变量,具有局部效果,即将a变成了局部变量。
function a(){}
}
b();
console.log(a); // 1
等号的区别
1.一个等号:赋值操作,将等号后的值赋值给等号前的
2.双等:左右变量的一个数据的比较,只会比较值是否相等,不会比较数据类型是否相等,数据类型不等时,会将两种类型转换成同样的数据类型再比较。
例:10 == ’10‘ 输出true
NaN == NaN:输出false
3.三等:严格相等判断,先去判断数据类型是否一样,再判断值是否一样,如果是引用数据类型,还会判断内存地址是否相等;
例:10 === '10' :输出false;
NaN === NaN:输出false
深拷贝与浅拷贝
深拷贝:即数据的内存空间互不干扰,基本数据类型的变量与值都是存放在栈区
var a = 123;
var b = a;// b=123
b = 456 ; // b=456, a=123
浅拷贝:引用地址的传递,当数据类型是引用数据类型时,赋值会给的是内存地址,因为它在栈里面存储的就是内存地址,值存放在堆区。
var a = {name:‘zhangsan’ , age :10 }
var b = a;
b.name = ‘lisi’; // 那么a的名称也将会lisi
解决方法浅拷贝的方法
三点运算符:
b={...a};
三点运算符会把对象拆开,即拆成name:'zhangsan'与age:10。我们通过三点运算符拆开后,再用对象{}把它重组,就等于b还是一个对象,但不会是跟a是同样的内存地址。
lodash_.cloneDeep()
序列化与反序列化
特殊值 NaN, isNaN, isFinite, Infinity
- NaN: 如 hello/2 的输出 不是一个数
- isNaN:判断该值是不是不是一个数:是一个数返回false,不是一个数返回true
- isFinite:判断有效值
a=‘123’ true
a=‘hello’ false
a=2 true - Infinity :9/0 Infinity 无穷数值 NaN===NaN //false
操作符
算数运算符
运算符 | 描述 | 例子 | 结果 |
---|---|---|---|
+ | 加法 | var num = 1 + 2; | 3 |
- | 减法 | var num = 4 - 3; | 1 |
* | 乘法 | var num = 4 * 3; | 12 |
/ | 除法 | var num = 6 / 3; | 2 |
% | 取余 | var num = 7 % 3; | 1 |
再js中,数字字符串也可以做运算,仅加号是拼接。
加号的几个作用:
1.简单的加法运算
2.符号取正操作
3.字符串的拼接
4.对布尔类型的转换,true转1,false转0
一元运算符
运算符 | 描述 |
---|---|
+ | 将操作数转换成数字,字符串的拼接 ,正号 |
- | 将操作数转换成数字,同时变为负数,负号 |
! | 逻辑取反运算符 |
++ | 递增,放在前面时,先累加,再赋值;放后面,就先赋值,后加加,与减减同理 |
– | 递减 |
delete | 删除数组或对象中特定索引的值,例 delete obj.name,删除obj里的name属性,没有返回值。 |
typeof | 操作数放到typeof的后面,会返回当前操作数的类型,对于数值类型可以准确返回,对于引用类型,Function会返回’function’,其他都只会返回’object’ |
void | void 运算符对任何函数的返回值变成 undefined。如 void test(); |
赋值运算符
运算符 | 例子 | 等同于 |
---|---|---|
= | x = y | x = y |
+= | x += y | x = x + y |
-= | x -= y | x = x - y |
*= | x *= y | x = x * y |
/= | x /= y | x = x / y |
%= | x %= y | x = x % y |
逻辑运算符
运算符 | 描述 | 例子 |
---|---|---|
&& | and | (x < 10 && y > 1) 为 true |
|| | or | ````(x==5 |
! | not | !(x==y) 为 true |
注意:逻辑运算符不一定会返回true和false值,如果有一个操作数不是布尔类型,逻辑与就不一定返回boolean类型,会返回决定该表达式是真或假的决定性的值。
例:
||语句如果第一个操作数是真,直接返回第一个操作数 。&&运算符若第一个为真,表达式取决于第二个,所以返回第二个值,当第一个操作数为假,则直接返回第一个操作数。
||操作符:如果第一个操作数是null,NaN,undefined,false,0,"" 则返回第二个操作数。&&操作符则相反。
console.log(123||345); //123
比较运算符
运算符 | 描述 | 比较 | 返回 |
---|---|---|---|
== | 等于 | x == 8 | false |
x == 5 | true | ||
x == “5” | true | ||
=== | 值相等并且类型相等 | x === 5 | true |
x === “5” | false | ||
!= | 不相等 | x != 8 | true |
!== | 值不相等或类型不相等 | x !== 5 | false |
x !== “5” | true | ||
x !== 8 | true | ||
> | 大于 | x > 8 | false |
< | 小于 | x < 8 | true |
>= | 大于或等于 | x >= 8 | false |
<= | 小于或等于 | x <= 8 | true |
三目运算符
expression ? sentence1 : sentence2
如果expression为真,那么返回sentence1,否则,返回sentence2
js的隐式转换(面试)
-
基本数据类型的转换:最后转换的结果只有两种类型 Number 和 String 类型
1.表达式中全部都是字符串,即是一个字符串
2.表达式中式一个Number,即一个简单的算术
3.表达式中Number和字符串拼接,如果表达式中如果有一个是字符串,那么最后的结果将是字符。只有加法适合,其他算术运算符都将是输出 NaN;
例:1 + 2 +3 + ‘123’ = ‘6123’;
将前面 1+2+3 -> 6,再把6 +‘123’ 做一个tostring的操作。 -
引用数据类型的转换:主要是靠ToPrimitive转换成基本的数据类型,而ToPrimitive转换成Number 还是 string 是由PreferredType决定的,在PreferredType中,除了日期转换成String类型,其余的都是Number类型。
ToPrimitive(input, PreferredType) -
如果PreferredType被标记为Number,则会进行下面的操作流程来转换输入的值。
1、如果输入的值已经是一个原始值,则直接返回它
2、否则,如果输入的值是一个对象,则调用该对象的valueOf()方法,
如果valueOf()方法的返回值是一个原始值,则返回这个原始值。
3、否则,调用这个对象的toString()方法,如果toString()方法返回的是一个原始值,则返回这个原始值。最终拿到一个string类型
4、否则,抛出TypeError异常。
1、如果输入的值已经是一个原始值,则直接返回它
2、否则,调用这个对象的toString()方法,如果toString()方法返回的是一个原始值,则返回这个原始值。
3、否则,如果输入的值是一个对象,则调用该对象的valueOf()方法,
如果valueOf()方法的返回值是一个原始值,则返回这个原始值。
4、否则,抛出TypeError异常。
js显示转换
1.将其他数据类型转换为number;如:
Number();
Number("1+2.3"); // NaN 符号位出现在其他位置,解析为NaN
Number("+12.1"); //12.1 首位为符号位,其余为为数值,转换为对应的数值
Number(""); // 0 空字符串被转换为0
Number(true); //1
Number(false); //0
Number(null); //0
Number(undefined); //NaN
parseInt(): 转换成整数,实行向下取整的过程,
parseInt("+12.1"); //12; 首位为符号位,其余为为数值,转换为整数
parseInt("1+2.7"); //1; 符号位出现在其他位置,保留符号位前面的数值
parseInt("123ac"); //123;如果首位为数值,依次向后解析,找到连续的数值,直到遇到第一个非数值的,将之前获取的数值转换为Number
parseFloat():转换成浮点数,保留小数部分,转换规则与parseInt相同。
+:当字符串为纯数字且加上一个Number类型的时候,数字字符串会转换成Number类型。
+"23" //23
+null //0
+undefined //NaN
!!!总结:当有字符出现在数字的后面时,parseInt会自动监测到含数字的部分,输出数字,Number会直接输出NaN,且paseInt不可以转换空字符串,会直接输出NaN,如果转换的值是null,undefined,boolean,均转换为NaN
2.将其他数据类型转换成布尔类型
Boolean();
!!a;
3.将其他数据类型转换成string
.toString():除了可以转化成字符串外,括号里面还可以取值,将数字转换为进制,例:num.toString(2):将num转换为2进制的字符串。
.+ :" + '':用加号连接有字符串,那么最后得出的是字符串
.String():与toString()不同点在于,它可以转换null与undefined成字符串“null”和‘undefined’,但它不能实现进制转换。格式:String(要转换的)
案例
[] + [] // ""
进行ToPrimitive,两个都是Array对象,不是Date对象,所以以Number为转换标准,所以先调用valueOf(),结果还是[ ],不是原始值,所以继续调用toString(),结果是“”(空字符串)原始值,将“”返回。第二个[ ]过程是相同的,返回“”。加号两边结果都是String类型,所以进行字符串拼接,结果是“”。
[] + {} // "[object Object]" 一个string 类型
进行ToPrimitive,依然是以Number为转换标准。
[ ]的结果是“”。
{ }先调用valueOf(),结果是{ },不是原始值,所以继续调用toString(),结果是“[object Object]”,是原始值,将“[object Object]”返回。
加号两边结果都是String类型,所以进行字符串拼接,结果是“[object Object]”。
{} + [] // 0
//在js中正确的写法就是
// {};
// +[];
这道题按照上一题的步骤,讲道理的话,结果应该还是“[object Object]”,但结果却出人意料——显示的答案是0!
这是什么原因呢?js解释器会将开头的 {}
看作一个代码块,而不是一个js对象;原来{ } + [ ]被解析成了
{ };+[ ],前面是一个空代码块被略过,剩下+[ ]就成了一元运算。[ ]的原值是””, 将””转化成Number结果是
0。
{} + {} // "[object Object][object Object]"
在金丝雀版本的chrome浏览器和node中,结果符合预期。
结果是”object Object”。
在普通版本的chrome浏览器中结果是NaN。
这是为什么呢?原因是在node中会将以“{”开始,“}”结束的语句外面包裹一层( ),就变成了({ } + { }),结果就符合预期。而普通版本的chrome依然会解析成{};+{},结果就变成了NaN
赋值运算符
运算符 | 例子 | 等同于 |
---|---|---|
= | x = y | x = y |
+= | x += y | x = x + y |
-= | x -= y | x = x - y |
*= | x *= y | x = x * y |
/= | x /= y | x = x / y |
%= | x %= y | x = x % y |
流程控制语句
for(语句1;语句2;语句3){}
for in 语句
数组:for(var 自己定义的下标名称 in 数组{};
对象 :for(var 自己定义的key值名称 in 对象){}
对循环的次数限制不严格
while与do while()
do{你的操作}while(限制条件);不管限制条件是否符合,do都会走一遍,再判断是否符合限制条件,再决定是否再执行do
冒泡排序
1.比较相邻两个数的大小,大的放在右边;
2.比较完一遍后,该数组的最大的值将在最后,则后面的循环就不用比较最后的那个。
3.重新比较一遍;
对象object
赋值:
- 直接赋值
var person={
name:'',
say:function(){
return this.name;}
}
- 通过对象赋值
var person= new Object();
person.name='';
person.say=function(){};
调用:
- person.name; person.say();
- person['name']; person['say'];
遍历:
for(var key in obj){};
删除属性
delet obj.要删除的属性; delet obj.name / delet obj[key]
强制类型转换/显式转换
- object 转 boolea
任何对象都是true;除非obj=null;
1.obj=new Object();
Boolean(obj) //true;
- object 转 string(默认调用toString()方法)
1.String(obj);
2.obj.toString();=> [object object],但类型是一个string
3.重写toString() obj={ toString:function(){ 你的转换操作 } },默认在Object(所属父类)方法中有toString()方法,再对象中重写该方法后,再调用String或toString会变成你的操作。
- object 转 Number (默认调用valueOf()方法)
1.Number(obj) ;
2.重写valueOf()方法
对象序列化–即将对象转换为json字符串,可以解决浅拷贝问题
序列化:JSON.stringify(obj)
① 使用toJSONString
var last = obj.toJSONString(); // 将JSON对象转化为JSON字符串
② 使用stringify
var last = JSON.stringify(obj); // 将JSON对象转化为JSON字符串
反序列化:JSON.parse(jsonStr) --将json字符串转化为对象
var obj = data.parseJSON(); //将字符串转换为json对象
var obj = JSON.parse(data); //将字符串转换为json对象
检测属性
in 检测某属性是否是某对象的自有属性或是继承属性,返回Boolean
格式:
‘要检测的属性值’ in 对象:'name' in obj;'toString' in obj ;
hasOwnProperty 检测自有的属性,继承的返回false;
格式:
obj.hasOwnProperty ('toString'); // false
propertyIsEnumerable 检测是否是可枚举(即可for循环遍历出来的)的属性,即我们自己手动创建的属性;
格式:
obj.propertyIsEnumerable ('toString'); // false;
obj.propertyIsEnumerable ('name') ;// true;
原型
原型:每个构造函数(Object)都有一个原型对象(Object.prototype),原型对象都包含一个指向构造函数的指针(constructor),实例(var obj = new Object() 即 obj为实例 )都包含一个指向原型对象的内部指针(proto)。
静态方法:只能由构造函数(Object)本身去调用
Object.defineproperty(属性所在的对象,‘属性的名字’,一个描述符对象)
格式:
Object.defineProperty(obj, ’prop‘, descript)详细的写应该是:
Object.defineProperty(obj, ’prop‘, {
Configurable:false, //是否可以被删除掉,默认为true
Enumerable: true, //是否可以被for in 循环出来,默认为true
writable:false, //是否可以被修改,默认为false
value:20 // 值
})
除了value,其余详细属性都是可选的,一般来说descript是取值
定义属性/数据属性:
- [[Configurable]]----是否可以通过delet 删除掉
表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或能否把属性修改为访问器属性,默认为true
-[[Enumerable]]-------是否可以被for in 循环出来
表示能否通过for-in循环返回属性,默认为true
- [[Writable]]
表示能否修改属性的值。(属性直接定义在对象中,默认为true)
- [[Value]]
包含这个属性的数据值 name:jacky
访问器属性-------可以控制访问与修改,
这个属性不包含数据值,包含的是一对get和set方法,
- [[Configurable]]
表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或能否把属性修改为访问器属性, 默认为false
- [[Enumerable]]
表示能否通过for-in循环返回属性,默认为false
- [[Get]]
在读取属性时调用的函数,默认值为undefined,即获得person.name时调用的是get方法
- [[Set]]
在写入属性时调用的函数,默认值为undefined,即在设置值如:person.name='lisi'; 时调用的时set方法
例:
var book = {
_year: 2020, //下划线表示是内部属性,只能通过对象的方法来读写
editor: 1
};
Object.defineProperty(book, 'year', {
get: function () {
return this._year;
},
// 若只指定get方法,不指定set方法,那就默认该属性是只读的
set: function (newYear) {
if (newYear !== this._year) {
this._year = newYear
this.editor ++
}
}
});
访问器属性与定义属性的区别
数据属性/定义属性:相当于给当前对象定义了一个公共属性,访问修改直接返回value值;
访问器属性:可以控制访问或修改这个属性的具体行为;
Object.defineProperties(目标对象,该对象的一个或多个键值对定义了将要为对象添加或修改的属性的具体配置)
格式:
Object.defineProperties(obj, {
name: {
value: 'zhangsan',
configurable: false,
writable: true,
enumerable: true
},
age: {
value: 18,
configurable: true
}
})
Object.getOwnPropertyDescriptor(obj 需要查找的目标对象 ,prop目标对象内属性名称) 读取属性的特性 true/flase
该方法返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性),
即查看该对象属性的详细配置(Configurable,Enumerable 等)的取值
格式:
Object.getOwnPropertyDescriptor(person, 'name');
is();
assign
实例方法/原型方法:存在原型对象中的方法
constructor
Object.prototype.constructor // 指向构造函数Object
保存用户创建当前对象的函数,与原型对象对应的构造函数
hasOwnProperty(propertyName)
检查给定的属性名是否是对象的自有属性
propertyIsEnumerable(propertyName)
检查给定的属性在当前对象实例中是否存在
valueOf()
返回对象的字符串,数值,布尔值的表示
toLocaleString()
返回对象的字符串表示,该字符串与执行环境的地区对应
例:
// 创建Date对象
var now = new Date();
console.log(now);
// 使用原型对象中的方法
console.log(now.toString());//Mon Aug 23 2021 23:22:35 GMT+0800 (GMT+08:00) (中国标准时间)
console.log(now.toLocaleString());//2021-8-23 23:22:35
toString()
返回对象的字符串表示
isPrototypeOf(object)
检查传入的对象的原型
a.isPrototypeOf(b) 如果a是b的原型,则返回true。如果b不是对象,或者a不是b的原型,则返回false。
函数
函数本质上是一个对象,每个函数都是一个function类型的实例,函数声明会提前,优先级比var高
函数的声明与调用
声明格式:
function 函数名(形式参数){} :函数声明,注意执行return 后的代码将不会被执行
var 函数名=function (){}:函数表达式;注意:当使用该函数声明时,在函数被赋值前就调用会报错,因为声明var会被提升,但赋值不会。
调用:
- 函数名(实参列表);
函数名.call(执行环境对象,实参列表);
函数名.apply(执行环境对象,实参列表数组);
函数名.bind(执行环境对象)(实参列表);
var obj = {
name: 'briup',
sayName: function (a,b) {
console.log(this.name);
}
}
var b = obj.sayName;
b(); //undefined,由于this的指向了b,而b没有name参数,就没办法找到
obj.sayName(); // briup
b.call(obj,1,2); // briup
b.apply(obj,[100,200]); // briup
b.bind(obj); // 代码没有被打印,这就是bind和call、apply方法的不同,实际上bind方法返回的是一个修改过后的函数。
// 新建一个变量c来接收bind修改后的函数
var c = b.bind(obj);
console.log(c); // 发现c是一个[Function: bound sayName]函数
// 执行c
c(); // briup
- 总结区别:call和apply都是改变上下文中的this并立即执行这个函数,bind方法可以让对应的函数想什么时候调就什么时候调用,并且可以将参数在执行的时候添加,这是它们的区别,
函数的内部属性
只有在函数内部访问的属性叫函数内部属性
arguments
当传递实参个数超过形参个数时,不会报错,会把所有的形式参数存放在arguments里面。不仅存放超出部分。
arguments是一个类数组,里面存放了所有的传递过来的形式参数,跟数组的调用方式一样。仅表现形式不一样
表现形式:“下标”:形式参数: “0”:1, “1”:33 ;调用:arguments[下标] :arguments[0];
获取形参的长度:函数名.length;
类数组对象转换为数组(笔试题)
1.Array.form(arguments)
2.Array.prototype.slice.call(arguments,0)
3. ...拓展运算符 -> [...arguments];
callee
属性是 **arguments** 对象的一个成员,仅当相关函数正在执行时才可用。
// 实现匿名的递归函数
var sum = function (n) {
if (n == 1) {
return 1;
} else {
return n + arguments.callee(n - 1);
}
}
console.log(sum(6));//输出结果:21
this
谁调用了这个方法,this就指向谁。取决于当下谁调用了,当前面没有写方法的调用者时,this就指向global。this不要跟作用域链混合。this的指向与它的调用对象与原型链继承有关
- 在方法中,this 表示该方法所属的对象。
- 如果单独使用,this 表示全局对象。
在全局作用域下,this指向的是modules.export (空对象)。
- 在函数中,this 表示全局对象。
在局部作用域中,this指向的是global
- 在事件中,this 表示接收事件的元素。
- 在显式函数绑定时,我们可以自己决定this的指向
call apply bind 这三个东西可以改变this的指向。
例:
var person1 = {
fullName: function () {
return this.firstName + " " + this.lastName;
}
}
var person2 = {
firstName: "Zach",
lastName: "Muyi",
}
var name = person1.fullName.call(person2); // 返回 "Zach Muyi",改变了this的指向,this直接指向peron2
IIFE:立即执行函数
IIFE: Immediately Invoked Function Expression,意为立即调用的函数表达式,也就是说,声明函数的同时立即调用这个函数。即该函数声明完,这个函数也就直接执行完毕了,边声明边执行。
格式:
1.(function(形参){})(实参);对返回结果不进行处理;
2. (function(形参){
函数体内容
}(实参));对返回结果不进行处理
注意:如果后面跟的是立即执行函数,那么结束一定要跟分号;
作用
- 页面加载完成后只执行一次的设置函数。
- 将设置函数中的变量包裹在局部作用域中,不会泄露成全局变量。
函数回调
含义:
一个函数被作为实参传入另一函数,并在该外部函数内被调用,用以来完成某些任务的函数,称为回调函数。
例:
function A (B) {
B();
console.log('A');
}
function B () {
console.log('B');
}
A(B);// B A
此时 B 函数为回调函数。
作用
回调函数的作用:回调函数一般都用在耗时操作上面:因为主函数不用等待回调函数执行完,可以接着执行自己的代码。比如ajax请求,比如处理文件等。
闭包
作用
它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
闭包的生成有三个必要条件
-
函数嵌套函数
-
内部函数引用了外部函数中的数据(属性、函数)
-
参数和变量不会被回收 这样就形成了一个不会销毁的函数空间,注意:要赋值给一个全局变量才能不被回收。
-
返回一个内部函数
-
** 产生一个闭包**
function A() {
var a = 1, b = 2;
//函数嵌套函数
function B() {
return a + b; //内部函数引用了外部函数中的数据(属性、函数)
}
return B;
}
var a=A();
a();//调用了B函数 返回3 ,不会被垃圾回收,因为赋值了一个全局变量,那全局变量就是该A()的返回值B函数,全局变量不会被垃圾回收,所以该函数的所有东西都不会被垃圾回收。
console.log(A()()); // 3,会一直返回3,没有赋值给全局变量,会产生垃圾回收
经典案例:
function f1() {
var n = 999;
nAdd = function () { n += 1 }
function f2() {
console.log(n);
}
return f2;
}
var result = f1();
result(); // 999
nAdd();
result(); // 1000 闭包的参数和变量不会被回收,所以调用nAdd时,n被修改。
在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。
优缺点
优点:不会造成变量污染
缺点:内存泄漏,不会被垃圾回收机制回收
作用域
函数作用域
在 JavaScript函数中声明的变量,会成为函数的局部变量。
函数内部声明的变量,在函数外部不能访问。
全局作用域
函数之外声明的变量,会成为全局变量。
函数外部声明的变量,在函数内部可以访问。
当函数嵌套,在这个时候,内部函数与外部函数的这个变量就组成了闭包。
作用域链
自由变量
当函数中要访问函数内部没有定义的变量,那么该变量为自由变量
数组
数组的创建与获取
创建:
1.var arr=['asd',{name:'123'},function arr(){}]:可知,数组里面可以存放任何类型的数据
2.arr=new Array(数组的长度/数组的元素);若括号里面放的是数字,那么定义的是数组的长度,放的是其他类型,那么就是数组的元素。
arr[下标]: 下标从0开始,最多一个数组中,可以保存
数组的删除
1.arr.length=arr.length-1:改变数组的长度,删除最后一个元素。
2.for循环遍历数组
数组的API
数组转字符串,数组.toString(),不修改原数组
以逗号的形式将数组中的元素进行分割
join() 以自定义的符号将数组中的元素进行分割,不修改原数组
arr.join('自定义的分割符'):默认是逗号
JSON.stringfy() JSON.parse() 数组转换成json对象
转换成json后,依然保留着中括号 例: [’元素1‘,'元素2']
isArray() 检测一个变量是否是一个数组
Array.from(),Array.of(数组元素可选) 将字符串/类数组转换成一个数组
常用的数组api
Array.prototype.push()
push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度。
pop() 方法用于删除数组的最后一个元素并返回删除的元素。
shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。
unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度。
reverse() 方法用于颠倒数组中元素的顺序。
sort() 方法用于对数组的元素进行排序。字符以ASC码的排序。
结合sort() 实现升降序:
arr.sort(function(a,b){
return b-a;(降序,升序a-b)
})
concat() 方法用于连接两个或多个数组。返回被连接数组的一个副本。
slice()方法可提取字符串的某个部分,并以新的字符串返回被提取的部分。不修改原数组
array.slice(start(可选), end(可选)):
start:如果是负数,就是倒数第几个
end:如果没有指定,那么就从开始到最后一个元素。注意:不包含end
splice() 方法用于添加或删除数组中的元素。这种方法会改变原始数组。
array.splice(index,howmany,item1,.....,itemX)
参数 | 描述 |
---|---|
index | 必需。规定从何处添加/删除元素。 该参数是开始插入和(或)删除的数组元素的下标,必须是数字。 |
howmany | 可选。规定应该删除多少元素。必须是数字,但可以是 “0”。 如果未规定此参数,则删除从 index 开始到原数组结尾的所有元素。 |
item1, …, itemX | 可选。要添加到数组的新元素 |
indexOf()/lastIndexOf() 方法可返回数组中某个指定的元素位置。
如果找到一个 item,则返回 item 的第一次出现的位置,否则就返回-1。开始位置的索引为 0。
lastIndexOf() 方法可返回一个指定的元素在数组中最后出现的位置,从该字符串的后面向前查找。
every() 方法用于检测数组所有元素是否都符合指定条件(通过函数提供)。返回一个Boolean值,全部满足条件才能输出true
array.every(function(currentValue(可自定义命名,但含义不变),index,arr), thisValue(可选))
**注意:** every() 不会对空数组进行检测。
**注意:** every() 不会改变原始数组。
注意:当有第二个参数thisValue时,第一个参数function的函数this的指向将改变成指向第二个参数,即this=thisValue;若不传该参数时,当前的function函数的this指向global
some() 方法用于检测数组中的元素是否满足指定条件(函数提供)。同样返回Boolean值,有一个满足则返回true
array.some(function(currentValue,index,arr),thisValue (可选) )
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。
forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。没有返回值
正则表达式
格式: /正则表达式/ 例 :/^a/
模式修饰符
模式修饰符写在最后
i:ignoreCase,匹配时忽视大小写
m:multiline,多行匹配
g:global,全局匹配:
1)如果正则表达式中有修饰符"g",这时,在正则表达式的实例reg中会维护lastIndex 属性,记录下一次开始的位置,当第二次执行exec的时候,从lastIndex开始检索。
2)如果正则表达式中没有修饰符"g",不会维护lastIndex属性,每次执行从开始位置检索,就会直接每次都是返回第一次匹配到的位置
正则表达式实例方法
exec
可用来匹配字符串中符合正则表达式的字符串,返回值是一个result数组: [匹配的内容,index: 在str中匹配的起始位置,input: 参数字符串,groups: undefined]
例:
// 如果是全局模式的正则验证 还可以使用循环进行输出
var str = 'hello world hello';
var reg = /hello/g;
while(true){
var result = reg.exec(str);
if(!result){
break;
}
console.log(result[0],result["index"],reg.lastIndex);
}
test
用来测试待检测的字符串中是否有可以匹配到正则表达式的字符串,如果有返回true,否则返回false
正则表达式的语法
[字符] 匹配任意里面的字符
只要在包含里面的任意字符,那么就返回true
反义字符 [^字符]
在字符串里,除了中括号里面的所有东西,都返回true;
边界符 ^【字符】 开始
^[字符] 以谁开始 例 /^1/g :以1开始字符串返回true 或 /^[0-9]/g 以数字开始的字符串
边界符 $ 结尾
例: /abc$/g : 以abc结尾的字符串
边界符结合
例: /^abc$/g : 只能检测到abc,abcabc都不能满足匹配,是一种精确匹配
/^[abc]$/g :相当于 /^a$/g , /^b$/g, /^b$/g,所以只有a,b,c三种字符串符合条件。
字符类
字符类 | 含义 |
---|---|
. | 匹配除换行符\n和回车符之外的任何单个字符,等效于**[^\n\r]** |
\d | 匹配一个数字字符,等效于[0-9] |
\D | [^0-9] |
\w | 匹配包括下划线的任何单个字符,包括AZ,az,0~9和下划线**""**,等效于 [a-zA-Z0-9] |
\W | [^a-zA-Z0-9_] |
\s | 匹配任何Unicode空白字符,包括空格、制表符、换页符等,等效于[\f\t\n\r] |
\S | [^\f\t\n\r] |
数量词
字符 | 含义 |
---|---|
* | >=0次 |
+ | ≥1 次 |
? | 0或1次 |
{n} | n 次 |
{n,} | ≥n 次 |
{n,m} | n到m 次 |
重复方式
贪婪模式
尽可能多的匹配(首先取最多可匹配的数量为一组进行匹配),当匹配剩余的字符串,还会继续尝试新的匹配,直到匹配不到为止,为默认模式。
例:var reg = /\d{3,6}?/g; 会最多取到六位
非贪婪模式 ?
尽可能少的匹配(每次取最少匹配的数量为一组进行匹配),直到匹配不到为止
**使用方法**:在量词后加上 **?**
例:var reg = /\d{3,6}?/g; 会最多取到三位
前瞻表达式
正向前瞻 ?=exp
根据后面的表达式来决定前面的是否匹配。
例: var reg= /H(?=i)/ : 会匹配到H后面有i的H,
注意:js不支持后瞻
负向前瞻 ?!exp
例:var reg = /H(?!i)/ :会匹配到H后面没有i的H;
选择 分组 以及 引用
选择 |
例:
var reg = /qq|163/
选择qq或者163,主要是给匹配提供两个选择,如果第一个匹配选项出现,则后面的选项将不再匹配,如 str= ‘163qq’,执行一次匹配那么会返回163。
分组 ()
用括号括起来的正则表达式为一个分组,分组之后,每个符合规则的字符串会存放到分组中,可以用 正则表达式变量名.$第几个分组 进行访问被该分组捕获的字符串。一个括号代表一个分组,从左往右开始算括号
例:
var reg = /((\d{4})-(\d{2})-(\d{2}))/ 大括号括起来的是第一个分组 reg.$1; 依次类推,\d{4} 为匹配的第二个分组。
引用
可以用 \分组序号 来引用被分组捕获的字符,
注意:引用是指表达式以及结果相同
例:
var reg = /(\w{3}) is \1/ --》\1就是引用\w{3}这个分组表达式捕获的字符串,二者完全相同。
字符串支持正则参数的几个方法
search
查找字符串中是否有匹配正则的字符串,有则返回字符串第一次出现时的位置,无则返回null
正则中无论是否有全局匹配都不会影响返回结果
match
匹配字符串中符合正则表达式的字符串,并返回该字符串的一个数组,其中包括字符串内容、位置
如果正则设置全局匹配,则一次性返回所有符合正则表达式的字符串数组
如果其中添加了分组,返回符合要求的字符串以及分组的一个数组,但如果同时开启全局匹配则不会在数组中添加分组内容
split(reg)
以匹配到的正则字符串来进行字符串分割
replace(reg,replaceword)
以匹配到的正则字符,替换成
面向对象
基本包装数据类型
基本三种数据类型String Number Boolen,只要被调用,js会自动创建一个实例对象(就是基本包装数据类型的对象 -> var str = new String();),那么就可以调用对象方法。如:str.substring();
三个步骤:
1.自动创建 String 类型的一个实例(和基本类型的值不同,这个实例就是一个基本包装类型的对象)
2.调用实例(对象)上指定的方法
3.销毁这个实例,调用完之后立即销毁。
基本包装类型的操作流程类似于隐式,不需要手动去转换
JS的工厂模式
一种设计模式
- 作用 :创建对象,降低代码冗余度
使用工厂模式创建对象
//将创建对象的代码封装在一个函数中
function createPerson(name, age, gender) {
var person = new Object();
person.name = name;
person.age = age;
person.gender = gender;
person.sayName = function () {
console.log(this.name);
}
return person;
}
//利用工厂函数来创建对象
var person1 = createPerson("zhangsan", 18, 'male');
var person2 = createPerson("lisi", 20, 'female');
优缺点
优点:批量创建对象,提高代码复用率;
缺点:
当工厂函数里面有方法时,那么在实例化时,每个实例创建一个方法,比较浪费内存空间,当方法放在外部时,无法区分该方法属于哪个对象中,任何对象都可以调用该方法,且造成方法冗余。无法区分对象的种类,给它创建的实例都是Object的实例,因为他里面写的是 person = new Object()
构造函数模式
实际上,构造函数也是函数的一种,如果直接调用构造函数,那么里面的东西的保存在global里面。例:Person() 直接调用。this指向的是global里面。
调用new关键字来创建实例对象。
构造函数创建对象
// 自定义构造函数
function Person(name, age, gender) {
this.name = name;// 2.this 指向person实例对象
this.age = age; // 3.执行函数体,给p1,p2做个赋值
this.gender = gender;
this.sayName = function () {
console.log(this.name);
}
}
var person1 = new Person('zhangsan', 29, 'male');//1.创建一个Person()的实例对象,
// 4.返回person实例对象
var person2 = new Person('lisi', 19, 'female');
person1.sayName(); // zhangsan
person2.sayName(); // lisi
new 关键字的操作(重点)
- 创建一个实例对象 Person()的实例对象
- this 指向person实例对象
- 执行函数体,给p1,p2做个赋值
- 返回person实例对象。
使用构造函数与工厂模式的区别
-
没有显式地创建对象。
-
属性和方法直接赋值给了 this。
-
没有 return。
另外,要注意函数名 Person 的首字母大写了。按照惯例,构造函数名称的首字母都是要大写的,非构造函数则以小写字母开头。这是从面向对象编程语言那里借鉴的,有助于在 ECMAScript 中区分构造函数和普通函数。毕竟 ECMAScript 的构造函数就是能创建对象的函数。
优缺点
优点:可以批量创建对象,且可以区分种类
缺点:方法冗余
注意事项
instantof 用于检测是否在同一个原型链上
原型模式
将所有的属性和方法都维护到我们的原型对象(prototype)中,达成共享的目的,在我们的构造函数中,不存放任何的东西, 一般不建议使用
原型创建对象
function Person(){}
Person.prototype.name = "zhangsan";
Person.prototype.age = 29;
Person.prototype.gender = "male";
Person.prototype.sayName = function () {
console.log(this.name);
};
//更简单的原型赋值
Person.prototype = {
name: "zhangsan",
age: 29,
gender: "male",
sayName() {
console.log(this.name);
}
};//注意,用该方法进行赋值后,他属于Object,相当于操作了Person.prototype = new Object()
var person1 = new Person();
person1.sayName(); // zhangsan
var person2 = new Person();
person2.sayName(); // zhangsan
console.log(person1.sayName == person2.sayName); // true
优缺点
组合模式
是构造函数与原型模式组合到一起
将实例的私有的属性和私有方法放在构造函数中
将公共的属性与公共方法放在原型对象中。
创建对象
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
this.firends = ['zhangsan', 'lisi'];
}
Person.prototype = {
constructor: Person,//更改constructor,不更改的话指向的是Object。
sayName: function () {
console.log(this.name);
}
};
var p1 = new Person('larry', 44, 'male');
var p2 = new Person('terry', 39, 'male');
p1.firends.push('robin');
console.log(p1.firends); // [ 'zhangsan', 'lisi', 'robin' ]
console.log(p2.firends); // [ 'zhangsan', 'lisi' ]
console.log(p1.firends === p2.firends); // false
console.log(p1.sayName === p2.sayName); // true
原型链继承
原型的继承关系组成原型链。
原型继承
万物皆为Object对象,默认引用数据类型都是继承Object原型,不需要我们手动继承。其他自定义的构造函数的继承需要我们手动去继承。
// 创建Animal
function Animal () {
this.name = 'animal';
// this --> Animal { name: 'animal' }
}
Animal.prototype.getAnimalName = function () {
console.log(this.name + 'getAnimalName');
}
// 创建Dog
function Dog () {
this.name = 'dog';//存在实例d1中 类似于d1.name;
// this --> Animal { name: 'dog' }
}
// Dog继承自Animal 将Animal的实例赋值给Dog的原型对象,相当于将Animal的实例中的__proto__赋值给了Dog的原型对象
// 如此 Dog原型对象 就能通过 Animal 对象的实例中的[[prototype]](__proto__) 来访问到 Animal原型对象 中的属性和方法了。
Dog.prototype = new Animal();
// 不建议使用Dog.prototype.__proto__=== Animal.prototype,因为双下划线的属性是js中的内部属性,各个浏览器兼容性不一,不建议直接操作属性,ES6中提供了操作属性的方法可以实现。
console.log(Dog.prototype.__proto__ === Animal.prototype);//true
// 在使用原型链继承的时候,要在继承之后再去原型对象上定义自己所需的属性和方法
Dog.prototype.getDogName = function () {
console.log(this.name + 'getDogName');
}
var d1 = new Dog();
var d2 = new Animal();
d2.getAnimalName();//animal
d1.getAnimalName()// 继承下来的方法,从下往上沿着原型链查找,找到getAnimalName方法存在于Animal.prototype中,
//Animal.prototype中的this指向是Animal.prototype的实例Dog.prototype,由于Dog.prototype的this指向的是他的实例Dog,那么拿到的this.name是dog;
d1.getDogName()
//d1 是Animal的实例
以上代码可知,由于执行了Dog.prototype = new Animal();那么Dog.prototype为Animal的一个实例,那么Dog.prototype.__proto__指向Animal.prototype,由于Dog.prototype的重新指向,那么Dog.prototype的constructor指针将不再指向Dog这个构造函数,而是指向Animal的这个构造函数。
instanceof与 isPrototypeOf() 确定原型链中的继承
- instanceof
instanceof运算符用于检测构造函数的prototype
属性是否出现在某个实例对象的原型链上。
console.log(d1 instanceof Object); //true
console.log(d1 instanceof Animal); //true
console.log(d1 instanceof Dog); //true - isPrototypeOf()
只要原型链中包含这个原型,这个方法就返回 true:
console.log(Object.prototype.isPrototypeOf(d1)); // true
console.log(Animal.prototype.isPrototypeOf(d1)); // true
console.log(Dog.prototype.isPrototypeOf(d1)); // true
子构造函数与父构造函数的方法问题
子构造函数的方法应该写在继承后面,否则会被继承重写,判断无法找到该方法或属性。
且运用简单赋值(Person.prototype = {})会破坏原型链,重新指向Object;重写该原型的所有东西
原型链的问题
由于原型中引用数据类型会被所有实例共享,因为引用数据类型是浅拷贝。解决办法是把引用数据类型放在构造函数中定义,但是在继承中,原型作为别人的实例对象,那么原型中的属性就是实例属性,但原型下面也有实例,那么该原型的属性既是原型属性也是实例属性
例子
function Animal() {
this.categorys = ["cat", "rabbit"];
}
function Dog() { }
// 继承 Animal
Dog.prototype = new Animal();
var d1 = new Dog();
d1.categorys.push("dog");
console.log(d1.categorys); // [ 'cat', 'rabbit', 'dog' ]
var d2 = new Dog();
console.log(d2.categorys); // [ 'cat', 'rabbit', 'dog' ]
在这个例子中,Animal 构造函数定义了一个 categorys 属性,其中包含一个数组(引用值)。每个Animal 的实例都会有自己的 categorys 属性,包含自己的数组。但是,当 Dog 通过原型继承Animal 后,Dog.prototype 变成了 Animal 的一个实例,因而也获得了自己的 categorys属性。这类似于创建了Dog.prototype.categorys s属性。最终结果是,Dog 的所有实例都会共享这个 categorys 属性。这一点通过d1.categorys 上的修改也能反映到 d2.categorys上就可以看出来。且子类型的实例无法传参到父类型的构造函数
经典继承
基于以上原型链的问题,提出的一种解决办法,也称对象伪装,即把
function Animal() {
this.categorys = ["cat", "rabbit"];
}
function Dog() {
// 继承 Animal
Animal.call(this);
}
在var d1 = new Dog()时,是d1调用Dog构造函数,所以其内部this的值指向的是d1,所以Animal.call(this)就相当于Animal.call(d1),就相当于d1.Animal()。最后,d1去调用Animal方法时,Animal内部的this指向就指向了d1。那么Animal内部this上的所有属性和方法,都被拷贝到了d1上。所以,每个实例都具有自己的categorys属性副本。他们互不影响。
var d1 = new Dog();
d1.categorys.push("dog");
console.log(d1.categorys); // [ 'cat', 'rabbit', 'dog' ]
var d2 = new Dog();
console.log(d2.categorys); // [ 'cat', 'rabbit' ]
子类构造函数中向父类构造函数传参。
function Animal(name) {
this.name = name;
}
function Dog() {
// 继承 Animal 并传参
Animal.call(this, "zhangsan");
// 实例属性
this.age = 29;
}
var d = new Dog();
console.log(d.name); // zhangsan
console.log(d.age); // 29
在这个例子中,Animal 构造函数接收一个参数 name,然后将它赋值给一个属性。在 Dog构造函数中调用 Animal 构造函数时传入这个参数,实际上会在 Dog 的实例上定义 name 属性。为确保 Animal 构造函数不会覆盖 Dog 定义的属性,可以在调用父类构造函数之后再给子类实例添加额外的属性。
问题
1.创建的实例并不是父类的实例,只是子类的实例。
2.没有拼接原型链,不能使用instanceof。因为子类的实例只继承了父类的实例属性/方法,没有继承父类的构造函数的原型对象中的属性/方法。
3.每个子类的实例都持有父类的实例方法的副本,浪费内存,影响性能,而且无法实现父类的实例方法的复用。
组合继承
组合继承(有时候也叫伪经典继承)综合了原型链和经典继承函数,将两者的优点集中了起来。基本的思路是使用原型链继承原型上的属性和方法,而通过经典继承函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。
function Animal(name) {
this.name = name;
this.categorys = ["cat", "rabbit"];
}
Animal.prototype.sayName = function () {
console.log(this.name);
};
function Dog(name, age) {
// 继承属性
Animal.call(this, name);
this.age = age;
}
// 继承方法
Dog.prototype = new Animal();
Dog.prototype.sayAge = function () {
console.log(this.age);
};
var d1 = new Dog("zhangsan", 29);
d1.categorys.push("dog");
console.log(d1.categorys); // [ 'cat', 'rabbit', 'dog' ]
d1.sayName(); // zhangsan
d1.sayAge(); // 29
var d2 = new Dog("lisi", 27);
console.log(d2.categorys); // [ 'cat', 'rabbit' ]
d2.sayName(); // lisi
d2.sayAge(); // 27
在这个例子中,Animal 构造函数定义了两个属性,name 和 categorys,而它的原型上也定义了一个方法叫 sayName()。Dog 构造函数调用了 Animal 构造函数,传入了 name 参数,然后又定义了自己的属性 age。此外,Dog.prototype 也被赋值为 Animal 的实例。原型赋值之后,又在这个原型上添加了新方法 sayAge()。这样,就可以创建两个 Dog 实例,让这两个实例都有自己的属性,包括 categorys,同时还共享相同的方法。
组合继承弥补了原型链和经典继承函数的不足,是 JavaScript 中使用最多的继承模式。而且组合继承也保留了 instanceof 操作符和 isPrototypeOf()方法识别合成对象的能力。
DOM(Document Object Model)
作用:要改变页面的某个东西,JS就需要获得对网页中所有元素进行访问的入口。这个入口,连同对HTML元素进行添加、移动、改变或移除的方法和属性,都是通过DOM来获得的。
编程范式是一种命令式编程:
打印的几种方式
info
debug
log
操作节点
节点的基本操作
获取节点getElementById
格式: document.getElementById('ID')
创建节点createElement
格式:
document.createElement('标签')
给节点添加内容或样式 innerHTML style
document.getElementById('ID').innerHTML = '添加文本'
document.getElementById('ID').style.css属性 = ‘样式' : document.getElementById('ID').style.cloro = 'red'
将节点添加到页面对应的结构中 appendChild
节点1.appendChild( 节点2); 把节点2插入到节点1中
其他方法
cloneNode() 克隆节点
var dupNode = node.cloneNode(deep);
返回node节点的一个副本dupNode;
deep 可选是否采用深度克隆,如果为true,则该节点的所有后代节点也都会被克隆,如果为false,则只克隆该节点本身。
节点层级
text为文本节点,即单纯的文字
节点树
除了根节点以外,其他节点对于周围的节点都存在三种关系。
-
父节点关系(parentNode):直接的那个上级节点。
-
子节点关系(childNode):直接的下级节点。
-
同级节点关系(sibling):拥有同一父节点的节点。
DOM提供操作接口,用来获取三种关系的节点。其中,子节点接口包括firstChild
(第一个子节点)和lastChild
(最后一个子节点)等属性,同级节点接口包括nextSibling
(紧邻在后的那个同级节点)和previousSibling
(紧邻在前的那个同级节点)属性。
Node 类型
node类型的方法可以继承给下面的所有东西。是最顶层的类型,纯文本,标签,注释,文档,属性等都是节点。可以说,所有的东西都是一个节点
属性
nodeType 返回一个整数值,表示节点的类型
nodeName 返回节点的名称 如DIV等
nodeValue 返回一个字符串,表示当前节点本身的文本值
该属性可读写只有文本节点(text)、注释节点(comment)和属性节点(attr)有文本值.
textContent 返回当前节点和它的所有后代节点的文本内容
nextSibling 返回紧跟在当前节点后面的第一个同级节点。
如果当前节点后面没有同级节点,则返回null
(**注意**:可能会获取到“空格”或“回车”这样的文本节点)
previousSibling 返回当前节点前面的、距离最近的一个同级节点。
.parentNode 返回当前节点的父节点
对于一个节点来说,它的父节点只可能是三种类型:元素节点(element)、文档节点(document)和文档片段节点(documentfragment),最顶层的父节点是 #document
.parentElement 返回当前节点的父元素节点
最顶层的父元素是<html>
firstChild和lastChild 回当前节点的第一个子节点与最后一个子节点
childNodes 返回一个类似数组的对象(NodeList集合),成员包括当前节点的所有子节点
方法
appendChild 插入子节点
appendChild方法接受一个节点对象作为参数,将其作为最后一个子节点,插入当前节点。该方法的返回值就是插入文档的子节点。
insertBefore() 插入父节点内部的指定位置
insertBefore方法用于将某个节点插入父节点内部的指定位置。
insertBefore方法接受两个参数,第一个参数是所要插入的节点newNode,第二个参数是父节点parentNode内部的一个子节点,
即将newNode插入到parentNode之前
removeChild() 从当前节点移除该子节点。
removeChild方法接受一个子节点作为参数,用于从当前节点移除该子节点。
返回值是移除的子节点。
replaceChild() 替换当前节点的某一个子节点
replaceChild方法用于将一个新的节点,替换当前节点的某一个子节点。
var replacedNode = parentNode.replaceChild(newChild, oldChild);
document类型
属性 document.
documentElement 始终指向HTML页面中的元素。
body 直接指向元素
doctype 访问<!DOCTYPE>, 浏览器支持不一致,很少使用
title 获取文档的标题
URL 取得完整的URL
domain 取得域名,并且可以进行设置,在跨域访问中经常会用到。
即用服务器打开,就会有域名,即ip地址,用本地服务器获取不到
referrer 取得链接到当前页面的那个页面的URL,即来源页面的URL。
要从一个页面跳转到这个页面才会有页面来源,获取页面来源的ip地址,用本地服务器不能获取到url
images 获取所有的img对象,返回HTMLCollection类数组对象
forms 获取所有的form对象,返回HTMLCollection类数组对象
links 获取文档中所有带href属性的a标签元素
innerHTML 属性 获取元素内容
innerHTML 属性可用于获取或替换 HTML 元素的内容。
innerHTML 属性可用于获取或改变任何 HTML 元素,包括 <html> 和 <body>。
方法
document.write 写入
向文档中写上文本或者是代码都行
getElementById 方法 返回匹配指定 id 的一个元素。
getElementsByTagName() 匹配指定标签名的所有元素。
返回一个`HTMLCollection`(伪数组),可以通过下标来获取指定内容
getElementsByClassName() 包含匹配指定类名的所有元素。
同样会返回一个伪数组
document.querySelector() 返回文档中匹配指定的CSS选择器的第一元素
例:
<div id="div1">我是一个div</div>
<div id="div1">我是一个div</div>
<script>
document.querySelector("#div1").innerHTML = "Hello World!"; //返回第一个div标签
</script>
document.querySelectorAll() 返回文档中匹配的CSS选择器的所有元素节点列表
可以用下标访问,类似于类名匹配
document.createElement(element) 创建元素
Element类型
Element 对象对应网页的 HTML 元素。每一个 HTML 元素在 DOM 树上都会转化成一个Element节点对象。最大的element元素是html
属性
attributes:返回一个与该元素相关的所有属性的集合。
classList:返回该元素包含的 class 属性的集合。
className:获取或设置指定元素的 class 属性的值。
clientHeight:获取元素内部的高度,包含内边距,但不包括水平滚动条、边框和外边距。
clientTop:返回该元素距离它上边界的高度。
clientLeft:返回该元素距离它左边界的宽度。
clientWidth:返回该元素它内部的宽度,包括内边距,但不包括垂直滚动条、边框和外边距。
innerHTML:设置或获取 HTML 语法表示的元素的后代。
tagName:返回当前元素的标签名。
方法
element.innerHTML 设置或获取HTML语法表示的元素的后代。
可以识别代码片段,会获取代码标签
element.attribute 修改已经存在的属性的值
element.getAttribute() 返回元素节点的指定属性值。
element.setAttribute(attribute, value) 把指定属性设置或更改为指定值。
element.style.property 设置或返回元素的 style 属性。
TEXT类型
Text 节点由 Text 类型表示,包含按字面解释的纯文本,也可能包含转义后的 HTML 字符,但不含 HTML 代码。
属性及方法
length
文本长度
appendData(text)
追加文本
deleteData(beginIndex,count)
删除文本
insertData(beginIndex,text)
插入文本
replaceData(beginIndex,count,text)
替换文本
splitText(beginIndex)
从beginIndex位置将当前文本节点分成两个文本节点
document.createTextNode(text)
创建文本节点,参数为要插入节点中的文本
substringData(beginIndex,count)
从beginIndex开始提取count个子字符串
DOM事件机制
事件流
即事件执行的顺序 ,分为事件捕获与事件冒泡,事件捕获-> 事件处理-> 事件冒泡
事件冒泡(IE事件流)
当父子元素都绑定相同事件后,点击子元素,父元素的事件也会被触发
阻止事件冒泡
在想要阻止冒泡的事件中添加 event.stopPropagation() 即可
DOM完整的事件流
前面的捕获阶段在div出现结束,进行冒泡阶段
事件绑定
在事件绑定时,请确保绑定事件的节点已经存在,否则事件无效
在html中选中节点,绑定事件 (事件处理程序)
格式:
<button onclick="showMsg()">点我啊</button> //方法绑定一定要加小括号!
<script>
function showMsg() {
console.log('Hello Wolrd!');
}
通过DOM0 (事件处理程序)
事件不可以追加
绑定事件
即在js里获取节点再绑定
格式:
var btn = document.getElementById('btn');
btn.onclick = function () {
console.log('我被点击了');
console.log(this); //this 指向btn
}
移除事件
格式: btn,onclick = null
DOM2 (事件处理程序)
事件可以追加
addEventListener()和removeEventListener()。
参数:addEventListener('事件名’,事件处理函数,Boolean),
Boolean:true表示在捕获阶段调用事件处理程序,false(默认值)表示在冒泡阶段调用事件处理程序。
绑定事件
即绑定事件监听
格式:
<button id='btn'>点我啊</button>
<script>
var btn = document.getElementById("btn");
btn.addEventListener("click", function () {
console.log('我被点击了');
console.log(this); //this 指向btn
}, false);
</script>
事件移除
格式:
removeEventListener('事件名',要移除的函数名)
事件对象event
事件对象event 里面包含了事件的所有的东西,如事件的元素,事件类型,事件的变量等。
event 对象只在事件处理程序执行期间存在,一旦执行完毕,就会被销毁。
event.target 与 event.currenttarget 的区别
event.target ->触发目标事件的源头,
event.currenttarget -> 当前正在执行的元素
如 在事件冒泡中,冒泡的源头就是 event.target , 冒泡事件在冒泡时,冒泡到父元素 那么 event.currenttarget 是当前执行事件的父元素,但是event.target 依然等于它的子元素
阻止默认事件的发生
默认事件如a标签的跳转事件,表单默认的提交行为等
event.preventDefault();
事件委托
如果当前的结构为一个父元素有多个子元素的结构时,利用事件冒泡的原理把子元素的事件绑定到父元素上,可以判断子元素的来源,来决定做什么事件。
例:
<ul id="myLinks">
<li id="li1">Go somewhere</li>
<li id="li2">Do something</li>
<li id="li3">Say hi</li>
</ul>
<script>
var list = document.getElementById("myLinks");
list.addEventListener("click", function (event) {
var target = event.target;//获取事件的来源,目标值
console.log(target);
switch (target.id) {
case "li1":
target.innerHTML = 'SuZhou';
break;
case "li2":
target.innerHTML = 'Coding';
break;
case "li3":
target.innerHTML = 'Hi';
break;
}
});
</script>
优点
-
document 对象随时可用,任何时候都可以给它添加事件处理程序(不用等待 DOMContentLoaded或 load 事件)。这意味着只要页面渲染出可点击的元素,就可以无延迟地起作用。
-
节省花在设置页面事件处理程序上的时间。只指定一个事件处理程序既可以节省 DOM 引用,也可以节省时间。
-
减少整个页面所需的内存,提升整体性能。
最适合使用事件委托的事件包括:click、mousedown、mouseup、keydown 和 keypress。
目的
1.减少dom操作就会减少浏览器的重排与重绘
重排与重绘
重排:
元素的位置发生改变,那么就会触发浏览器的重排,即重新排布
display:none ,添加删除,添加动画,样式。 用户行为
重绘:
当元素的外观发生改变,但是位置不变,那么就是重绘。
visibility: hidden 用户行为
总结:
重排与重绘都会消耗内存与消耗cpu,重绘不一定会重排,但重排一定会重绘
事件类型
所有的事件在设置时,要加上 on 修饰符 例:onload
用户界面事件(UIEvent):涉及与 BOM 交互的通用浏览器事件。
load
监听某些事情是否加载完毕 例如 window.onload img.onload ,加载完毕后触发
格式:
window.onload = function () {
console.log('onload');
}
unload
当页面完全卸载后在window上触发,
当所有框架都卸载后在框架集上触发,
当嵌入的内容卸载完毕后在<object>上触发。
select
在文本框(<input>或 textarea)上当用户选择了一个或多个字符时触发。
<input type="text" id="inp">
<script>
var inp = document.getElementById('inp');
inp.onselect = function (event) {
console.log(event);
// 可以通过window.getSelection()获取到选中的部分
console.log(window.getSelection().toString());
}
</script>
resize
在 window 或窗格上当窗口或窗格被缩放时触发。
scroll
当用户滚动包含滚动条的元素时在元素上触发。<body>元素包含已加载页面的滚动条。
焦点事件(FocusEvent)
blur
当元素失去焦点时触发。这个事件不冒泡,所有浏览器都支持。
focus
当元素获得焦点时触发。这个事件不冒泡,所有浏览器都支持。
focusin
当元素获得焦点时触发。这个事件是 focus 的冒泡版。
focusout
当元素失去焦点时触发。这个事件是 blur 的冒泡版。
鼠标事件(MouseEvent) 与滚轮事件(WheelEvent)**
click
在用户单击鼠标主键(通常是左键)或按键盘回车键时触发。
dblclick
在用户双击鼠标主键(通常是左键)时触发。
mousedown
在用户把鼠标光标从元素内部移到元素外部时触发。
这个事件不能通过键盘触发。
mouseenter
在用户把鼠标光标从元素外部移到元素内部时触发。这个事件不冒泡,
mouseleave
在用户把鼠标光标从元素内部移到元素外部时触发。这个事件不冒泡,
mousemove
在鼠标光标在元素上移动时反复触发。这个事件不能通过键盘触发。
mouseout
在用户把鼠标光标从一个元素移到另一个元素上时触发。
移到的元素可以是原始元素的外部元素,也可以是原始元素的子元素。这个事件不能通过键盘触发。
mouseover
在用户把鼠标光标从元素外部移到元素内部时触发。这个事件不能通过键盘触发。
mouseup
在用户释放鼠标键时触发。这个事件不能通过键盘触发。
mousewheel
鼠标滚轮事件
键盘事件(KeyboardEvent)与输入事件(InputEvent)
keydown,
用户按下键盘上某个键时触发,而且持续按住会重复触发。
keypress
用户按下键盘上某个键并产生字符时触发,而且持续按住会重复触发。Esc 键也会触发这个事件。DOM3 Events 废弃了 keypress 事件,而推荐 textInput 事件。
keyup
用户释放键盘上某个键时触发,不能规定按哪个键,用户不松手就不会触发
textInput
只能使用addEventListener绑定
BOM
window对象
在浏览器中所有的对象中都用window作为global对象。
window窗口
窗口的位置
screenLeft和screenTop
属性返回窗口相对于屏幕的X和Y坐标。(火狐浏览器不支持)
screenX和screenY
属性返回窗口相对于屏幕的X和Y坐标。(ie浏览器不支持,火狐可以使用此属性)
pageXOffset
设置或返回当前页面相对于窗口显示区左上角的 X 位置。
pageYOffset
设置或返回当前页面相对于窗口显示区左上角的 Y 位置。
注意:IE8及更早IE版本不支持该属性,但可以使用 “document.body.scrollLeft” 和 “document.body.scrollTop” 属性 。
窗口的大小
innerWidth 与 innerHeight 页面视图区的宽度/高度
outerWidth 与 outerHeight 浏览器窗口的宽度/高度
screen对象
屏幕总宽度/高度(像素单位):
screen.width
screen.height
可用宽度/高度(像素单位):
screen.availWidth
screen.availHeight
颜色深度:
screen.colorDepth
颜色分辨率:
screen.pixelDepth
Window open()
通过js代码的方式打开或访问一个新的url 窗口,a标签需要手动打开它不用。一般不用,会被浏览器拦截。
参数
格式: Window open('www.baidu.com','_blank','width = 200px,height = 100px')
1.url:跳转的地址
2.'_blank':打开方式,可选
3.specs:可选 'width = 200px,height = 100px'
4.replace :
调整窗口
格式:w = window.open('url');
w.close()//关闭窗口
大小 w.resizeTo(w,h)
位置 w.moveTo(x,y)
系统对话框
alert() 警告框
是一个同步执行的东西,弹出框之后,会阻塞我们的进程,即我们要操作这个对话框之后再进行下一步的进程操作,它只接受一个参数,即显示的信息,只有一个ok确认按钮
格式
alert('我是警告框')
confirm() 确认框
相较于警告框有取消已经确认按钮,可以通过判断按钮来做相应额操作
格式
<script>
var result = confirm('你确定吗?');
if (result) {
console.log('确定');
}else{
console.log('取消');
}
</script>
prompt() 提示框
提示框的用途是提示用户输入消息,除了 OK 和 Cancel 按钮,提示框还会显示一个文本框,让用户输入内容。prompt()方法接收两个参数:要显示给用户的文本,以及文本框的默认值(可以是空字符串)。如果用户单击了 OK 按钮,则 prompt()会返回文本框中的值。如果用户单击了 Cancel 按钮,或者对话框被关闭,则 prompt()会返回 null。
格式
<script>
var result = prompt("你叫什么名字?");
if (result) {
console.log(result);
}else{
console.log('取消');
}
</script>
location
location既是window的对象也是document的对象。提供了与当前窗口中加载的文档有关的信息,还提供一些导航功能。
格式
属性:location.host
方法:location.reload();
属性
host 返回服务器名称和端口号
hostname 返回不带端口号的服务器名称
href 返回当前加载页面的完整URL
pathname 返回URL的目录和文件名
port 返回URL中指定的端口号
protocol 返回页面使用的协议
search 返回URL的查询字符串。这个字符串以问号开头
方法
assign()
传递一个url参数,打开新url,并在浏览记录中生成一条记录。 可以回退到之前的url
replace()
参数为一个url,结果会导致浏览器位置改变,但不会在历史记录中生成新记录。即把当前的地址直接替换掉 ,不能返回回去之前的url
reload()
重新加载当前显示的页面,参数可以为boolean类型,默认为false,表示以最有效方式重新加载,可能从缓存中直接加载。如果参数为true,强制从服务器中重新加载。就是调用小刷新按钮,可以不传参。
history对象
该对象保存着用户上网的历史记录。出于安全方面的考虑,开发人员无法得知用户浏览过的URL,不过借由用户访问过的页面列表,同样可以在不知道实际URL的情况下实现后退前进,注意: 没有应用于History对象的公开标准,不过所有浏览器都支持该对象。
格式:
history.back();
length
返回历史列表中的网址数
注意:Internet Explorer和Opera从0开始,而Firefox、Chrome和Safari从1开始。
back()
加载 history 列表中的前一个 URL
forward()
加载 history 列表中的下一个 URL
go()
加载 history 列表中的某个具体页面,负数表示向后跳转,正数表示向前跳转。
间歇调用和超时调用
可能会造成内存泄漏
setTimeout(function(){},毫秒) 超时调用
在某一段时间之后,调用该函数,只会调用一次
setInterval() 间歇调用
每隔一段时间,调用该函数,函数会执行很多次。参数与超时调用一样
销毁超时调用与间歇调用 clearTimeout
例:var s = setTimeout(function(){},1000)
clearTimeout(s);
防抖和节流
如果事件处理函数用的频率无限制,会加重浏览器和服务器的负担,
此时我们就可以用防抖(debounce)和节流(throttle)的方式来减少调用频率,
同时又不影响实际效果。如 监听输入字符时。
函数防抖
单位时间内,频繁触发一个事件,以最后一次触发为准。
<input type="text" name="" id="inp">
<script>
//输入框事件
//声明全局变量存储定时器ID
var timeID = null
// 获取输入框节点
var inp = document.getElementById('inp')
// 输入框输入事件
inp.oninput = function () {
//1.先清除之前的定时器
clearTimeout(timeID)
//2.开启本次定时器
timeID = setTimeout(() => {
console.log(`输入的内容是${this.value}`)
}, 1000)//超过1秒钟之后才会进入到该函数,但是在用户连续输入时,没超过一秒中就无法进入该函数,已经被清除掉了。
}
</script>
函数节流
在一定时间内,频繁触发一个函数,只会触发一次。
<button id="btn">点我呀</button>
<script>
//声明一个全局变量存储触发时间
let lastTime = null
// 获取按钮节点
var btn = document.getElementById('btn')
//页面滚动事件
btn.onclick = function () {
//1.每一次触发 先获取本次时间戳
let currentTime = Date.now()
//2.判断当前时间 与 上次触发时间 是否超过间隔
if (currentTime - lastTime >= 3000) {
console.log(this.innerHTML)
//3.存储本次的触发时间
lastTime = currentTime
}
}
</script>
简单来说:你对事件3秒内执行10次的话,防抖是3秒结束后执行最后一次事件,而节流的话,假如指定节流时间1秒,那就是3秒你点了10次也只会执行3次。
AJAX
用于客户端与服务器端发送请求,是一个异步操作。发送http协议
XMLHttpRequest
是一个原生js的一个网络请求,通过它创建的实例来发送请求
发送请求的步骤
1.创建一个XMLHttpRequest的实例对象
var request = new XMLHttpRequest();
2.配置请求方式,设置请求接口地址
open
request.open(method,url,boolean)
参数 :
method :取值 get/post
url: 取值为请求的地址, 注意跨域请求会报错
boolean : true默认值,代表异步请求,false:代表同步请求,用同步请求会阻塞,像alter一样
3.设置请求头 requestHeader()
在请求中有参数的话需要设置
setRequestHeader
格式:request.setRequestHeader(‘Content-type’, '属性值');
发送json格式数据
request.setRequestHeader('Content-type', 'application/json; charset=utf-8');
发送表单数据
request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded; charset=utf-8');
发送纯文本(不指定Content-type时,此是默认值)
request.setRequestHeader('Content-type', 'text/plain; charset=utf-8');
发送html文本
request.setRequestHeader('Content-type', 'text/html; charset=utf-8');
4.发送请求
request.send();//调用send()之后,请求就会发送到服务器
请求发送后,会在控制台获得响应的数据,但在前端看不见
获取响应的注册事件
发送请求后,会收到响应,收到响应后,XHR对象的以下属性会被填充上数据。
-
responseText:作为响应体返回的文本。
-
responseXML:如果响应的内容类型是"text/xml"或"application/xml",那就是包含响应数据的 XML DOM 文档。
-
status:响应的 HTTP 状态。一般200 为成功
-
statusText:响应的 HTTP 状态描述。
-
readyState:返回HTTP请求状态
0 open()尚未调用 UNSENT
1 open()已调用 OPENED
2 接收到头信息 HEADERS_RECEIVED
3 接收到响应主体 LOADING
4 响应完成 DONE -
readystatechange 请求状态改变事件
当readyState值改变为4或服务器的响应完成时,所有的浏览器都触发该事件
5.获取响应返回的数据
request.onreadystatechange = function () {
if (request.readyState === 4 && request.status === 200) {
// console.log(request.responseText);
// 转换为JSON对象
console.log(JSON.parse(request.responseText));
}
}
get有参请求
1.url拼接字符串
2.参数序列化
2.1创建参数对象
var params = {参数:值,参数:值 }
2.2参数序列化
2.21 导入qs ----一个序列化工具
<script src="./qs.js"></script>
2.22 序列化 Qs.stringify(params)
2.23 拼接到url地址 'url?'+ Qs.stringify(params)
post 有参请求
基本操作与get有参请求一样,但参数不拼接到地址栏里,但是在send前根据接口需求需要设置请求头,即:
request.setRequestHeader('Content-type','application/json;charset=utf-8');
request.send(JSON.stringify(params))