js补充知识
数组
数组的声明
现在声明数组一般不用new
let arr1 = [];
let arr2 = ["s", "a"];
如果直接输出arr,会得到一个对象,里面有长度,和值
数组长度获取
console.log(arr.length);
新增数组元素
在数组已经达到上限的时候,有多种方法可以改变数组长度\
- 手动修改
arr.length
的值,然后再赋值
arr.length++;
arr[arr.length - 1] = 5;
- 直接进行赋值,不会有溢出的麻烦
arr[arr.length] = 5;
argument
argument的作用是处理存储所有传递过来的实参,而现在显然可以被rest参数给替代掉
argument是一个伪数组
伪数组
- 具有length属性
console.log(argument.length);
- 能够通过索引的形式获取argument的值
argument[0]
- 不能使用数组的一些方法,如
pop
,push
这也是伪数组与数组的根本区别
两种声明函数的方式
function fun(){};
fun();
var fun = function(){};
fun()
函数的实例化
function fun(
let a = 5;
console.log("fsf");
}
var fun1 = fun();
第二种方式是将函数实例化,就像是将这个函数变成了一个构造函数,能够通过点符号获取到这个函数作用域里面的变量和方法,但是不能直接执行这个函数, 所以说构造函数就是没有
作用域
在es6以前js是没有块级作用域的,而是只有局部作用域和全局作用域,局部作用域就是在函数的内部,而块级作用域是在一个{}内部,比如if,for,这些都是没有块级作用域的,内部定义的变量就是全局的
预解析
预解析就是分为变量预解析和函数预解析
变量提升(变量预解析)
变量提升会将变量声明提升到该定义域的最前面,变量赋值不会
console.log(a);
var a = 10;
变量提升后的实际执行顺序为
var a;
console.log(a);
a = 10;
将变量声明提升到定义域最前面
函数提升(函数预解析)
函数提升同理,将函数声明提升到定义域最前面,只需要注意,如果使用var fun = function()
的方式定义函数,这算是变量提升,不会把这个函数提升到最前面
fun();
var fun = function(){};
实际执行顺序
var fun;
fun();
fun = function(){};
对象
万物皆对象,对象是无序的属性和方法的集合,有点类似c语言里的结构体的实例化
对象的声明和使用
let student = {
name:"学生",
gotoschool: function(){
}
};
console.log(student.name);
//student["age"]也可以使用name
student.gotoschool();
对象声明的第二种方式new Object();
let student = new Obeject()//创建一个空的对象;
student.name = "学生";
student.gotoschool = function(){};
构造函数
构造函数相当于结构体,就是将对象进行封装;
function Student(name, tel, address, gotoschool){
this.name = name;
this.tel = tel;
this.address = address;
this.gotoschool = function(){
};
}
let xiaoming = new Student("xiaoming", 150, "beijing");
console.log(xiaoming.name);
xiaoming.gotoschool();
构造函数的方法不需要形参和实参
构造函数没有返回值
构造函数其实就是一个有了别名的函数,实质上还是一个函数。
遍历对象里的属性和方法
for(let k in student){
console.log(k);//student, tel , address, gotoschool();
console.log(student[k]);//xiaoming, 150, beijing;
}
不过我们一般不会去便利方法
内置对象
就是我们能够直接使用的一些简单的方法如Math, Date
Math的一些方法详见 js的一些方法.
Date对象
let date1 = new Date();
//不传入参数返回的是当前的时间
let date2 = new Date(2016, 10, 20);
//传入数字型可能会出现一些问题
let date3 = new Date("2016-10-20 10:10:10");
//字符串型不会出现问题,比较常用
下面是实例化后的一些方法
获取总的毫秒数,这样做倒计时更加准确
let date = +new Date();//注意这里是+new,而不是new
//如果给Date里加一个时间参数,就会把时间转化成总的毫秒数
console.log(data);//1970年至今的总毫秒数
也可以用最新的h5方法console.log(Data.now())
获取当前总的毫秒数
原型对象
现在假设Person是一个构造函数,而p是一个实例对象
p是由Person new出来的一个实例嘛,这个时候有一个属性,prototype,如果我们需要在给所有的实例对象后来加一个属性,比如age,不能在构造函数里加,就可以这样写Person.prototype.age = "20"
这个时候,就可以通过p.age访问到.
Person.prototype就是个原型对象,我们可以往prototype里加东西
那p是如何读取到原型对象上的东西的,p有一个属性叫_proto_,p.proto_可以称为隐式的原型对象,而prototype则是显式的原型对象,当我们在使用p.age的时候,先往自己身上找,也就是构造函数里的内容,没有找到age,就开始找_proto,这个属性指向的就是原型对象,Prototype里的东西都在这
原型对象也有原型对象,如果没有特殊更改,原型对象的原型对象就是object,所以p也能访问到Object的属性和方法,比如p.tostring()
不同数据类型的内存分配
分类
js的数组类型分为值类型和引用类型
值类型(也叫简单数据类型)就是string,number, boolean, undefined,null(本质是一个对象类型)
引用类型(复杂数据类型)是需要new来创建,比如数组,date,对象
堆和栈
js里面并没有堆和栈,只是为了方便理解
简单数据类型是存放在栈里面,复杂数组类型是将地址存放在栈里面,然后值放在堆里面,所以是先通过访问栈里面的地址然后再调出值。
所以在函数传参的时候两者就会有区别,简单数据类型直接是复制栈里面的值,复杂数组类型传的是栈里的地址,就像是c++里的引用形参,可以通过修改函数里的数据的值改变外面的值。
call apply
如下的函数
function sss(a, b){
console.log(this.a,this.b,this.num);
};
let test = {
num: 100;
};
sss.call(test, 3, 4);
其实总的来说call只有第一个参数,是一个对象
sss本身是在window下的,所以其中的this也是window,如果想要将其中的this变为test,就是将sss变为test下的,就需要将第一个参数设置为test,后面的参数是函数本身需要的参数
apply与call本身极为相似
只是后面接收参数的方法不一样而已
sss.appy(test, [3, 4]);
throw
throw会停止js然后将throw的值给到catch
let a = 6;
if(a > 5){
throw "a > 5 ERROR";
}
console.log("未被执行");
try{
console.log(a);
}catch(err){
console.log(err);
}
a = 6
a = 3时
事件委派
当有多个元素都有同样的绑定事件的时候,我们可以将事件绑定给父元素,当子元素触发事件,就会通过冒泡触发父元素的事件,优化性能
深拷贝
- 手动递归复制所有属性:
function deepCopy(obj) {
if (typeof obj !== "object") {
return obj;
}
const newObj = obj instanceof Array ? [] : {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = deepCopy(obj[key]);
}
}
return newObj;
}
- 使用 JSON.stringify 和 JSON.parse:
const newObj = JSON.parse(JSON.stringify(obj));
但是这个方法存在一些问题:
- 无法复制函数
- 无法复制 undefined 和 symbol
- 无法解决循环引用的问题
- 使用第三方库 lodash 的 cloneDeep 方法:
const newObj = _.cloneDeep(obj);
这个方法是一个非常完全的解决方案,可以深度复制任何对象,但是需要额外依赖第三方库。
数据转成bool
可以用Boolean()显式转换,也可以用!!两个取反转换
一些基础概念
DOM事件流 事件发生时按一定顺序传播,这个过程 document object model
捕获阶段:addEventlistener的第三个参数如果是ture就是捕获阶段,父元素和子元素同时绑定了点击事件,那么会先执行父元素事件,然后再执行子元素
冒泡阶段则相反,addEventlistener默认是冒泡,而有的事件没有冒泡阶段的,如onblur,onfocus
事件对象 添加事件监听时的函数小括号里的加参数,这个参数
addEventListener(“click”,function(ssss){}) sss就包含了click执行时的事件对象信息,有按下时的坐标等。
调用时有兼容性问题,所以就ssss = ssss || window.ssss
事件委托 通过给父元素绑定事件,用event。target来影响子元素,从而提高效率。
子元素没有事件监听,但是它会冒泡,找到父元素,父元素加了事件监听,就会执行事件,通过target找到对应的子元素。
BOM browser object model浏览器对象模型,是与浏览器实现交互的
document是window的子对象,在document里的变量和函数都会变成window的属性和方法,只是我们在调用的时候,可以省略window,如window.alert()
同步任务和异步任务 按照一定顺序的叫同步任务,可以一起执行的叫异步任务。异步任务比如click,timeout,interval
同步任务会被放到主线程栈中一直执行,而异步任务则会被放到任务队列里面,先把所有的同步任务执行完了之后再执行异步任务,而有多个异步任务的时候,会按照顺序先放到异步任务处理里面,等待异步任务触发,就放到任务队列里,比如click和timeout,点击了就加入到任务队列,timeout了也加入到队列里面
本地存储 存放在用户的浏览器里面,不会被刷新删除,容量较大,只能存储字符串,
sessionStorage生命周期是从页面的打开到关闭,刷新不会清除