目录
基础总结深入
一.数据类型
基本类型
String:任意字符串
Number:任意数字
boolean:true/false
undefined:undefined
null:null
对象(引用)类型
Object:任意对象
Array:一种特别的对象(数值下标 内部数据是有序的)
Function:一种特别的对象(可以执行)
== 与 ===的区别:前者会做数据转换 后者不会
判断数据类型的方法
(1)typeof:可以判断undefined 数值 字符串 布尔值 function,不能判断:null与object对象,一般对象object与array
(2)instanceof:判断对象的具体类型:Object Array Function
(3)===:可以用来判断undefined和null
相关问题
1.undefined与null的区别
undefined代表定义未赋值
null代表定义了也赋值了 只不过值为null
2.什么时候给变量赋值为null
(1)初始的时候赋值为null:var b = null;表明b将要赋值为对象 表明b是一个对象 可能因为我还没确定给b赋值的对象是谁
所以我还没给他赋值 将来我确定了对象 我会给他赋值为对象 当然了 你也可以直接var b
(2)结束前赋值为null:b = null;是为了让b指向的对象成为垃圾对象 方便被垃圾回收机制回收 当然了 你也可以让b = 2
来使b指向的对象成为垃圾对象 但是这么做不好 因为我本来是想释放数组对象 你这样又存了一个数存到b中去了 你又创建了一个没有用的数据 没必要
3.严格区别变量类型和数据类型
数据类型:基本类型 对象类型
变量的类型(变量内存的值的类型):
基本类型:保存的是基本类型的数据
引用类型:保存的是地址值
js是弱类型语言 变量本身没有类型 我们再判断变量的类型时实际上时判断值的类型
二.数据 变量 内存
数据
数据:存储在内存中代表特定信息的“东东” 本质上是01010……
数据的特点:可传递 可运算
一切皆数据
内存中所有操作的目标:数据
可以对数据进行的操作有哪些:算术运算 逻辑运算 赋值(实际上是数据拷贝 把数据从一个地方拷贝一份保存到另一个地方) 运行函数
内存
内存:内存条通电后产生的可存储数据的空间(临时的)
内存的产生与死亡:内存条(本质上是电路板 电路板就是一些电子元器件组成的一块板子)-》通电-》产生内存空间-》存储数据-》处理数据-》断电-》内存空间和数据都消失
一块小内存的两个数据:内部存储的数据 地址值
内存的分类:
栈:存全局变量 局部变量
堆:对象
函数在堆中 因为函数是对象 函数名在栈中 因为函数名是变量名
对象本身在堆空间 标识这个对象的变量在栈空间
变量
变量:可变化的量又变量名和变量值组成
每个变量都对应一块小内存 变量名用来查找对应的内存 变量值就是内存中保存的数据
数据 内存 变量三者之间的关系
内存是用来存储数据的临时空间(永久空间是硬盘 硬盘存储数据慢 但硬盘的存储空间大 内存的存储空间小但读写速度快)
变量是内存的标识
相关问题
1.var a = xxx; a内存中到底保存的是什么?
xxx是基本数据类型 保存的是这个数据xxx
xxx是对象 保存的是对象的地址值
xxx是一个变量 保存的是xxx的内存内容(可能是基本数据 也可能是地址值)
2.关于引用变量赋值的问题
2个引用变量指向同一个对象 通过一个变量修改对象内部数据 其他所有变量看到的是修改之后的数据
var obj1 = {name : 'TOM'};
var obj2 = obj1;
obj2.age = 12;
console.log(obj1.age);//12
function fn(obj) {
obj.name = 'A';
}
fn(obj1);
console.log(obj2.name)//A
2个引用变量指向同一个对象 让其中一个引用变量指向另一个对象 另一个引用变量依旧指向前一个对象
var a = {age: 12};
var b = a;
a = {name: 'BOB',age: 13};
console.log(b.age,a.name,a.age);//12 BOB 13
function fn2(obj) {
obj = {age: 15};
}
fn2(a);//把a赋值给了obj(相当于obj = a 也就是将a的内容拷贝一份给obj) 在还没有执行函数体时 obj和a指向同一个对象{name: 'BOB',age: 13}
// 在函数内部(执行函数体时)obj指向了另一个对象 obj = {age: 15} a没有变 obj也没有改变a指向的对象
//函数执行完后 函数内部的局部变量obj会自动释放内存空间 {age: 15}成为垃圾对象
console.log(a.age);//13
3.在js调用函数时 传递变量参数时 是值传递还是引用传递?
理解1:都是值(基本值/地址值)传递 这种理解就是把地址值 基本值都理解为值
理解2:可能是值传递 也可能是引用传递 引用传递传递的是地址值 这种理解就是把值传递理解为基本值传递 引用传递理解为传递地址值
参数传递实际上就是一个变量赋值给另一个变量 相当于a=b 传递的始终是b中存的数据 b的值 只是这个值可能是一般的数据 也可能是个地址数据
形参的本质是变量 而且是局部变量 实参的本质是数据 是个值
var a = 3;
function fn(a) {//值传递 全局变量a将自己的值3传递给形参a 在函数中操作的是值3 而不是全局变量a
a = a + 1;
}
fn(a);
console.log(a);//3
function fn2(obj) {
console.log(obj.name);
}
obj = {name: 'TOP'};//obj中存储的是{name: 'TOP'}对象的地址值
fn2(obj);//将全局变量obj中的地址值赋值给形参obj
4.JS引擎如何管理内存?
(1)内存生命周期
分配小内存空间 得到他的使用权
存储数据 可以反复进行操作
释放小内存空间
(2)释放内存
局部变量:函数执行完自动释放
对象:成为垃圾对象-》垃圾回收器回收
var a = 3;
var obj = {};//此时内存中有三个对象
obj = null;//此时对象中有两个对象
function fn(){
var b = {};
}
fn();
/*
局部变量什么时候产生:函数执行时 也就是函数调用时
局部变量什么时候死亡:函数执行完 函数内部所有的局部变量占用的空间都会自动释放
b是(立即)自动释放 b所指向的对象是在后面的某个时刻由垃圾回收器回收
*/
三.对象
对象
代表现实中的某个事物 是该事物在编程中的抽象
多个数据的集合体(封装体)
用于保存多个数据的容器
为什么要用对象
便于对多个数据进行统一管理
对象的组成
1.属性
(1)代表现实事物的状态数据
(2)由属性名和属性值组成
(3)属性名都是字符串类型 属性值是任意类型
2.方法
(1)代表现实事物的行为数据
(2)是特别的属性-》属性值是函数
如何访问对象内部数据
属性名:编码简单 但有时不能用
[‘属性名’]:编码麻烦 但通用
四.函数
实现特定功能的n条语句的封装体
只有函数是可以执行的 其他类型的数据不能执行
为什么要用函数?
提高代码复用
便于阅读交流
如何定义函数?
函数声明
function fn1() {
console.log("函数声明");
}
表达式
var fn2 = function () {
console.log("表达式");
}
如何调用(执行)函数?
函数名():直接调用
对象.对象的方法名():通过对象调用
new 构造函数名():new调用
方法名.call/apply(obj):如test.call/apply(obj) 临时让test成为obj的方法进行调用 相当于obj.test 也就是说obj对象中本来没有test方法 但是我可以通过obj对象来调用test方法
var obj = {};
function test() {
this.xxx = 'cara';
}
//不能使用obj.test()调用test方法 因为obj对象中没有test方法
test.call(obj);//但是js可以用test.call(obj)让一个函数成为指定任意对象的方法进行调用
console.log(obj.xxx);//cara
五.回调函数
什么函数才是回调函数?
满足以下三点:
你定义的
你没有调
但最终他(在某个时刻或某个条件下)执行了
常见的回调函数有哪些?
(1)DOM事件(点击 聚焦 失去焦点等DOM事件)回调函数:this是发生事件的dom元素 就是用户操作的那个元素
(2)定时器回调函数:this是window
(3)ajax请求回调函数
(4)生命周期回调函数
document.getElementById('btn').onclick = function () {//DOM事件回调函数
alert('琪琪加油!');
}
//定时器:超时定时器 循环定时器
setTimeout(function () {//定时器回调函数
alert('到点了');
},2000);
六.IIFE
全称:Immediately-Invoked Function Expression立即执行(调用)函数表达式 也叫匿名函数自调用
作用:
(1)隐藏实现
(2)不会污染外部(全局)命名空间
(3)用它来编码js模块
(function () {//匿名函数自调用
var a = 1;
function test() {
console.log(++a);
}
window.$ = function () {//向外暴露一个全局函数
return {
test: test
}
}
})()
//现在我想获得a应该怎么做?
$().test();//1.$是一个函数 2.$执行后返回的是一个对象{test: test} a打印输出2
七.this
- this是什么:
任何函数本质上都是通过某个对象来调用的 如果没有直接指定就是window
所有函数内部都有一个变量this
它的值是调用函数的当前对象 - 如何确定this的值:
test():window
p.test():p
new test():新创建的对象
p.call(obj):obj
function Person(color) {
console.log(this);//1
this.color = color;
this.getColor = function () {
console.log(this);//2
return this.color;
};
this.setColor = function (color) {
console.log(this);//3
this.color = color;
};
}
Person("red");//this是window 且只执行1 没调用2 3不会执行2 3的
var p = new Person("yellow");//this是p 且只执行1 没调用2 3不会执行2 3的
p.getColor();//this是p 且只执行2
var obj = {};
p.setColor.call(obj,"black");//this是obj
var test = p.setColor;
test();//this是window
function fn1() {
function fn2() {
console.log(this);
}
fn2();//this是window
}
fn1();
- 普通函数中的this:谁调用了函数或者方法,那么这个函数或者对象中的this就指向谁
- 匿名函数中的this:window
- 箭头函数中的this:定义箭头函数时,所在的作用域指向的对象
八.关于语句分号问题
1.js一条语句的后面可以不加分号
2.是否加分号是编码风格问题 没有应该不应该 只有自己喜不喜欢
3.在下面两种情况下不加分号会有问题:
(1)小括号开头的前一条语句
(2)中方括号开头的前一条语句
4.解决办法:在行首加分号
5.强有力的例子:vue.js库
函数高级
一.原型与原型链
函数的prototype
1.原型上面的方法是给实例对象用的
2.函数的prototype属性
(1)每个函数都有一个prototype属性 它默认指向一个Object空对象(Object空对象:没有我们自己添加的属性) 这个空对象称为原型对象
(2)原型对象中有一个属性constructor 它指向函数对象 constructor是引用变量属性
3.给原型对象添加属性(一般都是方法)
作用:函数的所有实例对象自动拥有原型中的属性(方法)
4.Type函数有个属性prototype prototype指向Type的原型对象Type Prototype Type Prototype有一个constructor属性 constructor指向Type函数 也就是说 构造函数和他的原型对象相互引用
// 每个函数都有一个prototype属性
console.log(Date.prototype,typeof Date.prototype);
function Fun() {}
console.log(Fun.prototype);// 默认指向一个Object空对象(Object空对象:没有我们我们自己添加的属性)
// 给原型对象添加属性(一般都是方法) 作用:为了让实例对象使用
Fun.prototype.test = function () {
console.log('test()');
}
console.log(Fun().test());
// 原型对象中有一个属性constructor 它指向函数对象
console.log(Date.prototype.constructor === Date);
console.log(Fun.prototype.constructor === Fun);
var fun = new Fun();
fun.test();
显示原型与隐式原型
1.每个函数都有一个prototype 即显示原型(属性) 默认值指向一个空Object对象
2.每个实例对象都有一个__proto__ 可称为隐式原型(属性)
3.对象的隐式原型的值为其对应构造函数的显示原型的值
// 定义构造函数
function Fn () {// 内部语句:this.prototype = {} this指向Fn(?)
}
// 每个函数function都有一个prototype 即显示原型(属性) 默认值指向一个空Object对象
console.log(Fn.prototype);
// 每个实例对象都有一个__proto__ 可称为隐式原型(属性)
var fn = new Fn();// 内部语句 this.__proto__ = Fn.prototype this指向实例对象 此处时fn
console.log(fn.__proto__);
// 对象的隐式原型的值为其对应构造函数的显示原型的值
console.log(Fn.prototype === fn.__proto__);
// 给原型添加方法
Fn.prototype.test = function () {
console.log('test()');
}
// 通过实例对象调用原型的方法
fn.test();
4.内存结构(见图)
通过对象去调用某个方法或属性时 先在本身(此处是Fn的实例对象)找 找不到再去原型对象找 通过隐式原型属性__proto__找到原型对象的 虽然找的时候没有看显式原型属性prototype的值 但是隐式原型属性__proto__的值最初是由显式原型属性prototype赋值给他的
5.总结:
(1)函数的prototype属性:在定义函数时自动添加的 默认值是一个空Object对象
(2)对象的__proto__属性:创建对象时自动添加的 默认值为构造函数的prototype属性
(3)程序员能直接操作显示原型 但不能直接操作隐式原型(ES6之前)
原型链
1.原型链(图):
(1)访问一个对象的属性时 先在自身属性中查找 找到返回 如果没有 再沿着__proto__这条链向上查找 找到返回 如果最终没找到 返回underfunded
(2)别名:隐式原型链
(3)作用:查找对象的属性(方法)
(4)查找变量看作用域链 查找方法看原型链
2.构造函数/原型/实体对象的关系(图)
var o1 = new Object();
var o2 = {};
3.构造函数/原型/实体对象的关系2(图)
function Foo () {};
这样写相当于
var Foo = new Function () {};
构造函数Function()本身也是Function()函数 即
Function = new Funtion();
4.函数既有隐式原型又有显示原型 所有的函数的隐式原型都一样 因为所有的函数都是new Function产生的 所以所有的函数的隐式原型__proto__ = Function.prototype
5.原型继承:构造函数的实例对象自动拥有构造函数原型对象的属性 利用的就是原型链
6.JS的继承是基于原型的继承 原型本质上是对象 所以也说JS的继承是基于对象的继承 Java是基于类的继承
7.函数的显示原型指向的对象默认是空Object实例对象 但Object是例外
8.构造函数既有显式原型又有隐式原型 二者不相等 但Function例外
console.log(Fn.prototype instanceof Object);//true
console.log(Object.prototype instanceof Object);//false
console.log(Function.prototype instanceof Object);//true
9.所有函数都是Function的实例(包括Function本身)
console.log(Function.__proto__ === Function.prototype);//true
10.Object的原型对象是原型链的尽头 因为Object.prototype.__proto__为null
console.log(Object.prototype.__proto__);// null
原型链属性问题
1.读取对象的属性值时 会自动到原型链中查找
2.设置对象的属性值时 不会查找原型链 如果当前对象中没有此属性 直接添加此属性并设置其值
3.方法一般定义在原型中 属性一般通过构造函数定义在对象本身上
function Fn () {}
Fn.prototype.a = 'xxx';
var fn1 = new Fn();
console.log(fn1.a, fn1);
var fn2 = new Fn();
fn2.a = 'yyy';// 原型链用于查找属性值 在设置属性值时不会看原型链 属性一般存在自身身上 方法存在原型身上
console.log(fn1.a, fn2.a, fn2);
function Person (name, age) {
this.name = name;
this.age = age;
}
Person.prototype.setName = function (name) {
this.name = name;
}
var p1 = new Person('TOM',12);
p1.setName('BOB');
console.log(p1);
var p2 = new Person('JACK',12);
p1.setName('CAT');
console.log(p2);
console.log(p1.__proto__ === p2.__proto__);// true 实例对象的隐式原型都指向构造函数的显示原型 所以p1 p2的隐式原型相等
// 但他们是两个对象 他们色属性值是不一样的
探索instanceof
1.instanceof:判断左边的对象是不是右边这个的类型的实例
2.instanceof是如何判断的?
(1)表达式:A instanceof B 其中 A是实例对象 B是构造函数对象
(2)如果B函数的显示原型对象在A对象的原型链上 返回true 否则返回false
(3)A是实例对象 有隐式原型属性__proto__ A.proto__指向一个对象 这个对象又有隐式原型属性__proto 该对象.proto__又指向一个对象 这个对象又有隐式原型属性__proto …… 如此形成原型链 原型链的尽头是Object的原型对象 这是条隐式原型链 B是(构造)函数 有显示原型属性prototype 若B.prototype所指向的对象是上述原型链中的某一个对象 则说明A是B的实例 返回true
3.Function是通过new自己产生的实例 Function = new Funtion();
function Foo () {}
var f1 = new Foo();
console.log(f1 instanceof Foo);// true
console.log(f1 instanceof Object);// true
console.log(Object instanceof Function);// true
console.log(Object instanceof Object);// true
console.log(Function instanceof Function);// true
console.log(Function instanceof Object);// true
function Foo () {}
console.log(Object instanceof Foo);// false
// 案例1
function Foo () {
}
var f1 = new Foo();
console.log(f1 instanceof Foo);// true
console.log(f1 instanceof Object);// true
// 案例2
// Object是实例对象 Object作为一个函数来说 他是new Function()产生的 实例对象的隐式原型__proto__指向构造函数的显示原型prototype
console.log(Object instanceof Function);// true
console.log(Object instanceof Object);// true
console.log(Function instanceof Function);// true
// Function作为实例对象 由于函数的原型对象默认是空的Object实例对象 所以空的Object实例对象的隐式原型属性指向构造函数Object()的显示原型属性
console.log(Function instanceof Object);// true
function Foo () {
}
console.log(Object instanceof Foo);// false
// 以下面试题
// 面试题1
function A () {
}
A.prototype.n = 1;
var b = new A();// 通过b.__proto__可以看到A.prototype.n = 1
A.prototype = {
n: 2,
m: 3
}
var c = new A();
console.log(b.n, b.m, c.n, c.m);// 1 undefined 2 3
// 面试题2
var F = function () {
Object.prototype.a = function () {
console.log('a()');
}
}
Function.prototype.b = function () {
console.log('b()');
}
var f = new F();
// F()中本来没有a() 所以要去原型prototype中找 F()的原型对象是空Object实例对象 Object实例对象可以看到Object原型对象上的方法
// Object实例对象.__proto__ = Object.prototype
f.a();
// F()中本来没有b() 所以要去原型prototype中找 F()的原型对象是空Object实例对象 Object原型对象上没有b() 所以返回undefined
f.b();// 报错:f.b is not a function
// 将F看成实例对象才能去调a() F是Function()的实例对象 Function()是Object的实例对象
F.a();
F.b();
补充
1.prototype属性是什么时候添加到函数上的:函数创建时 也就是定义函数时
定义函数时 函数体并没有执行 只是创建了函数对象 在创建函数对象时 在函数内部会执行this.prototype = {}给函数对象实例添加prototype属性 this是函数对象实例
2.函数既是构造函数函数也是实例对象 是Function()构造函数的实例
3.实例对象上还有个属性constructor constructor指向构造函数
4.所有函数的原型对象(prototype属性)默认都是Object的实例对象 Object()除外 Object的原型是他本身 Object的原型对象的隐式原型是null
5.Object()是Function()的实例对象 Object() = new Function()
二.执行上下文与执行上下文栈
变量提升与函数提升
1.变量声明提升
通过var定义(声明)的变量 在定义语句之前就可以访问到 只是此时的值为undefined
2.函数声明提升
通过function声明的函数 在之前就可以直接调用 只是此时的值为函数定义(对象)
3.变量提升和函数提升是如何产生的?
var a = 3;
function fn () {
console.log(a);// undefined
var a = 4;
/*
fn函数体相当于:
var a;
console.log(a);// undefined
a = 4;
所以打印输出undefined
*/
}
fn();
console.log(b);// undefined 变量提升
fn2();// 可调用 函数提升
// fn3();// 不能调用 变量提升
var b = 3;
function fn2 () {
console.log('fn2()');
}
var fn3 = function () {
console.log('fn3()');
}
执行上下文与执行上下文栈
1.代码分类 根据代码位置可分为:全局代码和函数(局部)代码
2.全局执行上下文:
(1)在执行全局代码前将window确定为全局执行上下文
(2)对全局数据进行预处理
- var定义的全局变量-》值为undefined 添加为window的属性
- function声明的全局函数-》赋值(fun) 添加为window的方法
- this-》赋值为window
(3)开始执行全局代码
3.函数执行上下文
(1)在调用函数 准备执行函数体之前 创建对应的函数执行上下文对象(你得==调用(执行)==函数才会创建函数执行上下文对象)
注:
(1)函数执行上下文对象不是一个真实的对象 是一个虚拟的对象 他是一片区域 这个区域中有局部变量 形参 arguments 这个区域在栈中
(2)局部变量放在栈中 栈进一步分为全局区域 你调用一个函数又会为这个函数分配一个区域 函数执行完了之后会把这个空间释放掉
(3)为什么要给函数创建一个封闭的区域(函数执行上下文对象):防止污染全局变量
(2)对全局数据进行预处理
- 形参变量-》赋值为实参数据-》添加为执行上下文的属性
- 类数组/伪数组arguments-》赋值(实参列表) 添加为执行上下文的属性
- var定义的局部变量-》undefined 添加为执行上下文的属性
- function声明的函数-》赋值(fun) 添加为执行上下文的方法
- this-》赋值(调用函数的对象)
(3)开始执行函数体代码
4.执行上下文栈
(1)在全局代码执行前 JS引擎就会创建一个栈来存储管理所有的执行上下文对象
(2)在全局执行上下文(window)确定后 将其添加到栈中(压栈)
(3)在函数执行上下文创建后 将其添加到栈中(压栈)
(4)在当前函数执行完后 将栈顶的对象移除(出栈)
(5)当所有的代码执行完后 栈中只剩下window
// 全局执行上下文对象
var a = 10;
var bar = function (x) {
var b = 5;
foo(x + b);// 函数执行上下文对象
}
var foo = function (y) {
var c = 5;
console.log(a + c + y);
}
bar(10);// 函数执行上下文对象
当前运行的始终是栈顶的执行上下文对象
相关问题
1.依次输出什么?
2.整个过程中产生了几个执行上下文:5个
console.log('global begin: ' + i);
var i = 1;
foo(1);
function foo (i) {
if (i === 4) {
return;
}
console.log('foo() begin: ' + i);
foo(i + 1);
console.log('foo() end: ' + i);
}
console.log('global end: ' + i);
答:
1.global begin: undefined
foo() begin: 1
foo() begin: 2
foo() begin: 3
foo() end: 3
foo() end: 2
foo() end: 1
global end: 1
2.5个
function a () {}
var a;
console.log(typeof a);// function 先执行变量提升 再执行函数提升
if (false) {
var z = 1;
}
console.log(z);// undefined
var m = 1;
function m (m) {
console.log(m);
var m = 3;
}
m(2);// 报错:m is not a function
上述代码等价于:
var m;
function m (m) {
var m;
console.log(m);
m = 3;
}
m = 1
m(2);// 此时m肯定不是一个函数
三.作用域与作用域链
作用域
就是一块“地盘” 一个代码段所在的区域
他是静态的 (相对于上下文对象)在编写代码时就确定了
作用域分类
(1)全局作用域
(2)函数作用域
(3)没有块作用域(ES6有了)在{}里面声明的变量 在外面看得见 ES6就看不见了
作用域作用
隔离变量 不同作用域下同名变量不会有冲突 在函数外部又一个变量a 在函数内部也可以又变量a
作用域与执行上下文对象
1.区别:
说法一:
(1)全局作用域之外 每个函数都会创建自己的作用域 作用域在函数定义时就已经确定了 而不是在函数调用时
(2)全局执行上下文环境是在全局作用域确定之后 js代码马上执行之前创建
(3)函数执行上下文是在调用函数时 函数体代码执行之前创建
说法二:
(1)作用域是静态的 只要函数定义好了就一直存在 且不会再变化
(2)执行上下文是动态的 调用函数时创建 函数调用结束时就会被自动释放
2.联系:
(1)执行上下文环境(对象)是从属于所在的作用域
(2)全局上下文环境-》全局作用域
(3)函数上下文环境-》对应的函数作用域
作用域链
嵌套的作用域产生的由内向外的过程
1.理解
多个上下级关系的作用域形成的链 它的方向时从下向上的(从内到外) 查找变量时就是沿着作用域链来查找的
2.查找一个变量的查找规律:
在当前作用域下的执行上下文中查找对应的属性 如果有直接返回 否则进入2
在上一级作用域的执行上下文中查找对应的属性 如果有直接返回 否则进入3
再次执行2的相同操作 直到全局作用域 如果还找不到就抛出找不到的异常:xx is not define
3.作用:查找变量
4.判断有几个作用域:n+1 其中 有n个函数 1是全局作用域
5.查找a.b:a是变量 先沿着作用域链找a 找不到就报错 找到了再沿着原型链找b b是对象属性 找不到返回undefined
6.a与window.a的区别:若a不存在 前者浏览器会报错 后者浏览器会返回undefined
相关问题
var x = 10;
function fn () {
console.log(x);
}
function show (f) {
var x = 20;
f();
}
show(fn);// 10
代码写好的时候 作用域就确定好了(红框的三个)fn函数中的x先在fn函数中找 找不到 向上去全局作用域找 找到了 所以x是10 本题注意作用域在代码写好的时候就确定好了
2.
var fn = function () {
console.log(fn);
}
fn();
输出:
ƒ () {
console.log(fn);
}
var obj = {
fn2: function () {
console.log(fn2)
}
}
obj.fn2();// 报错:fn2 is not defined
在fn2: function () {console.log(fn2)}
中找不到fn2 向上去全局作用域找 也找不到 报错fn2 is not defined
如果你想让他在obj对象中找fn2 那么应该写:console.log(this.fn2)
四.闭包
循环遍历加监听
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>遍历加监听</title>
</head>
<body>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<!--需求:点击某个按钮 显示:“点击的是第n个按钮”-->
<script type="text/javascript">
var btns = document.getElementsByTagName('button');
// 遍历加监听
// btns是伪数组 且btns.length这个length没有在里面 并不是固定的值 所以每次都需要重新计算btns.length 所以把btns.length拿出去可以提高效率
// for (var i = 0; i < btns.length; i++) {
//
// }
// for (var i = 0, length = btns.length; i < length; i++) {
// var obj = btns[i];
// obj.onclick = function () {
// alert('第' + (i + 1) + '个');// 不管点哪个都弹出:第4个 点击函数是在后面某个时刻执行的 函数执行的时候循环已经执行完了
// }
// }
// console.log(i);// 3 i是全局变量 函数中声明的变量才是局部变量 这里没函数 所以i是全局变量
// for (var i = 0, length = btns.length; i < length; i++) {
// var obj = btns[i];
// obj.index = i;// 将btn所对应的下标保存在btn上
// obj.onclick = function () {
// alert('第' + (this.index + 1) + '个');// 不管点哪个都弹出:第4个 点击函数是在后面某个时刻执行的 函数执行的时候循环已经执行完了
// }
// }
// 利用闭包
for (var i = 0, length = btns.length; i < length; i++) {
(function (j) {// 局部j
var obj = btns[j];// 局部j
obj.onclick = function () {
// 闭包的属性名是内部函数引用外部函数的变量名叫什么 注意是内部函数的变量名
alert('第' + (j + 1) + '个');// 局部j
}
})(i)// 这个i是全局的
}
</script>
</body>
</html>
产生了3个闭包 闭包中的值分别是j = 0, j = 1, j = 2所以可以实现
闭包有3个 没有释放的原因是内部函数被obj引用着 并且闭包中的东西直到页面关闭才释放 因为内部函数绑定在了按钮上 只有等到页面关闭 按钮消失 内部函数才变成垃圾对象 闭包消失 但是你也可以手动让内部函数变成垃圾对象:obj.onclick = null; 这样闭包也会消失
闭包
1.如何产生一个闭包:当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时 就产生了闭包
2.闭包到底是什么:
(1)使用chrome测试查看
(2)理解一:闭包是嵌套的内部函数(绝大部分人)
(3)理解二:(内部函数)包含被引用变量(函数)的对象(极少数人)
(4)注意:闭包存在于嵌套的内部函数中
3.产生闭包的条件:
(1)函数嵌套
(2)内部函数引用了外部函数的数据(变量/函数)
(3)执行外部函数:因为闭包在内部函数对象中 所以至少要执行函数定义产生内部函数对象 调用外部函数就会执行内部函数定义 不用调用内部函数
function fn1 () {
var a = 2;
var b = 'abc';
function fn2 () {// 执行函数定义就会产生闭包(不用调用内部函数)
console.log(a);
}
}
fn1();
4.常见的闭包
(1)将函数作为另一个函数的返回值
function fn1 () {
var a = 2;// 代码执行到这一行的时候闭包就已经产生了 因为存在函数提升
function fn2 () {// 执行函数定义就会产生闭包(不用调用内部函数)
a++;
console.log(a);
}
return fn2;
}
var f = fn1();
f();// 3
f();// 4
// 创建内部函数对象时fn1()产生闭包 所以上面只有一个闭包 如果还想产生一个闭包 则再调用一次外部函数fn1()即可
(2)将函数作为实参传递给另一个函数调用
function showDelay (msg, time) {
setTimeout(function () {
alert(msg);
}, time)
}
showDelay('cara', 2000);
5.闭包的作用:
(1)使用函数内部的变量再函数执行完后 仍然存活在内存中(延长了局部变量的生命周期)
(2)让函数外部可以操作(读写)到函数内部的数据(变量/函数)
6.判断闭包产生了几个 主要看外部函数调用了几次
问题
1.函数执行完后 函数内部声明的局部变量是否还存在
一般是不存在的 存在于闭包中的变量才可能存在(首先还是要闭包所在的函数对象不是垃圾对象才会存在
如:在下面代码中var f = fn1(); fn1函数执行完后 闭包所在的函数对象(fn3函数对象)被f引用 不是垃圾对象 所以存在于闭包中的变量a还存在
但如果var f = fn1(); 变为fn1(); 则fn1函数执行完后 闭包不在了 在于闭包中的变量也不在了)
2.在函数外部能直接访问函数内部的局部变量吗?
不能 但是我们可以通过闭包让外部操作他
function fn1 () {
var a = 2;
function fn2 () {
a++;
console.log(a);
}
function fn3 () {
a--;
console.log(a);
}
return fn3;
}
var f = fn1();// 这行代码执行完毕后 局部变量a fn3函数对象存在 fn2变量 fn2函数对象 fn3变量都被释放了
/*
a存在是因为a在闭包中
fn2变量不存在了是因为他不在闭包里
fn2函数对象被释放是因为fn2变量不存在了 就没有人引用他 他被自动释放了
fn3变量不存在了是因为他不在闭包里 fn3变量中存放的是fn3函数对象的地址值 在fn1函数中 将这个地址值return了 在var f = fn1();这行代码中
f变量指向了fn3函数对象(f变量中存放fn3函数对象的地址值) 所以fn3函数对象存在 fn3变量不在了并不代表fn3函数对象成为垃圾对象了
因为f引用着fn3函数对象
闭包一直存在没有消失是因为f指向了fn1函数中的fn3函数对象 fn3函数对象关联着闭包 闭包中有a变量
*/
f();// 1
f();// 0
闭包的生命周期:
1.产生:在嵌套内部函数定义执行完时就产生了(不是在调用)
2.死亡:在嵌套的内部函数成为垃圾对象时
function fn1 () {
// 代码执行到这一行的时候闭包就已经产生了 因为存在函数提升
var a = 2;
function fn2 () {
a++;
console.log(a);
}
return fn2;
}
var f = fn1();
f();// 3
f();// 4
f = null;// 闭包死亡 因为包含闭包的函数对象成为了垃圾对象
function fn () {
var a = 2;
var fn2 = function () {
a++;
console.log(a);
}
// 代码执行到这一行的时候闭包就已经产生了 因为内部函数定义(123-126行)执行完成
return fn2;
}
var fnn = fn();
fnn();// 3
fnn();// 4
fnn = null;// 闭包死亡 因为包含闭包的函数对象成为了垃圾对象
闭包的应用:定义JS模块
1.什么是JS模块:
具有特定功能的JS文件 该JS文件具有以下要求
将所有的数据和功能都封装在一个函数内部(私有的)
只向外暴露一个包含n个方法的对象或函数
模块的使用者只需要通过模块暴露的对象调用方法来实现对应的功能
2.要想让数据私有 必须把数据放在函数中 因为有函数才会有作用域 一个作用域就是一个封闭的空间
3.JS文件中的函数有两种写法:普通函数 匿名函数
(1)普通函数:
新建JS文件 内容如下:
function myModule () {
// 闭包中有msg
//私有数据
var msg = 'cara';
// 操作数据的函数
function doSomething () {
console.log('doSomething ()' + msg.toUpperCase());
}
function doOtherthing () {
console.log('doOtherthing ()' + msg.toLowerCase());
}
//向外暴露对象(给外部使用的方法)
return {
doOtherthing: doOtherthing,
doSomething: doSomething
}
}
在HTML文件中 使用:
<script type="text/javascript" src="myModule.js"></script>
<script type="text/javascript">
// 要想得到模块最终返回的对象 必须先执行函数
var module = myModule();
module.doSomething();
module.doOtherthing();
</script>
(2)匿名函数:
新建JS文件 内容如下:
(function (window) {
// 闭包中有msg
//私有数据
var msg = 'cara';
// 操作数据的函数
function doSomething () {
console.log('doSomething ()' + msg.toUpperCase());
}
function doOtherthing () {
console.log('doOtherthing ()' + msg.toLowerCase());
}
// 匿名函数自调用向外暴露对象(给外部使用的方法):把要暴露的东西添加为window的属性
window.myModule2 = {
doOtherthing: doOtherthin,
doSomething: doSomething
}
})(window)
/*
最好用上面那种写法 因为上面那种写法可以进行代码压缩 可以将函数中的变量压缩成一个个字母 下面这种不会进行代码压缩
(function () {
// 闭包中有msg
//私有数据
var msg = 'cara';
// 操作数据的函数
function doSomething () {
console.log('doSomething ()' + msg.toUpperCase());
}
function doOtherthing () {
console.log('doOtherthing ()' + msg.toLowerCase());
}
// 匿名函数自调用向外暴露对象(给外部使用的方法):把要暴露的东西添加为window的属性
window.myModule2 = {
doOtherthing: doOtherthin,
doSomething: doSomething
}
})()
*/
在HTML文件中使用:
<script type="text/javascript" src="myModule2.js"></script>
<script type="text/javascript">
// 引入JS文件就可以用了
myModule2.doSomething();
myModule2.doOtherthing();
</script>
4.匿名函数的写法用起来比较方便 因为如果是普通函数的话 要想得到模块最终返回的对象 必须先执行函数 但是匿名函数向外暴露函数时 把要暴露的东西添加为window的属性 使用时直接使用即可
闭包的缺点和解决方案
1.闭包缺点:函数执行完后 函数内的局部变量没有释放 占用内存事件会变长 容易造成内存泄露
2.如何解决:能不用闭包就不用闭包 及时释放
内存溢出与内存泄露
1.内存溢出:一种程序运行出现的错误 当程序运行需要的内存超过了剩余的内存时 就会抛出内存溢出的错误
2.内存泄露:
(1)占用的内存没有及时释放
(2)内存泄露积累多了就会导致内存溢出
3.常见的内存泄露:
(1)意外的全局变量
(2)没有及时清理的计时器或回调函数
(3)闭包
// 意外的全局变量
function fn () {
a = 3;
console.log(a);
}
fn();// fn执行完以后 a并没有销毁
相关题目
var name = 'The Window';
var object = {
name: 'My Object',
getNameFunc: function () {
return function () {
return this.name;
};
}
};
alert(object.getNameFunc()());// The Window
/*
通过object调用getNameFunc函数 得到getNameFunc函数的返回值(函数) 然后执行这个函数()
上述代码相当于:
function () {
return this.name;
}()
所以this指向window 输出The Window
*/
// 上面的代码没有闭包 下面的代码有闭包 闭包中存的是that
var name2 = 'The Window';
var object2 = {
name2: 'My Object',
getNameFunc: function () {
var that = this;
return function () {
return that.name2;
};
}
};
alert(object2.getNameFunc()());// My Object
/*
that中保存的是调用getNameFunc函数时的this 在上述代码中是object2
*/
function fun (n, o) {
console.log(o);
return {
fun: function (m) {
return fun(m, n);
}
};
}
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);// undefined
var b = fun(0).fun(1); fun(2).fun(3);// undefined 2
var c = fun(0).fun(1); c.fun(2); c.fun(3);// undefined
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);// undefined 0 0 0
执行var a = fun(0);返回一个对象{fun: function (m) {return fun(m, n);} 此时闭包产生了 闭包中的值是n = 0 后面执行a.fun(1);时其实执行的是:function (1) {return fun(1, 0);}函数 (注意:n是闭包中的值) 接下来又执行fun(1, 0) 打印输出0 执行a.fun(1);产生了新的闭包 闭包中的值是m = 1 但是由于返回的fun(m, n);函数没有指针引用 所以闭包很快就消失了 后面执行a.fun(2); a.fun(3);其实用的都是a中的闭包(n = 0) 所以打印输出的全是0
var b = fun(0).fun(1).fun(2).fun(3);// undefined 0 1 2
执行fun(0)返回一个对象{fun: function (m) {return fun(m, n);} 此时闭包产生了 闭包中的值是n = 0 后面执行fun(0).fun(1)其实是执行function (1) {return fun(1, 0);}函数 (注意:n是闭包中的值) 返回fun(1, 0) 打印输出0 此时产生闭包 此时闭包中的值是m = 1 执行f(1, 0)会返回对象{fun: function (m) {return fun(m, n);} fun(0).fun(1).fun(2)其实是执行function (2) {return fun(2, 1);}函数 打印输出1 fun(0).fun(1).fun(2).fun(3)其实是执行function (3) {return fun(3, 2);}函数 打印输出2
var c = fun(0).fun(1); c.fun(2); c.fun(3);// undefined 1 1 1
面向对象高级
一.对象创建模式
1.方式一:Object构造函数模式
(1)套路:先创建空Object对象 再动态添加属性/方法
(2)适用场景:起始时不确定对象内部数据
(3)问题:语句太多
/*
一个人:
name: "TOM" age: 12
*/
var p = new Object();
p.name = 'TOM';
p.age = 12;
p.setName = function (name){
this.name = name;
}
2.方式二:对象字面量模式
(1)套路:使用{}创建对象 同时指定属性/方法
(2)适用场景:起始时对象内部数据是确定的
(3)问题:如果创建多个对象 有重复代码
/*
一个人:
name: "TOM" age: 12
*/
var p = {
name: 'TOM',
age: 12,
setName: function (name){
this.name = name;
}
}
// 如果创建多个对象 代码很重复
var p2 = {
name: 'JACK',
age: 13,
setName: function (name){
this.name = name;
}
}
3.方式三:工厂模式
(1)套路:通过工厂函数动态创建对象并返回
(2)适用场景:需要创建多个对象
(3)问题:对象没有一个具体的类型 都是Object类型
/*
一个人:
name: "TOM" age: 12
*/
function createPerson (name, age) {
var obj = {
name: name,
age: age,
setName: function (name){
this.name = name;
}
}
return obj;
}
//创建两个人
var p1 = createPerson('TOM', 12);
var p2 = createPerson('JACK', 13);
function createStudent (name, price) {
var obj = {
name: name,
age: price
}
return obj;
}
var p3 = createStudent('CAT', 13000);
// p1 p2是人 p3是学生 类型不一样 但是通过这种方式创建的对象都是Object类型 无法区分具体的类型 但是有时我们需要知道具体的类型
4.方式四:自定义构造函数模式
(1)套路:自定义构造函数 通过new创建对象
(2)适用场景:需要创建多个类型确定的对象
(3)问题:每个对象都有相同的数据 浪费内存
// 定义类型
function Person (name, age) {
this.name = name;
this.age = age;
this.setName = function (name){
this.name = name;
}
}
var p1 = new Person('TOM', 12);
p1.setName('JACK');
console.log(p1.name, p1.age);
console.log(p1 instanceof Person);
function Student (name, price) {
this.name = name;
this.price = price;
this.setName = function (name){
this.name = name;
}
}
var s = new Student('cat',14000);
console.log(s instanceof Student);
var p2 = new Person('BOB',15);
console.log(p1, p2);
两个Person就会有两个setName方法 分别存在两个对象中 会浪费内存 所以我们可以把setName方法 放在__proto__中 两个Person的实例对象的__proto__相同 都是Person的prototype
5.方式五:构造函数+原型的组合模式
(1)套路:自定义构造函数 属性再函数中初始化 方法添加到原型上
(2)适用场景:需要创建多个类型确定的对象
function Person (name, age) {
// 在构造函数中只初始化一般属性
this.name = name;
this.age = age;
}
Person.prototype.setName = function (name){
this.name = name;// 原型的方法setName给实例对象用的 this指向实例对象
}
var p1 = new Person('TOM', 12);
var p2 = new Person('BOB',15);
console.log(p1, p2);
二.继承模式
继承的好处:提高了代码的复用性、维护性、让类与类之间产生了关系 是多态的前提
继承的弊端:类的耦合性增强了,但是开发的原则:高内聚,低耦合。
方式1:原型链继承
1.套路:
(1)定义父类型构造函数
(2)给父类型的原型添加方法
(3)定义子类型构造函数
(4)创建父类型的对象赋值给子类型的原型
(5)将子类型原型的构造属性设置为子类型
(6)给子类型的原型添加方法
(7)创建子类型的对象:可以调用父类型的方法
2.关键:子类型的原型为父类型的一个实例对象
// 如何给supper的实例对象添加方法:给supper的原型prototype添加方法
// 父类型
function Supper () {
this.supProp = 'Supper property';
}
Supper.prototype.showSupProp = function () {
console.log(this.supProp);
}
// 子类型
function Sub () {
this.subProp = 'Sub property';
}
Sub.prototype = new Supper();// 子类型的原型为父类型的一个实例对象
Sub.prototype.constructor = Sub;// 让子类型的原型的constructor指向子类型
Sub.prototype.showSubProp = function () {
console.log(this.subProp);
}
var sub = new Sub();
sub.toString();
/*
在作用域链上找sub 因为sub是变量 再sub原型链上找方法
实例对象找方法 先在本身中找 找不到去原型中找 sub可以调用Object对象的toString方法
是因为sub的原型对象是Object实例对象(内部有一条语句:Sub.prototype = {})
Object实例对象可以找到Object原型上面的方法 toString方法就在Object原型上面
showSupProp方法在Supper的原型上 所以Supper的实例对象可以找到showSupProp方法
所以我让sub的原型成为Supper的实例对象(Sub.prototype = new Supper();)
sub就可以调用Supper的showSupProp方法 也就让Sub继承了Supper
*/
sub.showSupProp();
sub.showSubProp();
方式2:借用构造函数继承(假的)
1.套路:
(1)定义父类型构造函数
(2)定义子类型构造函数
(3)在子类型构造函数中调用父类型构造函数
2.关键:在子类型构造函数中通过call()调用父类型构造函数
function Person (name, age) {
this.name = name;
this.age = age;
}
function Student (name, age, price) {
Person.call(this, name, age);// 相当于this.Person(name, age)
this.price = price;
}
var s = new Student('TOM', 20, 9000);
console.log(s.name, a.age, s.price);
方式3:原型链+借用构造函数的组合继承
1.利用原型链实现对父类型对象的方法继承
2.利用call()借用父类型构造函数初始化相同属性
function Person (name, age) {
this.name = name;
this.age = age;
}
Person.prototype.setName = function (name) {
this.name = name;
}
function Student (name, age, price) {
Person.call(this, name, age);// 为了得到属性
this.price = price;
}
Student.prototype = new Person();// 为了能看到父类型的方法
Student.prototype.constructor = Student;// 修正constructor属性(次要的)
Student.prototype.setPrice = function (price) {
this.price = price;
}
var s = new Student('TOM', 20, 9000);
console.log(s.name, a.age, s.price);
线程机制与事件机制
线程与进程
1.进程:程序的一次执行 他占有一片独有的内存空间 可以通过Windows任务管理器查看进程
2.线程:
(1)是进程内的一个独立执行单元
(2)是程序执行的一个完整流程
(3)是CPU的最小的调度单元
3.应用程序存储在硬盘上 首先我们启动程序A/B 程序一启动就会启动它对应的进程 有些程序可以启动多个进程 比如浏览器 你每打开一个页面他就启动一个进程 这种程序成为多进程程序或多进程应用 并不是所有程序都可以启动多个进程 有的只能启动一个进程 这种程序称为单进程程序 在每个进程中 又可以启动多个线程 这样的程序又成为多线程程序 若只能启动一个线程 那程序被称为单线程程序
4.应用程序必须运行在某个进程的某个线程上 二者缺一不可 否则没法运行代码
5.一个进程中至少有一个运行的线程:主线程 程序启动后自动创建
6.一个进程中也可以同时运行多个线程 我们会说程序是多线程运行的
7.一个进程内的数据可以供其中的多个线程直接共享
8.多个进程之间的数据是不能直接共享的
9.线程池:保存多个线程对象的容器 实现线程对象的反复利用
假如我现在有个程序要执行 我可以先从线程池中拿出一个线程来 去执行某段程序 执行完了后把这个线程放回线程池中 为了复用
10.何为多进程和多线程?
多进程运行:一运用程序可以同时启动多个实例运行
多线程:在一个进程内 同时有多个线程运行
11.比较单线程和多线程
同一时间 多线程可以做两件事 但是单线程只能做一件事
多线程:
优点:能有效提高CPU利用率
缺点:
(1)创建多线程开销
(2)线程间切换开销
(3)死锁与状态同步问题
假设我是单核的 我也可以创建多线程 假设我创建了两个线程 在同一时刻只能执行一个线程 另一个线程暂停 两个线程交替执行 而不是说 CPU先把其中一个线程执行完再执行下一个
单线程:
优点:顺序编程简单易懂
缺点:效率低
12.JS是单线程还是多线程?
JS是单线程运行的 但使用H5中的Web Workers可以多线程运行 也就是说 H5中设置了启动分线程的语法
13.浏览器运行是单线程还是多线程?
多线程
14.浏览器运行是单进程还是多进程?
(1)有的是单进程:火狐 新版IE
(2)有的是多进程:chrome 新版IE
(3)如何查看浏览器是否是多进程运行的呢?
15.浏览器内核:支撑浏览器运行的最核心的程序(代码)
16.不同的浏览器 内核可能不一样:
(1)chrome safari:webkit
(2)火狐:gecko
(3)IE:trident
(4)360 搜狗等国内浏览器:trident + webkit
17.内核由很多模块组成:
(1)JS引擎模块:负责js程序的编译与运行
(2)HTML CSS文档解析模块:负责页面文本的解析
(3)DOM/CSS模块:负责DOM/CSS在内存中的相关处理
(4)布局和渲染模块:负责页面的布局和效果的绘制(内存中的对象)
……(上面的模块运行在主线程 下面的在分线程)
(5)定时器模块:负责定时器的管理
(6)DOM事件响应模块:负责事件的管理
(7)网络请求模块:负责AJAX请求
定时器引发的思考
1.定时器真的是定时执行的吗?
定时器并不能保证真正定时执行 一般会延迟一丁点(可以接受) 也有可能延迟很长时间(不能接收)
2.定时器回调函数是在分线程执行的吗?
在主线程执行的 JS是单线程的 代码都是在主线程执行的
3.定时器是如何实现的?
事件循环模型
<script type="text/javascript">
document.getElementById('btn').onclick = function () {
var start = Date.now();
console.log("启动定时器前");
setTimeout(function () {
console.log("定时器执行了",Date.now() - start);
}, 200)
console.log("定时结束");
//做一个长时间的工作
for (var i = 0; i < 1000000000; i++) {
//此时就会发现 定时器延迟了很长时间
}
}
</script>
<button id="btn">按钮</button>
JS是单线程执行的
1.如何证明JS执行是单线程的?
setTimeout()的回调函数是在主线程执行的 定时器回调函数只有在运行栈中的代码全部执行完后才有可能执行
2.为什么JS要用单线程模式 而不用多线程模式?
JavaScript是单线程的 与他的用途有关 作为浏览器脚本语言 JavaScript的主要用途是与用户互动 以及操作DOM 这决定了它只能是单线程的 否则会带来很复杂的同步问题 只能有一个线程操作界面 否则会混乱
3.代码分类:
(1)初始化代码
(2)回调代码
4.JS引擎执行代码的基本流程:
(1)先执行初始化代码:包含一些特别的代码 设置定时器 绑定事件监听 发送AJAX请求
(2)后面在某个时刻才会执行回调代码
我们启动定时器开始计时 会将回调函数和定时时间给WebAPIS的定时器管理模块setTimeout 到了时间点定时器管理模块setTimeout将回调函数放在回调队列中 待执行 所以定时器延时很长时间就是因为主线程在执行初始化代码 如果我们给某个按钮绑定了onclick事件 会将回调函数交给WebAPIS的DOM(document)事件管理模块 当我一点击 WebAPIS的DOM(document)事件管理模块会将回调函数放在回调队列中 待执行
WebAPIS的模块都在分线程执行 JS引擎在主线程执行 WebAPIS由浏览器负责
回调队列中可能由多个待执行的回调函数当初始化代码执行完毕后才去循环遍历执行回调队列中的代码执行
事件循环模型
1.所有代码分类
初始化执行代码(同步代码):包含绑定DOM事件监听 设置定时器 发送AJAX请求的代码
回调执行代码(异步代码):处理回调逻辑
2.JS引擎执行代码的基本流程:先执行初始化代码再执行回调代码
3.模型的两个重要组成部分:事件(定时器/DOM事件/AJAX)管理模块 回调队列
4.模型的运转流程:
执行初始化代码 将事件回调函数交给对应模块管理
当事件发生时 管理模块会将回调函数及其数据添加到回调队列中
只有当初始化代码执行完后(可能要一定时间)才会遍历读取回调队列中的回调函数执行
5.执行栈execution stack:所有的代码都是在此空间中执行的
6.浏览器内核browser core:(浏览器内核中的模块)
(1)JS引擎模块(在主线程处理)
(2)其他模块(在主/分线程处理)
7.任务队列 消息队列 事件队列:指的是回调队列
8.事件轮询:从任务队列中循环取出回调函数放入执行栈中处理(一个接一个)
9.事件驱动模型:图
10.请求响应模型:浏览器给服务器发送请求 服务器接收请求后处理请求 然后返回响应数据 浏览器接收响应数据并展示
setTimeout(function () {
console.log('timeout222');
alert('222');
}, 2000)
setTimeout(function () {
console.log('timeout111');
alert('111');
}, 1000)
setTimeout(function () {
console.log('timeout000');
}, 0)
function fn () {
console.log('fn');
}
fn();
console.log('alert之前');
alert('--------');
console.log('alert之后');
11.事件循环只有一个,但任务队列可能有多个,任务队列可分为宏任务和微任务。
(1)宏任务:XHR回调、事件回调(鼠标键盘事件)、setImmediate、setTimeout、setInterval、indexedDB数据库操作等I/O以及UI rendering
(2)微任务:process.nextTick、Promise.then、MutationObserver(html5新特性)。
注意进入到任务队列的是具体的执行任务的函数。比如上述例子setTimeout()中的timer函数。另外不同类型的任务会分别进入到他们所属类型的任务队列,比如所有setTimeout()的回调都会进入到setTimeout任务队列,所有then()回调都会进入到then队列。当前的整体代码我们可以认为是宏任务。事件循环从当前整体代码开始第一次事件循环,然后再执行队列中所有的微任务,当微任务执行完毕之后,事件循环再找到其中一个宏任务队列并执行其中的所有任务,然后再找到一个微任务队列并执行里面的所有任务,就这样一直循环下去。
H5 Web Workers多线程
1.H5规范提供了JS分线程的实现 取名为Web Workers
2.相关API:
(1)Worker:构造函数 加载分线程执行的js文件
(2)Worker.prototype.onmessage:用于接收另一个线程的回调函数
(3)Worker.prototype.postMessage:向另一个线程发送信息
3.不足:
(1)慢
(2)Worker内代码不能操作DOM(更新UI)
(3)不能跨域加载JS
(4)不是每个浏览器都支持这个新特性
4.递归本质上是嵌套调用 会使得执行栈中会有很多很多的函数 所以递归的效率很低
5.Web Workers是HTML5提供的一个JavaScript多线程解决方案
我们可以将一些大计算量的代码交由Web Worker运行而不冻结用户界面
但是子线程完全受主线程控制 且不得操作DOM 所以这个新标准并没有改变JavaScript单线程的本质
6.使用:
创建在分线程执行的JS文件
在主线程中的JS中给分线程发送消息并设置回调
7.图解:一个线程在一个时刻只能干一件事 加入主线程给分线程发了十个数据 分线程不能同时处理 只能一个接一个的处理
所以分线程用回调队列(自己的回调队列 和主线程的那个不是同一个)存放主线程发来的数据 通过事件循环机制依次取出数据进行处理
用分线程是因为他不阻塞主线程 不冻结界面
<input type="text" placeholder="数值" id="number">
<button id="btn">计算</button>
<script type="text/javascript">
// 主线程中执行的文件
var input = document.getElementById('number');
document.getElementById('btn').onclick = function () {
var number = input.value;
// 创建一个worker对象
var worker = new Worker('Worker.js')// 参数是分线程中要执行的JS代码
// 绑定接收信息的监听
worker.onmessage = function (event) {
console.log('主线程接收分线程返回的数据' + event.data);
alert(event.data);// event中的data保存着分线程返回回来的信息
}
// 向分线程发送信息
Worker.postMessage(number);
console.log('主线程向分线程发送数据' + number);
console.log(this);// window
}
</script>
分线程Worker.js文件中:
// 分线程中要执行的文件
function fibonacci (n) {
return n <= 2 ? 1 : fibonacci(n - 1) + fibonacci(n - 2);
}
console.log(this);//WorkerGlobalScope全局对象
/*
在全局中可以用document 因为document是window的属性 全局对象属性可以直接使用
那我现在作为worker的分线程来说也有自己的全局对象WorkerGlobalScope WorkerGlobalScope中由onmessage postmessage等可以直接使用
*/
var onmessage = function (event) {
var number = event.data;
console.log('分线程接收到主线程发送的数据:' + number);
// 计算
var result = fibonacci(number);
postMessage(result);
// alert(result);// 在这里不能用alert 因为alert是window的方法 我们在主线程可以用alert是因为在主线程中全局对象是window 而window上面由alert
// 分线程不能更新界面 因为在分线程中看不到window
// 分线程中的全局对象不再是window 所以在分线程中不可能更新界面
console.log('分线程接向主线程返回数据:' + result);
}
================== 自己新增的内容 ==============
HashMap和Array的区别?
- 查找效率:
HashMap因为其根据hashcode的值直接算出index,所以其查找效率是随着数组长度增大而增加的。
ArrayMap使用的是二分法查找,所以当数组长度每增加一倍时,就需要多进行一次判断,效率下降 - 扩容数量
HashMap初始值16个长度,每次扩容的时候,直接申请双倍的数组空间。
ArrayMap每次扩容的时候,如果size长度大于8时申请size*1.5个长度,大于4小于8时申请8个,小于4时申 请4个。这样比较ArrayMap其实是申请了更少的内存空间,但是扩容的频率会更高。因此,如果数据量比较大的时候,还是使用HashMap更合适,因为其扩容的次数要比ArrayMap少很多。 - 扩容效率
HashMap每次扩容的时候重新计算每个数组成员的位置,然后放到新的位置。
ArrayMap则是直接使用System.arraycopy,所以效率上肯定是ArrayMap更占优势。 - 内存消耗
以ArrayMap采用了一种独特的方式,能够重复的利用因为数据扩容而遗留下来的数组空间,方便下一个ArrayMap的使用。而HashMap没有这种设计。 由于ArrayMap之缓存了长度是4和8的时候,所以如果频繁的使用到Map,而且数据量都比较小的时候,ArrayMap无疑是相当的是节省内存的。
总结:数据量比较小,并且需要频繁的使用Map存储数据的时候,推荐使用ArrayMap。 而数据量比较大的 时候,则推荐使用HashMap。
HashMap和Object的区别
数组和伪(类)数组的区别
- 数组是一个特殊对象,与常规对象的区别:
(1)当由新元素添加到列表中时,自动更新length属性
(2)设置length属性,可以截断数组
(3)从Array.protoype中继承了方法
(4)属性为’Array’ - 类数组是一个拥有length属性,并且他属性为非负整数的普通对象,类数组不能直接调用数组方法。
本质区别:类数组是简单对象,它的原型关系与数组不同。
json和xml数据的区别
(1)数据体积方面:xml是重量级的,json是轻量级的,传递的速度更快些。
(2)数据传输方面:xml在传输过程中比较占带宽,json占带宽少,易于压缩。
(3)数据交互方面:json与javascript的交互更加方便,更容易解析处理,更好的进行数据交互
(4)数据描述方面:json对数据的描述性比xml较差
(5)xml和json都用在项目交互下,xml多用于做配置文件,json用于数据交互。
call appy bind的异同点
- 相同点: 都可以改变函数内部的this指向。
- 区别点:
call 和 apply 会调用函数,并且改变函数内部this指向。
call 和 apply 传递的参数不一样,call 传递参数arg1,arg2…形式 apply 必须数组形式[arg]
bind 不会调用函数,可以改变函数内部this指向。 - 应用:
call 经常做继承。
apply 经常跟数组有关系,比如借助于数学对象实现数组最大值最小值。
bind 不调用函数,但是还想改变this指向,比如改变定时器内部的this指向。
new一个对象后发生了什么
- 创建一个实例对象
- 将构造函数的this指向该实例对象
- 执行构造函数
- 返回实例对象
for, forEach, for in, for of, map区别
- forEach遍历数组值 不能使用 break 语句或使用 return 语句
- for in遍历对象的键(key) 或者数组下标 不推荐循环一个数组
- for of遍历数组值(value) 允许遍历 Arrays(数组), Strings(字符串), Maps(映射), Sets(集合)等可迭代的数据结构等