浏览器本身不会执行JS代码,而是通过JS引擎解释为机器语言后执行(逐行解释)。
目录
数据类型
① 种类
一般数据类型:Number、String、Boolean、Undefined、 Null;(NaN也属于Number)
引用数据类型:Object,Function、Array。(Function和Array是特殊的对象)
② 判断
typeof:返回字符串类型的数据类型值
let num=1;
console.log(typeof(num)) -> 'number'
console.log(typeof(num)===number)) -> false
console.log(typeof(num)==='number')) ->true
let fn=function(){}
console.log(typeof(fn)==='function')) ->true
console.log(typeof(fn)==='object')) ->false
可以判断的数据类型: undefined、string、number、function;
不可以判断的数据类型:null和object、array和object。(null、array都会输出'object')
//会把array也判断为object
let arr=[];
console.log(typeof(arr)==='object'); ->true
let temp=null;
//会把null也判断为object
console.log(typeof(temp)) ->'object'
解决方法:
console.log(temp!=null&&typeof(temp)==='object'); ->true
instanceof:判断对象的具体类型
let obj={
str1:'直接生成',
str2:new String('用new生成'),
arr:[],
fn:(a,b)=>a+b
}
console.log(obj instanceof Object) -> true
//在判断字符串的时候,如果不是通过new 方式创建的字符串,得到的结果为false
console.log(obj.str1 instanceof String) ->false
console.log(obj.str2 instanceof String) ->true
console.log(obj.arr instanceof Array,obj.arr instanceof Object) -> true true
console.log(obj.fn instanceof Function,obj.fn instanceof Object) -> true true
===:可以判断undefined和null
let n=null;
let m;
console.log(n===null,m===undefined) -> true true
数据类型转换
① 隐式转换
1、‘+’两边只要有一个是字符串,都会把另一个也转为字符串。
let str = 'string' + 1;
console.log(typeof(str)); → String
2、‘+’作为正号时,可将字符串类型的数字转为数字类型。
let num = +'11';
console.log(typeof(num)); → Number
3、除‘+’外的运算符,如‘-’、‘*’等会把字符串类型的数字转为数字类型。
let str1 = '1';
let str2 = '2';
let num = str1 * str2;
console.log(typeof(num)); → Number;
② 显式转换
Number() ,parseInt() ,paraseFloat() 可以过滤px单位 ,String() ,str.toString()
三元运算符(表达式)
条件?满足 : 不满足
let num1=1;
let num2=2;
num1>num2 ? alert('num1>num2') : alert('num1<num2');
数组
数组的概念在多种语言中都有涉及,例如C、C++、Java、Python,所以想必大家对它都不陌生。
① 创建数组
创建方式有两种:字面量创建[ ] 、newArray()。
② 增加元素
增加元素的方式也有两种:array.push(...) 、array.unshift(...)。
① array.push(...) :元素添加至末尾,返回新数组长度。⭐
② array.unshift(...) :元素添加至开头,返回数组新长度。
③ 删除元素
删除元素的方式有三种:array.pop()、array.shift()、array.splice(start , delete_number [, element])
① array.pop() :删除数组最后一个元素,返回该元素。
② array.shift() :删除数组第一个元素,返回该元素。
③ array.splice(起始位,删除数目,添加/替换的元素)。 ⭐
④ 返回元素索引号
1、返回第一个满足的索引号: array.indexOf(要找的内容);
2、返回最后一个满足的索引号:array.lastIndexOf(要找的内容);
⑤ 数组转换为字符串
两种方式:
1、 .toString()
2、 .join(分隔符) //默认为逗号
伪数组
伪数组和数组类似,都有长度length、有索引index,但是伪数组没有方法。
获取dom元素集合的返回的都是伪数组类型:
document.getElementsByTagName()、document.getElementsByClassName()、document.querySelectorAll()
对象
对象Object:一组无序的相关属性和方法的集合,引用数据类型,存地址。
① 创建对象
创建对象有三种方式:利用对象字面量、new Object()、构造函数。
对象字面量:
let obj={
属性名 :属性值,
方法名 :function(){}
}
调用:obj.属性名/obj['属性名'] ; obj.方法名()
new Object():
let obj=new Object();
obj.属性名=属性值;
obj.方法名=function(){};
调用:obj.属性名/obj['属性名'] ;obj.方法名()
构造函数:
把对象里一些相同的属性和方法抽象出来封装到函数里,提高代码的重复利用率。
function Obj(){ //构造函数命名规范:首字母大写
this.属性名=属性值;
this.方法名=function(){}
//不用return!
}
调用:let obj=new Obj(); //对象实例化
obj.属性名/obj['属性名'] ;obj.方法名()
new的过程(对象实例化):
在内存中创建一个新对象 → this指向该对象 → 执行构造函数里的代码,给该新对象添加属性和方法 → 返回这个新对象(所以不需要写return)
② 遍历对象
for( let k in obj ) { }
let obj={
name:'huahua',
age:18,
move:function(){
alert('1')
}
}
for(let k in obj){
console.log( k,obj[k]);
}
输出:name huahua
age 18
move ƒ (){
alert('1')
}
JSON
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,在与后端的数据交互中有较为广泛的应用。
json和对象字面量很相似,但json的属性名必须加双引号,而对象字面的量的可以省略。json的遍历方法和对象一样,用 for...in...
let j={
"name":'hua',
"age":18,
//注,json中很少写方法
"fn":function(){}
}
//遍历
for(let k in j){
console.log(k,j[k]); //属性名 属性值
}
//name hua
//age 18
//fn ƒ (){}
浅拷贝和深拷贝 ⭐
- 浅拷贝:只拷贝最外面一层的数据;更深层次的对象,只拷贝引用。
- 深拷贝:会把对象里所有的数据重新复制到新的内存空间,是最彻底的拷贝。
① 浅拷贝
let obj={
name:"hua",
second:{sex:'women'}
}
let copy={
name:"123",
age:18,
second:{sex:"both",weight:100}
}
console.log(JSON.stringify(copy));
//{"name":"123","age":18,"second":{"sex":"both","weight":100}}
for(let k in obj){
copy[k]=obj[k]
}
//JSON.stringify()转为JSON字符串
console.log(JSON.stringify(copy),'---',JSON.stringify(obj));
//{"name":"hua","age":18,"second":{"sex":"women"}} --- {"name":"hua","second":{"sex":"women"}}
obj.name="aaa";
obj.second.sex='man';
console.log(JSON.stringify(copy),'---',JSON.stringify(obj));
//{"name":"hua","age":18,"second":{"sex":"man"}} --- {"name":"aaa","second":{"sex":"man"}}
copy.name='bbb'
copy.second.sex='unkonw';
console.log(JSON.stringify(copy),'---',JSON.stringify(obj));
//{"name":"bbb","age":18,"second":{"sex":"unkonw"}} --- {"name":"aaa","second":{"sex":"unkonw"}}
- 第一层的数据两个对象互不影响,但第二层的数据由于两个对象指向同一个地址所以会互相影响。
- copy对象中,同名的属性会被拷贝对象里的数据覆盖,copy对象里有而拷贝对象中没有的第一层数据会保留。
浅拷贝的实现方式:
- for ... in ,比较繁琐(例子见上方代码) ;
- Object.assign(B,A) ⭐对象 A 复制(拷贝)给对象 B(ES6新增)
let copy={};
//三种方式
1、copy=Object.assign(obj);
2、copy=Object.assign({},obj);
3、Object.assign(copy,obj)//可以有多个源对象
- 只有第三种方式,copy对象里有而拷贝对象中没有的第一层数据会保留。另外两种都不会保留。
② 深拷贝
深拷贝其实就是将浅拷贝进行递归。
// 方法:深拷贝
function deepCopy(newObj, oldObj) {
for (let k in oldObj) {
// 获取属性值 oldObj[k]
let item = oldObj[k];
// 判断这个值是否是数组
if (item instanceof Array) {
newObj[k] = [];
deepCopy(newObj[k], item);
}
// 判断这个值是否是对象
else if (item instanceof Object) {
newObj[k] = {};
deepCopy(newObj[k], item);
}
else {
// 简单数据类型,直接赋值
newObj[k] = item;
}
}
}
函数
函数function:一些功能或语句的封装。在需要的时候,通过调用的形式,执行这些语句。实现代码复用。函数也是一个对象。
① 命名函数
使用函数声明来创建一个函数。
function 函数名(形参,...){
}
调用:函数名(实参,...);
② 匿名函数
使用函数表达式来创建一个函数。所谓的“函数表达式”,其实就是将匿名函数赋值给一个变量。因为,一个匿名函数终究还是要给它一个接收对象,进而方便地调用这个函数。
let fn=function(形参){}
后续的DOM里的绑定事件函数和BOM里的定时器函数也是匿名函数。
③ 构造函数new Function()
let fn=new Function('形参','函数体');
④ 立即执行函数
立即执行函数:函数定义完,立即被调用,无需额外调用,这种函数叫做立即执行函数。
两种写法
1、( function (形参) {} (实参) )
2、( function (形参) {} ) (实参)
⑤ 箭头函数( ES6新增)
相当于匿名函数且简化了函数定义
let fn=(形参)=>{函数体}
箭头函数不会自己创建this,只会从自己的作用域链的上一层继承this
⑥ fn() 和 fn 的区别 ⭐
- fn():调用函数。调用之后,获取了函数的返回值。
- fn:函数对象。相当于直接获取了整个函数对象。
- 判断数据类型时,函数、方法不加(),判断的是整个函数对象而不是返回值。
⑦ argument函数内置对象
以伪数组的形式存储所有传递过来的实参
function fn(a,b){
console.log(argument);
console.log(argument.length);
}
fn(1,'b')
输出:Argument{0:1 , 1:'b'} 2
⑧ 内置函数Math
获取随机数:Math.random() //范围是[0,1)
给定范围取随机值:[min,max] , random()*(max-min+1)+min
取整:floor向下取整,ceil向上取整,round四舍五入,±x.5的取大
预解析⭐
分为变量提升和函数提升
通过例子理解更清晰,下面的代码会输出什么呢?
fn();
console.log(a);
console.log(b);
var a=10;
function fn(){
console.log(a);
console.log(b);
var a=b=9;
console.log(a);
console.log(b);
}
console.log(a);
预解析后:
//变量提升
var a;
//函数提升
function fn(){
//变量提升
var a;
console.log(a); //undefined
//console.log(b); //报错
a=9;
b=9; //全局变量
console.log(a); //9
console.log(b); //9
}
fn();
console.log(a); //undefined
console.log(b); //9
a=10;
console.log(a); //10
this指向问题 ⭐
① 默认指向
一般指向调用它的对象
- 全局作用域、普通函数、定时器函数、立即执行函数中,指向window;
- 方法调用中,谁调用this就指向哪个对象;
- 构造函数中,this指向构造函数的实例;
- 箭头函数中,this继承作用域链的上一层;
- 事件绑定中,this指向绑定的对象。
② call
call(this指向,参数) 两种应用
- 调用函数
- 改变this指向
- 主要用于实现继承
function fn(a){
console.log(111,a);
console.log(this);
}
let arr=[];
fn(2); //111 2 window
fn.call(arr,2); //111 2 []
③ apply
apply(this指向,[数组(伪数组)形式传参]) 也是两种应用
- 调用函数
- 改变this指向
- 主要运用于数组,如Math.max(math,arr)返回最大值
function fn(a){
console.log(111,a);
console.log(this);
}
let arr=[];
fn(2); //111 2 window
fn.apply(arr,[2]); //111 2 []
闭包Closure ⭐
① 闭包的概念
如果外部作用域有权访问另外一个函数内部的局部变量时,那就产生了闭包。这个内部函数称之为闭包函数。
再次执行fn2后输出4,这说明了闭包里的数据没有消失,而是保存在了内存中。如果没有闭包,代码执行完倒数第三行后,变量a就消失了。闭包对象的个数取决于外部函数的调用次数,与内部函数无关。
被访问的局部变量所在的函数就是一个闭包函数。还不确定到底哪个是闭包函数的,可以通过浏览器调试设置断点查看。上图中, Local 指的是局部作用域,Global 指的是全局作用域;而 Closure 则是闭包,fn1 是一个闭包函数。
② 闭包的作用
延长局部变量的生命周期
让函数外部可以操作(读写)到函数内部的数据(变量/函数)
③ 闭包的应用
- 高阶函数
- 封装JS模块:定义特色JS模块,将所有数据和功能都封装在一个函数里(私有的),外部调用者只能调用暴露的方法或对象。在C++、Python、Java等语言中有私有属性的概念,但在JS中只能通过闭包模拟实现。⭐
正则表达式
定义:用于定义一些字符串的规则。
作用:计算机可以通过正则表达式检查一个字符串是否符合指定规则,或将字符串中符合规则的部分提取出来。
创建方式:
① 构造函数
- let 变量 = new RegExp("正则表达式" [, "匹配模式"] ); //可以传一个参数也可以传两个参数,参数是字符串。
- 变量.test(字符串);//判断是否符合指定的正则表达式
匹配模式可以是 'i':忽略大小写,也可以是 'g':全局匹配模式,会保留lastIndex
let my=new RegExp('a');
let myReg=new RegExp('A','i');
let str='H';
let myR=new RegExp(str,'i');
console.log(my.test('hello'),myReg.test('hello')); // false false
console.log(my.test('hallo'),myReg.test('hallo')); // true true
console.log(my.test('hello'),myReg.test('hallo')); // true true
② 利用字面量
- let 变量 = /正则表达式/[模式匹配]; //注意没有引号
- 变量.test(字符串);
let my=/a/;
let myReg=/a/i;
console.log(my.test('hello'),myReg.test('hello')); // false false
console.log(my.test('hallo'),myReg.test('hallo')); // true true
③ 两种方式对比
- 方法一更灵活,参数可以传递变量;
- 方法二更简单。
④ 全局匹配g
const s='huao_huaphua';
//let my=new RegExp('hua','g');
let my=/hua/g;
console.log(my.test(s),my.lastIndex); // true 3
console.log(my.test(s),my.lastIndex); // true 8
console.log(my.test(s),my.lastIndex); // true 12
console.log(my.test(s),my.lastIndex); // false 0
匹配成功返回true,同时把 lastIndex
属性的值设置为上次匹配成功结果之后的下一个字符所在的位置,下次匹配将从 lastIndex
指示的位置开始;匹配不成功时返回 false,同时将 lastIndex 属性的值重置为 0。
全局匹配模式一般用于
exec()
、match()
、replace()
等方法。g模式会生成一个
lastindex
参数来存储匹配最后一次的位置。
⑤ 常用符号
- |或,[]或。/[ab]/=/a|b/;/[0~9]/任意数字;/[A-z]/任意字母;/[a-z]/任意小写字母;
- ^起始符
- [^]除了
- $结束符
- [内容]{数量}
⑥ 典型案例(判断字符串是否为电子邮件)
let reg=/^\w{3,}(\.\w+)*@[A-z0-9]+(\.[A-z]{2,5}){1,2}$/;
^以什么开头,\w表示字母数字下划线,\.转义字符加点表示小数点(没\就表示任意字符),+表示前面的至少一个,*表示前面的有0个或多个。
故这一整句表示:以三个以上的字母或数字或下划线为开头,然后(.一个或多个字母或数字或下划线)0个或多个,@,任意一个字母或数字,(.(任意字母)两个到五个)一个到两个结束。
原型和原型链 ⭐
① 原型 prototype
- 实例成员就是通过实例化对象生成的属性和方法,只能通过实例化对象访问;
- 静态成员就是直接通过构造函数生成的,只能通过构造函数访问。
- 在实例成员的方法中,每生成一个实例对象,就会为方法开辟一个新的内存空间,而静态成员则不会,这样就能节省空间。
function fn(){
this.name='hua',//实例成员,只能通过实例化对象访问
this.sing=function(){}
}
let obj_fn=new fn();//实例化对象
console.log(obj_fn.name);
obj_fn.sing();
fn.age=18;//静态成员,只能通过构造函数访问
每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法都会被构造函数所共享。
function fn(){
//this.sing=function(){} //obj_fn.sing===obj_fn2.sing false
}
let obj_fn=new fn();
let obj_fn2=new fn();
fn.prototype.sing=()=>'singing';
console.log(obj_fn.sing===obj_fn2.sing) //true
console.log(fn.prototype.sing(),obj_fn.sing()); //singing singing
console.log(obj_fn.__proto__===fn.prototype) //true
对象都会有一个__proto__属性(每边两个下划线)指向构造函数的prototype原型对象,所以 对象可以调用原型对象的成员。
② constructor
原型对象prototype和对象原型__proto__里都有一个constructor属性,它指回构造函数本身。
function fn(name){
this.name=name;
}
let obj_fn=new fn('hua');
//fn.prototype.sing=function(){}//与下面等价
//fn.prototype.dance=function(){}
fn.prototype={
//给原型对象赋值了一个对象,则必须给它手动利用constructor指向原来的构造函数
constructor:fn,
sing:function(){},
dance:function(){}
}
console.log(obj_fn.__proto__.constructor); //都指向构造函数fn
console.log(fn.prototype.constructor);
给原型对象赋值了一个对象,则必须给它手动利用constructor指向原来的构造函数。此时不能通过实例对象调用prototype里的内容,得用构造函数名.prototype.属性/方法来调用。
③ 原型链
function fn(){}
let obj_fn=new fn();
console.log(obj_fn.__proto__.constructor); //fn
console.log(fn.prototype.constructor); //fn
console.log(fn.prototype===obj_fn.__proto__); //true
console.log(fn.prototype.__proto__.constructor); //Object
console.log(Object.prototype.constructor); //Object
console.log(Object.prototype===fn.prototype.__proto__); //true
console.log(Object.prototype.__proto__); //null
④ 对象成员查找规则
按照原型链的顺序:实例对象 -> 构造函数原型对象 -> Object原型对象 -> null
function fn(){
this.age=16;
}
let obj_fn=new fn();
fn.prototype.age=18;
Object.prototype.age=20;
console.log(obj_fn.age); //16
继承
运用call和原型对象、对象原型。
function Father(name){
this.name=name;
}
function Son(name){
Father.call(this,name); //简单继承
}
let son=new Son('hua');
console.log(son.name); //hua
Father.prototype.age=()=>18;
son.prototype=new Father(); //将父亲赋值给实例对象的原型对象
son.prototype.constructor=Son;//指回实例对象的构造函数
console.log(son.prototype.__proto__.age()); //18
内存泄漏
概念:占用的内存没有及时释放 。注意,内存泄露的次数积累多了,就容易导致内存溢出。
- 闭包(一般不会,但如果滥用闭包则会)
- 没有及时清理的定时器或回调函数
- 意外的全局变量
内存溢出:当程序需要的内存大于剩余的内存空间时,就会抛出内存溢出的错误。
严格模式
即在严格的条件下运行(ES5新增)。严格模式通过在脚本或函数的头部添加"use strict"; 表达式来声明。
为什么使用严格模式?
- 消除代码运行的一些不安全之处,保证代码运行的安全;
- 提高编译器效率,增加运行速度;
- 为未来新版本的js做铺垫。
限制:
- 不能使用未声明的变量;
- 不能删除变量;
- 变量不能重名;
- 不能使用转义字符\;
- 不能对只读属性赋值;
- 禁止this只想全局对象;
- 变量名不能使用'eval'、'arguments';
- ……