0.JS高级思维导图
1.基础深入总结
0.思维导图
1.数据类型
1.分类
1. 基本类型
- String: 任意字符串
- Number:任意数字
- boolean:true,false
- undefined:undefined
- null:null
2. 引用类型
- Object:任意对象都是Object类型
- Function:函数,一种特别的对象,内部包含可以执行代码
- Array:数组,一种特别的对象,它的属性是数值下标,内部数据有序!
2.判断
1. typeof(返回数据类型的字符串表达)
- String: ‘string’
- Number:‘number’
- boolean:‘boolean’
- undefined:‘undefined’
- null:‘object’(判断不了是不是null,你可以用===来判断)
- 另外三个引用类型:function返回‘function’哦!!其他两个返回Object。
2. instanceof(判断对象的具体类型是否属于XXX)
var b1 = {
b2: [1, 'abc', console.log],
b3: function () {
console.log('哈哈哈');
}
}
console.log(b1 instanceof Array); //false
console.log(b1.b2 instanceof Array); //true
console.log(b1.b3 instanceof Function); //true
3. ===
- 基本数据类型只可以用来判断undefined,null(因为只有这两种有一个值啊,你比如说string类型,你让他 ===于谁??boolean类型你让他 ===于true还是false???)
console.log(b1.b5 === undefined);
console.log(b1.b4 === null);
//加上了typeof和==与就可以转化为boolean形式用于判断
console.log(typeof (b1.b3) === 'function');
3.相关问题
如果一个东西后面跟了一个小括号,那么这个是一个函数,你要学会推理,没遇到一个东西要首先去推理他的数据类型,这样变回清晰很多,比如console.log,这是什么?后面有括号,这显然是一个函数,同时console后面有点,肯定是一个obj。
1.undefined和null的区别?
一个是没有赋值,一个是赋值了但是为null
2. 什么时候给变量赋值为null?
为什么typeof(null)为object??
初始赋值为null,表明将要赋值为对象
结束时赋值为null,释放对象,让b指向的对象成为垃圾对象,被浏览器的垃圾回收器回收
// 起始,表明将要赋值为对象
var b = null;
// 确定对象就赋值
b = {
name: 'wyx'
}
// 最后(释放对象,让b指向的对象成为垃圾对象,被浏览器的垃圾回收器回收)
b = null;
//让b=2也可以,实现了回收垃圾对象的目标,但是又多了一个没用的2.
console.log(b);
3. 严格区别变量类型与数据类型?
- 数据类型 (基本类型和对象类型)
- 变量类型(基本类型和引用类型:变量内存值的类型)
var c={}
2. 数据,变量和内存
1. 什么是数据
- 存储在内存中代表特定信息的东西,本质上是0101二进制,eg:var age=18,18就是一个数据(存在内存中;代表特定年龄,二进制)
- 数据的特点:可传递,可运算
- 内存中所有操作(算数,逻辑,赋值运算,运行函数)的目标:数据
2. 什么是内存
- 内存条通电以后产生的可存储数据的空间(临时的)
- 内存产生和死亡:内存条(电路板)⇒ 通电 ⇒ 产生内存空间 ⇒ 存储数据并处理 ⇒ 断电 ⇒ 内存空间和数据都消失
- 一块小内存的两个数据(一个地址值数据,一个内部存储的数据值)
- 内存的分类(栈:全局变量和局部变量,堆:对象)
3. 什么是变量
- 由变量名和变量值组成
- 一个变量都对应一块小内存,变量名用来查找对应的内存,变量值存储在内存中。
4. 三者之间的关系
- 内存是用来存储数据的空间
- 变量是内存的标识
5.相关问题
1.关于赋值与内存的问题?
问题: var a = xxx,a内存中到底保存的是什么?
- xxx是基本数据, 保存的就是这个数据
- xxx是对象, 保存的是对象的地址值
- xxx是一个变量, 如果变量是基本数据类型,保存的是基本数据,如果变量是引用类型,保存的是地址值
2.关于引用变量赋值问题?
var obj1 = { name: 'jack' };
var obj2 = obj1;
obj1.name='tom';
console.log(obj2.name) //tom
var a = { name: 'wyx' }
function b(obj) {
obj = { name: 'zjk' }
}
b(a);
console.log(a); //wyx
- 直接赋值后,两个引用变量指向同一个对象(这两个变量保存的是同一个地址值)
- 通过一个变量修改对象内部数据,另一个变量看到的是修改之后的数据
3.关于数据传递问题?
在js调用函数时,传递变量参数时,是值传递还是引用传递?
- 理解1:都是值传递,一个是基本值,一个地址值
- 理解2:基本类型是值传递,也有可能是引用传递(地址值)
var a = 3
function fun(a) {
a = a + 1;
}
// 调用函数形参赋值时,执行了a=a操作,是把a的值3赋值给形参a,执行a=a+1对原来的a没有任何影响
fun(a);
console.log(a); //3
var obj = { name: 'wyx' };
// 执行了obj=obj,把obj的值,也就是地址值赋给了形参obj,所以指向同一个对象,执行obj.name=zjk对原来的obj有影响了哈
function fun2(obj) {
obj.name = 'zjk'
}
fun2(obj);
console.log(obj.name);
JS引擎如何管理内存?
- 内存生命周期
- 分配内存空间,得到他的使用权
- 存储数据,可以反复进行操作
- 释放小内存空间
- 释放内存
- 局部变量:函数执行完自动释放
- 对象:先成为垃圾对象 ⇒ 垃圾回收器回收
3.对象
1.什么是对象
- 多个数据的封装体
- 用来保存多个数据的容器
- 一个对象代表现实世界中的一个事物
2.为什么要用对象
- 统一管理多个数据
3.对象的组成
- 属性:由属性名(本质都是字符串!但是语法上可以省略引号)和属性值(任意类型)组成
- 方法:一种特别的属性,属性值是函数,包含可以执行的代码
4.如何访问对象数据
- 对象名.属性名 :编码简单,不能通用(当属性名包含空格和‘ - ’时,变量名不确定时)
- 对象名[‘属性名’] :编码复杂,可以通用
4.函数
1.什么是函数
- 实现特定功能的n条语句的封装体
- 只有函数是可以执行的,其他类型的数据都不可以执行
2.为什么要用函数
- 提高代码复用率
- 便于阅读交流,体现了一种封装的思想(阅读者只需要看一遍就知道这一个函数的逻辑)
3.如何定义函数
- 函数声明 function fun(){}
- 函数表达式 var fun=function(){}
4.如何执行/调用函数
- 直接调用:fun()
- 通过对象调用:obj.fun()
- new调用:new fun()
- test.call/apply(obj): 临时让test成为obj的方法调用
var obj = {}
function fun(name) {
this.name = name
}
fun.call(obj, '王雨欣');
//说明临时成为了obj的方法被调用
console.log(obj.name);
5.回调函数
- 什么是回调函数
- 你定义的
- 你没有调
- 但是它执行了
- 常见回调函数(ajax请求,生命周期,dom事件,定时器)
<script>
window.onload = function () {
var btn = document.querySelector('button');
// 这个成为dom事件回调函数
btn.onclick = function () {
alert(this.innerHTML)
}
}
// 定时器回调函数
setTimeout(function () {
alert('到点了')
}, 2000)
</script>
6.IIFE(匿名函数自调用:立即调用函数表达式)
1. 理解
immediately-invoked function expression,也称作匿名函数自调用
- 假如我写了这样一个函数
- 接下来只能对他做两件事(存起来 或者 立即执行)
- 假如存起来,那么可以var test=function(){} 或者 function test(){}这两种存法,存起来稍后再用
- 假如立即执行,那么把这家伙当做一个整体,后面加上括号即可
2. 作用:
- 隐藏实现,用来写模块
- 不污染全局的命名空间
- 用他来写js模块
<script>
var a = 4;
(function () {
var a = 3;
console.log(a + 3);
})()
console.log(a)
//在前面写上封号,是为了和前面的数据不搞在一起,当然你加在(a)后面也可以
; (function () {
var a = 1
function test() {
console.log(++a);
}
//向外暴露一个全局函数
window.$ = function () {
return {
test: test
};
}
})()
//$是一个函数,加括号调用,执行后返回一个对象,再执行这个对象的test方法。
$().test();
</script>
7.函数中的this!
- this是什么
- 任何函数本质上都是通过某个对象来调用的(看是你明确指定的还是?如果没有明确指定就是window来调用的)
- 所有函数内部都有一个变量,就是this!!
- 它的值是调用函数的当前对象
- 如何明确this的值(着重理解一下下面的例子)
1.以函数的形式调用时,this永远都是window
2.以方法的形式调用时,this就是调用方法的对象
3.以构造函数的形式调用时,this就是新创建的对象
4.使用call和apply调用时,this就是指定的那个对象
5.在全局作用域中this代表window
6.箭头函数中的this指向是固定不变的,一直指向的是定义函数的环境
7.定时器 setInterval setTimeout 里面回调函数的this指向window
<script>
function Person(color) {
console.log(this);
this.color = color;
this.getcolor = function () {
console.log(this);
return this.color;
};
this.setcolor = function (color) {
console.log(this);
this.color = color;
};
}
Person('red'); //只执行第一条语句,this是谁?window
var p = new Person('yello'); //只执行第一条语句,this是谁?p
p.getcolor(); //p
var obj = {};
p.setcolor.call(obj, 'balck'); //obj
var test = p.setcolor;
test(); //window
function fun1() {
function fun2() {
console.log(this);
}
fun2();
}
fun1(); //window
</script>
<script>
var a = 1;
a = 2;
window.a = 3;
function Test() {
let a = 4;
// 这里的this是一个匿名对象,就相当于给Test这个匿名对象加了一个属性a
this.a = 5;
console.log(a); //打印的是4,现在内部找有没有a(有啊,等于4),没有的话再去外部找a
console.log(this.a); //打印的是5
setTimeout(function () {
console.log(a); // 打印的是4,就是打印内部的a
})
setTimeout(function () {
console.log(this.a); //打印的是,3,因为定时器 setInterval setTimeout 里面回调函数的this指向window
})
setTimeout(() => {
console.log(a); //打印的是4,箭头函数中的this指向是固定不变的,一直指向的是定义函数的环境,这里也就是匿名对象
}, 30)
setTimeout(() => {
console.log(this.a); //打印的是5,这里的this指的是匿名对象Test {a: 5},所以输出5
}, 40)
}
new Test();
</script>
2.函数高级
0.思维导图
1.原型与原型链
1.原型(prototype)
- 概念
- 每个函数都有一个prototype属性,默认指向一个空对象,称为原型对象
- 原型对象中有一个属性constructor,它指向函数对象
- 给原型对象添加的属性方法
- 函数的所有实例对象可以访问prototype属性指向的对象中的方法。
2.显示原型和隐式原型
-
每个构造函数function都有一个prototype,即显示原型(属性),默认指向一个空的obj对象
-
每个实例对象都有一个__proto__,成为隐式原型(属性),默认指向一个空的obj对象
-
实例对象的隐式原型的值为其对应的构造函数的显性原型的值,他们两个保存了相同的地址值
-
内存结构图
-
总结
- 函数的prototype属性:在定义函数时自动添加,赋值默认为一个空的obj对象
- 实例对象的__proto__属性,在创建对象时自动添加,赋值为对应显示原型的地址值。
- 函数内部其实执行了这样一条语句(this.prototype={})
- 程序员能直接操作显式原型,但是es6之后也可以直接操作隐式原型。
3.原型链(隐式原型链)图解
1. 作用:查找对象的属性
访问一个对象的属性时,
先在自身属性中查找,找到返回
如果没有,再沿着_ proto__ 这条链向 上查找,找到返回
如果最终没找到,返回undefined
2. 图解1:
- fn.test1();从fn出发,找到Fn的实例对象,找到test1,nice
- fn.test2();从fn出发,找到Fn的实例对象,找不到test2,再从__proto__出发,找到test2();nice
- fn.tostring();从fn出发,找到Fn的实例对象,找不到tostring(),再从__proto__出发,找不到tostring;再次从__prototype__,出发,在obj对象的原型中找到,but始终找不到啊
3. 图解2:
- P17后半部分,重点是你要理解,基础的function,Function,Object都是由构造函数Function创建的实例对象,都拥有__proto__,而且一定牢记这句话(实例对象的隐式原型的值等于构造函数的显式原型的值)
4. 补充!
- 函数的显式原型指向的对象:默认是object空实例对象
(但是Object不满足,因为他的显示原型对象的隐式原型的值为null,为原型链的终点) - 所以Object.prototype instanceof Object ⇒ false
A instanceof B 就是从A 的隐式原型链里面找是否含有B的显式原型,那么Object.prototype的隐式原型__proto__已经是最顶端,为null,自然也就不能往下找了
- 对比分析Function.prototype instanceof Object
Function.prototype默认是一个空对象⇒ 是Object的实例⇒ 实例对象都有__proto__⇒ 指向其构造函数的显式原型⇒ 指向Object.prototype ⇒ 所以返回true
-
所有函数都是Function的实例,包含他自己:Function是Function自身的实例
所以Function.proto=Function.prototype; -
Object的原型对象时隐式原型链的尽头,因为Object.proto==null.
5.原型链属性问题
- 读取对象的属性值时:也会自动到原型链中查找
- 方法一般定义在原型中,属性一般般通过构造函数定义在对象本身上,因为一般每个实例对象的属性都是不一样的,但是共用一个方法,把方法定义在实例对象里面,就可以避免在构造实例对象的时候,反复创建函数,造成内存空间浪费。
6. 探索instanceof
- 在4的补充里面已经说了,翻回去看,A instanceof B 就是从A 的隐式原型链里面找是否含有B的显式原型。下面的图解和例子后续都好好理解一下
7. 原型的面试题
见面试题的笔记集合!
2. 执行上下文与执行上下文栈(stack)
1.变量提升与函数提升
0. 引例:
var a = 3;
function fn() {
console.log(a);
var a = 4;
}
fn(); //undefined
1. 变量声明提升
- 通过var 定义或者声明的变量或者函数,在定义语句之前就可以访问到,只不过值是undefined,意思就是把变量声明提前执行了,定义后面var的时候再执行
2. 函数声明提升
- 通过function声明的函数,在之前就可以直接调用,值就是函数的定义哦
3. 问题:变量提升和函数提升是如何产生的?
1、在js中js引擎会优先解析var变量和function定义!在预解析完成后从上到下逐步进行!
2、解析var变量时,会把值存储在“执行环境”中,而不会去赋值,值是存储作用!例如:
alert(a); var a = 2; 这时会输出undifiend,意思是没有被初始化没有被赋值!
这并不是没有被定义,错误了的意思!
3、在解析function时会把函数整体定义,这也就解释了为什么在function定义函数时为什么可以先调用后声明了!其实表面上看是先调用了,其实在内部机制中第一步实行的是把以function方式定义的函数先声明了(预处理)
2.执行上下文
1. 代码分类
- 全局代码
- 函数(局部)代码
2. 全局执行上下文
- 在执行全局代码前将window确定为全局执行上下文
- 对全局数据进行预处理
- var 定义的全局变量 ⇒ undefined,添加为window的属性
- function声明的全局函数 ⇒ 赋值(fun),添加为window的方法
- this ⇒ 赋值(window)
- 开始执行全局代码
3. 函数执行上下文
- 在调用函数,准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的,存在于栈中),对局部数据进行预处理
- 形参变量⇒ 赋值(实参)⇒ 添加为执行上下文的属性
- arguments⇒ 賦值(实参列表),添加为执行上下文的属性,
- var定义的局部变量⇒ undefined, 添加为执行上下文的属性
- function声明的函数⇒ 赋值(fun)。 添加为执行上下文的方法
- this⇒ 赋值(调用函数的对象)
- 开始执行函数体代码
3.执行上下文栈
1. 理解
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);
bar(10); //30,因为调用bar(10)的时候,上面的两条赋值语句都执行了哦
- 在刚刚的引例中(两个bar(10)),产生了5个函数上下文执行对象(调用时产生),四次调用,一次window。
- 产生的上下文:调用函数的次数加上全局执行上下文
- 理解
- 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
- 在全局执行上下文(window)确定后,将其添加到栈中(压栈)
- 在函数执行上下文创建后,将其添加到栈中(压栈)
- 在当前函数执行完后,将栈顶的对象移除(出栈)
- 当所有的代码执行完后,栈中只剩下window
2. 流程分析
4.执行上下文/栈面试题
见面试题合集。
3. 作用域与作用域链
1.理解
- 就是一块地盘,一个代码段所在的区域
- 它是静止的(相对于上下文对象),在编写代码时就确定了
2.分类
- 全局作用域
- 函数作用域
- 没有块作用域(ES6有了)
比如下面的例子,在java里面是有块作用域的,就是if的{}里面的内容外面不可见,但是es6之前 js无
if(true){
var c=3;
}
console.log(c);
3.作用
- 隔离变量,不同作用域下同名变量不会有冲突。
4.作用域与执行上下文
1. 区别1
- 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义的时候就已经确定了,而不是在函数调用时。
- 全局执行上下文环境是在全局作用域确定之后,js代码马上执行之前创建
- 函数执行上下文是在函数调用时,函数体代码执行之前创建。
2. 区别2
- 作用域是静态的,只要函数定义好了就一直存在,不会再变
- 上下文环境是动态的,调用函数的时候创建,函数执行完毕上下文环境就会被释放
3. 联系
- 上下文环境(对象)是从属于所在的作用域
- 全局上下文环境⇒ 全局作用域
- 函数上下文环境⇒ 对应的函数使用域
5.作用域链
- 实例1
1. 理解
- 多个上下级关系的作用域形成的链,他的方向是从下往上的(从内往外)
2. 作用
查找变量就是沿着作用域链来查找
- 在当前作用域的执行上下文中查找对应的属性,如果有就直接返回,没有进入上一级作用域
- 在上一级作用域中查找对应的属性,如果有就直接返回,没有重复执行上述操作,知道全局作用域,如果还找不到就抛出找不到的异常
6.面试题
见面试题合集
4. 闭包
和原型合称js两大神兽hhh
0. 引例:循环遍历加监听
<script>
var btn = document.getElementsByTagName('button');
var length = btn.length;
// 遍历加监听
for (var i = 0; i < length; i++) {
var bt = btn[i];
bt.onclick = function () {
alert('第' + (i + 1) + '个')
}
}
// 改进方法1
for (var i = 0; i < length; i++) {
var bt = btn[i];
// 把bt的下标绑定在自己身上
bt.index = i;
bt.onclick = function () {
alert('第' + (this.index + 1) + '个')
}
}
//改进方法2
for (var i = 0; i < length; i++) {
(function (i) {
var bt = btn[i];
// 把bt的下标绑定在自己身上
bt.index = i;
bt.onclick = function () {
alert('第' + (this.index + 1) + '个')
}
})(i)
}
</script>
1.闭包的理解 Closure
1. 如何产生闭包
- 当一个嵌套的内部子函数引用了嵌套的外部父函数的变量(函数)时,就产生了闭包。注意:是在外部函数执行上下文中创建内部函数时闭包产生的,不是执行时,是创建时。
2. 闭包是什么
- 使用Chrome调试查看(打断点再刷新)
- 理解一:闭包是嵌套的内部函数(绝大多数人)
- 理解二:包含被引用变量(函数)的对象
- 注意:闭包存在于嵌套的内部函数中
3. 产生闭包的条件
- 函数嵌套
- 内部函数引用了外部函数的数据(变量/函数),执行函数定义就会产生闭包,不用调用内部函数,但是外部函数要调用。调用外部函数产生新的闭包,调用内部函数不会产生新的闭包
2.常见的闭包
1. 将函数作为另一个函数的返回值(将内部函数作为外部函数的返回值)
分析
1.产生了1个闭包,执行fn1的时候创建的(就看你外部函数调用了几次,外部的哈)
Call Stack 调用栈 其中的anonymous:全局上下文栈
2. 将函数作为实参传递给另一个函数调用
3.闭包的作用
- 使得函数内部的变量在函数执行完后,仍然存活在内存中,延长了局部变量的生命周期
- 让函数外部虽然看不见,但是可以操作(读写)函数内部的数据(变量,函数)
- 问题
- 函数执行完后,函数内部声明的局部变量是否还存在?一般不在了,只有存在闭包中的变量才存在,比如我这个如果不用。
- 在函数外部能直接访问函数内部的局部变量吗?外部不能直接看,但是可以通过闭包的方式操作内部数据。
var f=fn1(),那么都会成为垃圾对象
函数执行完毕后,函数没了,之所以存在是因为f执行了那个函数对象的地址值
4.闭包的生命周期
- 产生:在嵌套内部函数定义执行完了时就产生(不是调用,不是有函数提升嘛)。
- 死亡:在嵌套的内部函数成为垃圾对象时。
5.闭包应用_自定义JS模块
1. 什么是js模块
- 具有特定功能的js文件
- 将所有的数据和功能都封装在一个函数内部
- 只向外暴露一个包装n个方法的对象或者函数
- 模块的使用者 只需要通过模块暴露的对象调用方法来实现对应的功能
2.js模块具体实现步骤
- 有两种写法
- 写法1
1.创建一个js文件
2. 在js文件里写上一个函数
3. 在这个函数最后return 一个函数或者对象(当想要return多个数据时就用对象)
4. 在html文件引入这个js文件,创建一个变量fn保存这个函数调用return回的对象
5. 利用fn调用闭包里的函数即可
- 写法2
1.创建一个js文件
2. 在js文件里写上一个匿名函数自调用
3. 在这个匿名函数自调用最后利用window保存一个对象
4. 在html文件引入这个js文件,由于匿名函数自调用已经在引入文件的时候自己执行了,所以不用再调用
5. 直接利用window的对象里的属性调用闭包里的函数即可
- 源码:
module.js
function myModule() {
// 私有数据
var msg = 'atguigu';
function doSomething() {
console.log('dosomething()' + msg.toUpperCase());
}
function doOtherthing() {
console.log('dootherthing()' + msg.toLowerCase());
}
// 1. 返回一个函数
// return doOtherthing;
// 2. 如果想要暴露两个函数,需要通过对象把他们连个封装起来,返回一个对象
return {
doSomething: doSomething,
doOtherthing: doOtherthing
}
}
module2.js
(function myModule(window) {
// 私有数据
var msg = 'atguigu';
function doSomething() {
console.log('dosomething()' + msg.toUpperCase());
}
function doOtherthing() {
console.log('dootherthing()' + msg.toLowerCase());
}
// 匿名函数自调用暴露的方法:把要暴露的数据添加为window的属性
window.myModule2 = {
doSomething: doSomething,
doOtherthing: doOtherthing
}
})(window)
html:
// 1.函数return
// var fn = myModule();
// fn.doSomething();
// fn.doOtherthing();
// 2.匿名函数自调用
// 2.1前者必需先执行函数,但是第二种只要引入jd文件就可以得到想要的对象
console.log(window.myModule2);
myModule2.doOtherthing();
6.闭包的缺点
- 缺点
- 函数执行完后,函数内部的局部变量没有释放,占有内存时间变长
- 容易造成内存泄漏
- 解决办法
- 能不用闭包就不用
- 及时释放(就让它等于null就ok,不要忘了!让内部函数成为垃圾对象–>回收闭包)
7.内存溢出和内存泄漏
- 内存溢出
- 一种程序运行出现的错误
- 当程序运行需要的内存超过了剩余的内存时,就出抛出内存溢出的错误
- 内存泄露
- 占用的内存没有及时释放
- 内存泄露积累多了就容易导致内存溢出
- 常见的内存泄露:
- 意外的全局变量
- 没有及时清理的计时器或回调函数
- 闭包
8.闭包面试题
见面试题锦集
3.面向对象高级
1.对象创建模式
1.Object构造函数模式
- 先创建object空对象,再动态的添加属性和方法
- 适用场景:起始时不确定内部数据(比如不知道name是啥呀,年龄具体是多少)
- 问题:语句太多
2.对象字面量模式
- 使用{}创建对象,同时指定属性和方法
- 适用场景:内部数据确定,不需要创建多个对象
- 问题:创建多个对象有很多重复代码
3.工厂模式(*)
- 通过工厂函数动态创建对象并返回
- 适用场景:需要创建多个对象
- 问题:对象没有一个具体的类型,都是object类型
4.自定义构造函数模式
- 自定义构造函数,通过new创建对象
- 适用场景:需要创建多个类型确定的对象
- 问题:每个对象都有相同的数据,浪费内存
- 解决办法:把方法放在原型对象里面。
4.构造函数+原型模式
- 就是把公共的方法写在原型里面,在构造函数里面只初始化一般属性
2.继承模式
1.原型链继承
1. 套路
- 定义父类型构造函数
- 给父类型的原型添加方法
- 定义子类型的构造函数
- 创建父类型的对象赋值给子类型的原型
- 将子类型原型的构造属性设置为子类型
- 给子类型原型添加方法
- 创建子类型的对象: 可以调用父类型的方法
2.关键
- 子类型的原型为父类型的一一个实例对象
3.目的
- 子类型可以看见父类型的方法
<script>
// 1.*定义父类型构造函数
function Supper() {
this.supProp = 'supper'
}
// 2. 给父类型的原型添加方法
Supper.prototype.showSupper = function () {
console.log(this.supProp);
}
// 3. 定义子类型的构造函数
function Sub() {
this.subProp = 'sub'
}
// 4.创建父类型的对象赋值给子类型的原型
/*
之所以可以看见tostring方法是因为以前默认的原型对象是一个空对象,也就是object的实例,所以可以
访问到object原型的方法,那么加入让原型对象是supper的实例,那么也就可以访问到supper原型对象上的方法。
*/
Sub.prototype = new Supper();
// 5. 将子类型原型的构造属性设置为子类型,不然supper的实例,constructor指向supper哦
Sub.prototype.constructor = Sub;
console.log(Sub.prototype);
// 6.给子类型原型添加方法
Sub.prototype.showSub = function () {
console.log(this.subProp);
}
// 7.创建子类型的对象: 可以调用父类型的方法
var sub = new Sub();
sub.showSupper();
</script>
2.借用构造函数继承(假的)
1.套路:
- 定义父类型构造函数
- 定义子类型构造函数
- 在子类型构造函数中调用父类型构造函数
2. 关键:
在子类型构造函数中通用call()调用父类型构造函数
<script>
// 1. 定义父类型构造函数
function Supper(name, age) {
this.name = name
this.age = age
}
// 2. 定义子类型构造函数
function Sub(name, age, price) {
// 3. 在子类型构造函数中调用父类型构造
Supper.call(this, name, age)
/*
这个call相当于:this.Person(name,age),借用一下父元素的构造函数嘛
*/
this.price = price
}
var sub = new Sub('wyx', 20, 200000000)
console.log(sub.name, sub.age, sub.price);
</script>
3.组合继承
- 就是原型链继承和构造函数继承的组合
<script>
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.construtor = Student;
Student.prototype.setPrice = function (price) {
this.price = price;
}
var stu = new Student('wyx', '20', 20000000000)
stu.setName('wyxx');
stu.setPrice('400000000000');
console.log(stu);
</script>
4.线程机制和事件机制
0.思维导图
1. 进程与线程
1. 什么是进程
- 程序一次执行,它占有一片独有的内存空间,各个进程的内存空间相互独立
- 可以通过任务管理器查看
2. 什么是线程
- 是进程内的一个独立执行单元
- 是程序执行的一个完整流程
- 是cpu最小的调度单元
3. 图解
- 比如,打开多个页面谷歌浏览器可以同时启动多个进程,程序有单进程的也有多进程的,进程有多线程也有单线程的。(可以同时听歌和玩qq就是两个进程,qq可以同时打语音同时传文件就是两个线程)
4.相关知识 - 应用程序必须运行在某个进程的某个线程上
- 一个进程中至少有一个运行的线程:主线程,进程启动后自动创建
- 一个进程中也可以同时运行多个线程,我们会说程序是多线程运行的
- 一个进程内的数据可以供其中的多个线程直接共享
- 多个进程之间的数据是不能直接共享的
- 线程池(thread pool):保存多个线程对象的容器,实现线程对象的反复利用,复用。
5.相关问题
- 何为多进程与多线程?
多进程运行:一应用程序可以同时启动多个实例运行
多线程:在一个进程内,同时有多个线程运行 - 比较单线程与多线程?
- 单线程
优点:顺序编程,简单易懂
缺点:效率低 - 多线程
优点: 能有效提升CPU的利用 率
缺点: 创建多线程开销,线程间切换开销
,死锁与状态同步问题
- JS是单线程还是多线程?
js是单线程运行的
但是使用H5的web workers可以多线程运行(比如原本注册账号规则去服务器检查,但是明明在本地就可以检查,去服务器检查会造成用户等待时间过长,还造成服务器压力,所以在浏览器端直接设计一种语言就检查了?—>js在这样的背景下产生了,是一种脚本语言。但是如果是单线程,在浏览器点了一个按钮,需要进行10s的计算,如果是单线程,我就在这10s不可以干其他的事情了,所以有多线程的需求,h5就有web workers:注意h5不光包含标签,还包含css样式和一些js新语法) - 浏览器运行是单线程还是多线程?
多线程运行
- 浏览器运行是单进程还是多进程?
有单进程(老版本ie和火狐)也有多进程(谷歌,新版ie,可以用任务管理器查看)
2. 浏览器内核
1. 浏览器内核
- 五款主流的浏览器(IE,chrome,firefox,safari,opera)
- 这五款浏览器的内核不同,也就是浏览器所采用的的渲染引擎(如何显示网页的内容和页面格式信息)不同,正因为内核不同,所以要考虑兼容问题
IE内核Trident
谷歌内核WebKit/Blink
火狐内核Gecko
Safarri内核WebKit
欧朋内核Presto
2. 内核由很多模块组成
主线程有以下四个
- js引擎模块 : 负责js程序的编译与运行
- html,css文档解析模块 : 负责页面文本的解析
- DOM/CSS模块 : 负责dom/css在内存中的相关处理 ,就是生成模块对象。
- 布局和渲染模块 : 负责页面的布局和效果的绘制(内存中的对象)
…
分线程有以下三个
- 定时器模块 : 负责定时器的管理
- DOM事件响应模块 : 负责事件的管理
- 网络请求模块 : 负责ajax请求
3. 定时器引发的思考
1. 定时器真是定时执行的吗?
- 定时器并不能保证真正定时执行
- 一般会延迟一丁点(可以接受)。也有可能延迟很长时间(不能接受)
2. 定时器回调函数是在分线程执行的吗?
- 在主线程执行的,js是单线程的
3. 定时器是如何实现的?
- 事件循环模型(后面讲)
4. js是单线程执行的
1. 如何证明js执行是单线程的?
- setTimeout()的回调函数是在主线程执行的
- 定时器回调函数只有在运行栈中的代码全部执行完后才有可能执行
2. 为什么js要用单线程模式,而不用多线程模式?
- JavaScript的单线程,与它的用途有关。
- 作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。
- 这决定了它只能是单线程,否则会帶来很复杂的同步问题(比如线程1准备更新p标签对象,线程2决定去删除,线程1运行的时候tmd被线程2删了,线程1懵逼了,就报错)
3.代码的分类
- 初始化执行代码(同步代码):包含绑定dom 事件监听,设置定时器, 发送ajax 请求的代码
- 回调执行代码(异步代码):处理回调逻辑
- 初始化代码执行完了才循环遍历回调队列里面的回调函数,执行回调代码。
4. js引擎执行代码的基本流程
- 先执行初始化代码:包含一些特别的代码,比如回调函数(成为异步执行)
* 设置定时器
* 绑定监听
* 发送ajax请求 - 后面在某个时刻才会执行回调代码
5. 浏览器的事件循环(轮询)模型
1. 模型原理图
2. 相关重要概念
- 初始化代码===>回调代码
模型的2个重要组成部分:
事件管理模块
回调队列 - 执行栈(execution stack)
所有的代码都是在此空间中执行的 - 浏览器内核(browser core)
js引擎模块(在主线程处理)
其它模块(在主/分线程处理)
以下三个队列属于同一个callback queue
-
任务队列(task queue)
-
消息队列( message queue)
-
事件队列( event queue)
-
事件轮询( event loop)
—从任务队列中循环取出待处理的回调函数放入执行栈中处理(一个接一个) -
事件驱动模型(event-driven interaction model)
-
请求响应模型(request-response model)
3. 执行流程
假设设定了一个2s的定时器,但是主线程的程序执行了十几秒,但是我们必须保证主线程的程序先执行才能执行回调队列里面的定时器回调代码,所以就导致不是2s之后才执行,而是又延后了很多秒才执行。
- 模型的运转流程(面试时问道可以画图说明)
- 执行初始化代码,将事件回调函数交给对应模块管理
- 当事件发生时,管理模块会将回 调函数及其数据添加到回调列队中
- 只有当初始化代码执行完后(可能要一定时间), 才会一个一个遍历读取回调队列中的回调函数执行。
<script>
function fn1() {
console.log('fn1');
}
fn1()
document.querySelector('button').onclick = function () {
console.log('button is clicked');
}
// button和timer的执行顺序看你button和timer谁点的快,执行的早,顺序不确定哦
setTimeout(function () {
console.log('timer is excuted ');
}, 2000)
function fn2() {
console.log('fn2()');
}
fn2()
</script>
6. h5 web workers
0. 引例:一个递归fib函数
<script>
var text = document.querySelector('input');
document.getElementById('bt').onclick = function () {
// 我需要把这个传给分线程去计算,计算完了返回给主线程
var res = fib(text.value);
console.log(text.value + ' : ' + res);
}
function fib(num) {
// 学会使用三目运算符替代ifelse
return num <= 2 ? 1 : fib(num - 2) + fib(num - 1)
}
</script>
/*
递归的问题:非常的效率很低,占有内存空间大。所以如果输入的num是45的话,就会运行很久,这个时候我们不能够点button,不能够修改input的value值, 整个界面被冻结,不能进行任何操作,因为响应计算机操作的主线程忙着运算。带来了一些问题。
所以可以把大计算量的代码交由webWorker运行而不冻结用户界面,但是子线程完全受主线程控制,(且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质
*/
1. 使用步骤
-
创建一个在分线程执行的js文件:比如woker.js
-
在主线程的js里创建一个worker对象并向他传递将在新线程中执行的脚本的url
var worker = new Worker('woker.js');
-
向分线程发送消息
worker.postMessage(text.value)
-
接收worker传过来的数据函数,绑定接收消息的监听
worker.onmessage = function (event) {
console.log(event.data);
}
- 在分线程的js文件中:
var onmessage = function (event) {
// 分线程接受到主线程发送的数据event.data
var num = event.data;
// 计算
var res = fib(num);
// 像主线程返回分线程计算完的数据
postMessage(res);
}
function fib(num) {
return num <= 2 ? 1 : fib(num - 2) + fib(num - 1)
}
2. 注意事项
- DedicatedWorkerGlobalScope
- 分线程的全局对象不再是window(比如不能调用alert),而是上面这个玩意对象
- 所以在分线程中不可能更新界面,因为全局对象不是window,根本就没有办法操作window里面的数据,只有主线程可以更新界面,分线程除了做一些长时间的计算没啥用
3. 缺点
- 慢:因为传过去又传回来
- 有些浏览器不支持
- 不能跨域加载js
- woker内的代码不能访问dom(更新UI)