目录
JS基础总结深入
一、数据类型
1. 分类
基本数据类型 | 描述 |
---|---|
String | 任意字符串 |
Number | 任意的数字 |
Boolean | true / false |
Undefined | undefined |
Null | null |
对象(引用)类型 | 描述 |
Object | 任意对象 |
Function | 一种特别的对象(可以执行) |
Array | 一种特别的对象(数值下标,内部数据是有序的) |
2. 判断
1)typeof:
可以判断 undefined / 数值 / 字符串 / 布尔值 / function;不能判断 null与object object与array
2)instanceof:A instanceof B A是不是B的实例
判断对象的具体类型
3)===
可以判断 undefined null
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript">
// typeof 判断
var a
console.log(a, typeof a, typeof a==='undefined',a===undefined)
// undefined 'undefined' true true
console.log(undefined==='undefined') //false
a=4
console.log(typeof a==='number') //true
a='atyou'
console.log(typeof a==='string') //true
a=true
console.log(typeof a==='boolean') //true
a=null
console.log(typeof a, a==='null') //object false
// instanceof 判断
var b={
b1:[1, 'abc',console.log],
b2:function(){
console.log('b2')
return function(){
return 'yhwang'
}
}
}
console.log(b instanceof Object, b instanceof Array) //true false
console.log(b.b1 instanceof Array, b.b1 instanceof Object) //true true
console.log(b.b2 instanceof Function, b.b2 instanceof Object) //true true
console.log(typeof b.b2==='function') //true
console.log(typeof b.b1[2]==='function') //true
b.b1[2](666) // 666
console.log(b.b2()()) // b2 yhwang
</script>
</head>
<body>
</body>
</html>
3. 相关问题
1) undefined 与 null 的区别:undefined表示定义未赋值,null定义并赋值,值为null
2) 什么时候给变量赋值为null *初始赋值,表明将要赋值为对象 *结束前,让对象成为垃圾对象
3) 严格区分变量类型和数据类型
*数据的类型:基本类型和对象类型
*变量的类型(变量内存值的类型):基本类型(保存就是基本类型的数据),引用类型(保存的是地址值)
二、数据 变量 内存
1. 数据
存储在内存中代表特定信息的东西,本质上是0101....二进制 特点:可传递可运算 一切皆数据 内存中所有操作的目标:数据 算术运算、逻辑运算、赋值、调用函数传参(形参:局部变量,实参:变量值/数据)
2. 内存
内存条通电后产生的可存储数据的空间(临时的),内存的产生和死亡: 内存条(电路板)--通电--产生内存空间--存储数据--处理数据--断电--内存空间和数据都消失
一块小内存的2个数据:内部存储的数据 地址值
内存分类: 栈(全局变量/局部变量)空间较小 堆(对象)空间较大
3. 变量
可变化的量,由变量名和变量值组成,每个变量对应的一块小内存,变量名是用来查找对象内存,变量值就是内存中保存的数据
4. 数据、内存、变量间的关系
内存是用来存储数据的空间,变量是内存的标识
5. 相关问题
1)var a=xxx,a内存中保存的是什么?
xxx是基本数据,保存的就是这个数据;是对象,被保存的就是对象的地址值;是一个变量,保存的就是xxx的内存内容(可能是基本数据,也可能是地址值)
2)关于引用变量赋值问题
● 多个引用变量指向同一个对象,通过一个变量修改对象内部数据,其他所有变量看到的是修改之后的数据
● 多个引用变量指向同一个对象,让其中一个引用变量指向另一个对象,另一引用变量依然指向前一个
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript">
var obj1={name:'tom'}
var obj2=obj1
obj2.age=12
console.log(obj1.age) // 12
// 2个引用变量指向同一个对象,通过一个变量修改对象内部数据,其他所有变量看到的是修改之后的数据
function fn(obj){
obj.name='A'
}
fn(obj1)
console.log(obj2.name) // A
var a={age:12}
var b=a
a={name:'bob',age:13}
b.age=14
console.log(b.age,a.name,a.age) // 14 bob 13
// 2个引用变量指向同一个对象,让其中一个引用变量指向另一个对象,另一引用变量依然指向前一个
function fn2(obj){
obj={age:15}
}
fn2(a)
console.log(a.age) // 13
</script>
</head>
<body>
</body>
</html>
3)在JS调用函数时传递变量参数时,是值传递还是引用传递
● 理解1:都是值(基本/地址值)传递
● 理解2: 可能是值传递,也可能是引用传递(地址值)
4) JS引擎如何管理内存
* 内存生命周期:分配小内存空间,得到它的使用权;存储数据,可以反复操作;释放小内存空间
* 释放内存:局部变量:函数执行完自动释放;对象:成为垃圾对象--垃圾回收器回收
三、对象
1.对象概述
1)对象: 多个数据的封装体,用来保存多个数据的容器,一个对象代表现实中的一个事物
2) 为什么要用对象:统一管理多个数据
3) 对象的组成:
属性:属性名(字符串)和属性值(任意)组成 方法:一种特别的属性(属性值是函数)
4) 如何访问对象内部数据: ① . 属性名: 编码简单有时不能用 ②['属性名']: 编码麻烦能通用
2. 相关问题
什么时候要使用 ['属性名'] 的方式:①属性名包含特殊符号 - 空格 ②属性名不确定
四、函数
1. 函数概述
1)函数是实现特定功能的n条语句的封装体,只有函数可以执行,其它数据类型不可以
2)使用函数可以提高代码复用,便于阅读交流
3)定义函数:
①函数声明:由于“函数声明提升”特性,因此可以放在调用它的语句之后
②表达式:在使用前必须先赋值,函数表达式中,创建的函数叫匿名函数
4)调用(执行)函数:
* test() 直接调用
* obj.test() 通过对象调用
* new test() new调用
* test.call/apply(obj) 临时让test成为obj的方法进行调用
2. 回调函数
1)回调函数是 自己定义的,自己没有调用,最终它却执行了(在某个时刻或条件下)
2)常见回调函数:
DOM事件回调函数(this指发生事件的DOM元素) 定时器回调函数(this指window)
ajax请求回调函数(后面说) 生命周期回调函数(后面说)
3. IIFE 立即调用函数
IIFE Immediately-Invoked Function Expression
作用:隐藏实现,不会污染外部(全局)命名空间,用它来编码JS模块
下面2个括号()都会立即执行
(function () { /* code */ } ()); 推荐使用这个
(function () { /* code */ })(); 但是这个也是可以用的
4. 函数中的this
1)this 任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是window;所有函数的内部都有一个变量this,它的值是调用函数的当前对象
2)以函数形式调用 this永远是window 以方法形式调用 this是调用方法的那个对象
3)确定this的值 test() :window p.test():p new test():新创建的对象 p.call(obj):obj
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript">
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("yellow"); // this指P
p.getColor(); // this指P
var obj={};
p.setColor.call(obj,"black"); // this指obj,此方法call(参数)相当于调用p的setColor函数,在obj上使用 传递实参black
var test=p.setColor;
test(); // this指window
function fun1(){
function fun2(){
console.log(this);
}
fun2(); // this指window
}
fun1();
</script>
</head>
<body>
</body>
</html>
5. 语句分号
可以不加分号
有2种情况需要加: 小括号开头的前一条语句,中方括号开头的前一条语句,可以在行首加
JS函数高级
一、原型和原型链
1. 函数中的prototype(原型)属性
1)每个函数都有一个prototype属性,默认指向一个Object空对象(即原型对象),原型对象中有一个constructor属性,指向函数对象。
注意:此处的空对象是指没有我们自己添加的属性,但可能会有本身自带的属性
2)给原型对象添加属性(一般都是方法) ,作用:函数的所有实例对象自动拥有原型中的属性(方法) 构造函数和它的原型对象相互引用
2. 显式属性和隐式属性
1)每个函数function都有一个prototype,即显式原型(属性),每个实例对象都有一个__proto__,即隐式原型(属性);实例对象的隐式原型的值为其对应的构造函数显式原型的值
2)函数的prototype属性:在定义函数时自动添加的,默认值是一个空Object对象;实例对象的__proto__属性是在创建对象时自动添加的,默认值为构造函数的prototype属性值;程序员只能直接操作显式原型,不能直接操作隐式原型(ES6之前)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript">
// 定义构造函数
function Fn(){ //内部语句: this.prototype={}
}
// 函数有显式原型prototype
console.log(Fn.prototype);
var fn=new Fn()
// 实例对象有隐式原型 __proto__
console.log(fn.__proto__)
// 实例对象的隐式原型值对应构造函数的显式原型值
console.log(Fn.prototype==fn.__proto__) //true
// 给原型添加方法
Fn.prototype.test=function(){
console.log('test()')
}
// 通过实例调用原型的方法
fn.test() // test()
</script>
</head>
<body>
</body>
</html>
原型对象相当于一个公共区域,所有同一个类的实例都可以访问到这个原型对象的属性,可以将对象中共有的内容统一设置到原型对象中,这样不用分别为每个对象添加,也不影响全局
3)使用in检查对象中是否含有某个属性时,如果对象中没有但原型中有,也会返回true; 可以使用对象的hasOwnPrototype()来检查自身中是否含有该属性,这样只有对象自身中含有属性时才会返回true console.log(mc.hasOwnPrototype("age"))
3. 原型链(隐式原型链)
1)访问一个对象的属性时,先在自身中查找,找到返回;如果没有则沿着__proto__向原型对象中查找,找到返回;如果没有则去原型的原型中寻找,直到找到Object中依然没有找到,返回undefined
① 所有函数的显式原型对象默认是空Object的实例对象(Object函数除外)
② 所有函数都是Function的实例(包含Function和Object函数)
③ Object的原型对象是原型链尽头
④ Object函数也是被Function创建的
2)原型链属性问题
4. 探索instanceof
instanceof 表达式: A instanceof B(如果B函数显式原型对象在A对象原型链上,返回true)
Function是通过new自己产生的实例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript">
function Foo(){}
var f1=new Foo()
console.log(f1 instanceof Foo) //true
console.log(f1 instanceof Object) //true
// Object 作为函数来说,是Function产生的
console.log(Object instanceof Function) //true
//Function的隐式原型是Function的显式原型,Function显式原型的隐式原型对应Object的显式原型
console.log(Function instanceof Object) //true
// Object函数的隐式原型对应Function的显式原型,Function的显式原型的隐式原型是Object的显式原型
console.log(Object instanceof Object) //true
// Function是自己产生的 所以自己是自己的实例对象
console.log(Function instanceof Function) //true
console.log(Object instanceof Foo) //false
</script>
</head>
<body>
</body>
</html>
5. 原型-面试题
注意: A.prototype.n=1 是向A的原型中添加属性n A.prototype={ } 是改变A的原型属性为...
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript">
/* 测试题1 */
// 定义一个函数
function A(){
}
// 向原型中添加了 n 属性
A.prototype.n=1
// 创建一个实例 b里面也可以看到n 通过b的隐式原型看到的
var b=new A()
// 改变原型属性为
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 */
function F(){}
Object.prototype.a=function(){
console.log('a()')
}
Function.prototype.b=function(){
console.log('b()')
}
var f=new F()
console.log(f) // F
console.log(Object.prototype) //Object
// F()本身只有一个隐式原型属性,值是空的object实例对象,而实例对象有个隐式原型属性指向Object Object里面有个a
f.a() // a()
// newF里本身没有a接着找,newF的隐式原型属性对应F的显式原型属性,而构造函数的显式原型属性默认是空的Object对象,Object实例对象可以看到Object原型对象的方法 空的Object对象和a是放在一个容器里面的
//f.b() //报错 找不到
F.a() // a()
F.b() // b()
</script>
</head>
<body>
</body>
</html>
二、执行上下文与执行上下文栈
变量提升和函数提升的现象是由执行上下文导致的
1. 变量提升和函数提升
1)变量声明提升:通过var定义(声明)的变量,在定义语句之前就可以访问到,值:undefined
2)函数声明提升:通过function声明的函数,在之前就可以直接调用,值:函数定义(对象) 注:函数提升必须使用声明的方式,不可以 Fn() var fn=function(){console.log(12345)} ,这样会变量提升
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript">
var a=3
function fn(){
console.log(a)
var a=4
}
fn() // undefined 变量声明提前 打印结果前,函数先在自身找变量,找到了自身的a 但没有赋值所以undefined
console.log(b) //undefined 变量提升
fn2() //可调用 函数提升 fn2() 声明方式 函数提升
fn3() //不能调用 变量提升 fn3 not a function
var b=3
function fn2(){
console.log('fn2()')
}
var fn3=function(){
console.log('fn3()')
}
</script>
</head>
<body>
</body>
</html>
2. 执行上下文
1)代码分类(位置):全局代码 函数(局部代码)
2)全局执行上下文:在执行全局代码前将window确定为全局执行上下文,对全局数据进行预处理: 必须是var 定义的全局变量-->undefined,添加为window的属性; function声明的全局函数-->赋值(fun),添加为window的方法; this-->赋值(window)
注意:必须是var 定义的变量才会被window收集,var a=1; 但是a=1不会
3)函数执行上下文:在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象,对局部数据进行预处理: 形参变量-->赋值(实参)-->添加为执行上下文的属性 ; arguments-->赋值(实参列表),添加为执行上下文的属性;var定义的局部变量-->undefined,添加为执行上下文的方法;this-->赋值(调用函数的对象)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript">
// 全局执行上下文
console.log(a1, window.a1) // undefined
a2() // a2()
console.log(this) //window
var a1=3
function a2(){
console.log('a2()')
}
console.log(a1) // 3
// 函数执行上下文
function fn(a1){
console.log(a1) // 2
console.log(a2) // undefined
a3() // a3()
console.log(this) // window
console.log(arguments) // 伪数组 (2,3)
var a2=3
function a3(){
console.log('a3()')
}
}
fn(2,3)
</script>
</head>
<body>
</body>
</html>
3. 执行上下文栈
结构特点:后进先出 优先级:函数提升>变量提升(优先级越高执行越晚)
▶ 在全局代码执行前,JS引擎就会创建一个栈来存储管理所有的执行上下文对象;、
▶ 在全局执行上下文(window)确定后,将其添加到栈中(压栈)
▶ 在函数执行上下文创建后,将其添加到栈中(压栈)
▶ 在当前函数执行完后,将栈顶的对象移除(出栈)
▶ 当所有的代码执行完后,栈中只剩下window
4. 面试题
三、作用域与作用域链
1. 作用域
一个代码段所在的区域,它是静态的(相对于上下文对象),在编写代码时就确定了;
分类:全局作用域 函数作用域 没有块作用域(ES6有了),JAVA也有
作用:隔离变量,不同作用域下同名变量不会有冲突
作用域数量:n+1 n指函数定义的个数 注意:作用域与函数调用无关,指的是函数定义
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript">
var a=10,
b=20
function fn(x){
var a=100,
c=300
console.log('fn()',a,b,c,x) // fn() 100 20 300 10
function bar(x){
var a=1000,
d=400
console.log('bar()',a,b,c,d,x)
// bar() 1000 20 300 400 100
// bar() 1000 20 300 400 200
}
bar(100)
bar(200)
}
fn(10)
// 共3个作用域
</script>
</head>
<body>
</body>
</html>
2. 作用域与执行上下文
1)区别一:全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了,而不是在函数调用时
2)区别二:作用域是静态的,只要函数定义好了就一直存在,且不会再变化
3)联系:上下文环境(对象)是从属于所在的作用域;全局上下文环境-->全局作用域;函数上下文环境-->对应的函数作用域
3. 作用域链:嵌套的作用域产生的由内到外的一个过程
1)多个上下级关系的作用域形成的链,它的方向是自下而上(由内向外);查找变量时就是沿着作用域链来查找的;如果找a.b a是变量,沿着作用域找到a,再找b属性(沿着原型链找)
2)查找一个变量的规则:在当前作用域下的执行上下文中查找对应的属性,有就直接返回,否则进入上一级作用域的执行上下文中查找,有就直接返回,没有再往上找,直到全局作用域,没有就报错了
4. 面试题
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript">
// 面试题 1
var x=10
function fn(){
console.log(x) // 10
}
function show(f){
var x=20
f()
}
show(fn)
// 面试题 2
var fn=function(){
console.log(fn) // f(){console.log(fn)}
}
fn()
var obj={
fn2:function(){
console.log(this.fn2) // fn2 函数 对象引用
console.log(fn2) // 报错,没写this. 遵循作用域链查找 在全局中找并没有
}
}
obj.fn2()
</script>
</head>
<body>
</body>
</html>
四、闭包
1. 闭包的产生
当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包
理解1:闭包是嵌套在内部函数;理解2:包含被引用变量(函数)的对象(用Chrome调试查看)
注意:闭包存在于嵌套的内部函数中
产生闭包的条件:①函数嵌套 ②内部函数引用了外部函数的数据(变量/函数)
闭包产生的个数:外部函数被调用的次数
2. 常见的闭包:
①将函数作为另一个函数的返回值;② 将函数作为实参传递给另一个函数调用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript">
// 常见闭包: 1. 将函数作为另一个函数的返回值
function fn1(){
var a=2
function fn2(){
a++
console.log(a)
}
// 将函数fn2 暴露出去就可以从外部操作函数的内部变量
return fn2
}
var f=fn1()
f() // 3
f() // 4
// 常见闭包:2. 将函数作为实参传递给另一个函数
function showDelay(msg,time){
setTimeout(function(){
alert(msg)
},time)
}
showDelay('atyou',2000)
</script>
</head>
<body>
</body>
</html>
3. 闭包的作用
1)使函数内部的变量在函数执行完后,仍然存活在内存中
2)让函数外部可以操作(读写)到函数内部的数据(变量/函数)
3)函数执行完后,函数内部声明的局部变量一般不存在,但在闭包中的变量可能存在
4)在函数外部不能直接访问函数内部的局部变量,但在闭包中可以
4. 闭包的生命周期
产生:在嵌套内部函数定义执行完时就产生了(不是在调用时)
死亡:在嵌套内部函数成为垃圾对象时
5. 闭包的应用
自定义JS模块:一个JS模块本质上就是具有特定功能的JS文件,要具有一定特点:将所有的数据和功能都封装在一个函数内部(私有的),隐藏和操作数据,只向外暴露一个包含n个方法的对象或函数
暴露一个行为:函数 多个行为:对象
方式1:return 返回值
方式2: 匿名函数自调用
6. 闭包的缺点及解决
1)缺点:函数执行完后,函数内的局部变量没有释放,占用内存时间会变长,容易内存泄露。解决:能不用闭包就不用,及时释放
2)内存溢出:一种程序运行出现的错误,当程序运行需要的内存超过了剩余的内存时,就会抛出内存溢出的错误
(举例:不断地创建数组)
3)内存泄露:占用的内存没有及时释放,内存泄露积累多了就容易导致内存溢出
4)常见的内存泄露:
①意外的全局变量(不用var定义的)
②没有及时清理的计时器或回调函数
③闭包
7. 面试题
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript">
var name="The Window";
var object={
name:"My Object",
getNameFunc:function(){
return function(){
return this.name; // 此时this没有直接指定
}
}
}
alert(object.getNameFunc()()); // The Window
var name2="The Window";
var object2={
name2:"My Object",
getNameFunc:function(){
var that=this; //此时this指调用getNameFunc的object2,将this赋值给that保存
return function(){
return that.name2;
}
}
}
alert(object2.getNameFunc()()); // My Object
</script>
</head>
<body>
</body>
</html>
终极面试题:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript">
function fun(n,o){
console.log(o)
return{
fun:function(m){
return fun(m,n)
}
}
}
var a=fun(0) // undefined 闭包里的是0
a.fun(1) // 0
a.fun(2) // 0
a.fun(3) // 0
var b=fun(0).fun(1).fun(2).fun(3) // undeifined 0 1 2
var c=fun(0).fun(1)
c.fun(2)
c.fun(3) // undefined 0 1 1
</script>
</head>
<body>
</body>
</html>
8. 闭包注意点
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
如果一定要引用循环变量:方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变,如下:
JS面向对象高级
一、对象创建模式
1. Object构造函数模式
先创建空Object对象,再动态添加属性/方法; 适用于起始时不确定对象内部数据;但语句太多
2. 对象字面量模式
使用{}创建对象,同时指定属性/方法; 适用于起始时对象内部数据是确定的,且创建的对象少,1个; 如果要创建多个对象 有重复代码。
3. 工厂模式
通过工厂函数动态创建对象并返回; 适用于需要创建多个对象;问题是:对象没有一个具体的类型,都是Object类型; 此模式不太用
4. 自定义构造函数
通过new创建对象;适用于需要创建多个类型确定的对象;问题是每个对象都有相同的数据,浪费内存
5. 构造函数+原型的组合模式
自定义构造函数,属性在函数中初始化,方法添加到原型上,适用于需要创建多个类型确定的对象
二、继承模式
1. 原型链的继承:得到方法
定义父类型函数-->给父类型的原型添加方法-->定义子类型的构造函数-->创建父类型的对象赋值给子类型的原型-->将子类型原型的构造属性设置为子类型-->给子类型添加方法-->创建子类型的对象,可以调用父类型的方法
关键:子类型的原型为父类型的一个实例对象
2. 借用构造函数继承(假的):得到属性
定义父类型的构造函数,定义子类型的构造函数,在子类型构造函数中调用父类型构造
关键:在子类型构造函数中通用super()调用父类型构造函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript">
function Person(name,age){
this.name=name,
this.age=age
}
function Student(name,age ,price){
// student 借用了Person的this.name 和 this.age 调用Person
Person.call(this,name,age) //借用构造函数 相当于:this.Person(name,age)
this.price=price
}
var s=new Student('tom',20,3000)
console.log(s.name,s.age, s.price) // tom 20 3000
</script>
</head>
<body>
</body>
</html>
3. 组合继承
原型链+借用构造函数的组合继承
利用原型链实现对父类型对象的方法继承,利用super() 借用父类型构造函数初始化相同属性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript">
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) //初始化属性数据用call
this.price=price
}
// 以上是借用构造函数
// 子类型Student的原型为父类型Person的一个实例对象
Student.prototype=new Person()
// 让子类型原型的constructor指向子类型
Student.prototype.constructor=Student
Student.prototype.setPrice=function(price){
this.price=price
}
var s=new Student('tom',24,40000)
s.setName('bob')
s.setPrice(250000)
console.log(s.name,s.age,s.price) // bob 24 250000
</script>
</head>
<body>
</body>
</html>
附加: new一个对象背后做了什么?
创建了一个空对象;给对象设置__proto__,值为构造函数对象的prototype属性值 this.__proto__=Fn.prototype; 执行构造函数体(给对象添加属性/方法)
JS线程机制与事件机制
一、进程与线程
1. 进程
程序的一次执行,它占有一片独立的内存空间
多进程运行:一应用程序可以同时启动多个实例运行;
2. 线程
进程内一个独立执行单元,是程序执行的一个完整流程,是CPU最小的调度单元
多线程:在一个进程内,同时有多个线程运行
优点:能有效提升CPU的利用率,创建多线程开销 缺点:线程间切换开销,死锁与状态同步问题
单线程:在一个进程内,只有一个线程运行
优点:顺序编程简单易懂 缺点:效率低
3. 相关知识
1)应用程序必须运行在某个进程的某个线程上
2)一个进程中至少有一个运行的线程:主线程,进程启动后自动创建
3)一个进程中也可以同时运行多个线程,我们会说程序是多线程运行的
4)一个进程内的数据可以供其中的多个线程直接共享
5)多个进程之间的数据是不能直接共享的
6)线程池(thread pool):保存多个线程对象的容器,实现线程对象的反复利用
二、浏览器内核
支撑浏览器运行的最核心的程序,不同的浏览器可能不一样。比如:
Chrome,Safri::webkit Firefox:Gecko IE:Trident 360,搜狗等国内浏览器:Trident+webkit
内核由很多模块组成:
主线程 | 分线程 |
---|---|
JS引擎模块:负责JS程序的编译与运行 | 定时器模块:负责定时器的管理 |
html,css文档解析模块:负责页面文本的解析 (分解读取文件) | 事件响应模块:负责事件的管理 |
DOM/CSS模块:负责dom/css在内存中的相关处理 (生成n个DOM对象) | 网络请求模块:负责ajax请求 |
布局和渲染模块:负责页面的布局和效果的绘制(内存中的对象) | |
--------等等 |
三、定时器引发的思考
1)定时器并不能保证真正定时执行,一般会延迟一丁点(可以接受),也有可能延迟很长时间(不能接受)
2)定时器回调函数是在主线程执行的,JS是单线程
3)定时器是通过事件循环模型实现的
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript">
window.onload=function(){
var btn=document.getElementById('btn')
btn.onclick=function(){
var start=Date.now()
console.log('启动定时器前')
setTimeout(function(){
console.log('定时器执行了', Date.now()-start)
},200)
console.log('启动定时器后')
for (var i=0;i<10000000000;i++){
}
}
}
</script>
</head>
<body>
<button id="btn">按钮</button>
</body>
</html>
没加 for 循环时
加了for循环
四、JS是单线程执行
1)JS是单线程运行的,但使用H5中的Web Workers可以多线程运行
浏览器是多线程运行的,有的是单进程:firefox 老版IE 多进程:Chrome 新版IE
2)setTimeout()的回调函数是在主线程执行的,定时器回调函数只有在运行栈中全部执行完后才有可能执行
3)JS的单线程与它的用途有关,作为浏览器脚本语言,JS的主要用途是与用户互动,以及操作DOM,这决定了它只能是单线程,否则会带来复杂的同步问题
4)代码的分类: ①初始化代码:最先执行 ②回调代码:后执行
5)JS引擎执行代码的基本流程:①先执行初始化代码:包含一些特别的代码(设置定时器、绑定监听、发送ajax请求) ②后面在某个时刻才会执行回调函数 回调函数异步执行
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript">
setTimeout(function(){
console.log('timeout 2222')
alert('2222222') // ⑥
},2000)
setTimeout(function(){
console.log('timeout 1111')
alert('1111111') // ⑤
},1000)
setTimeout(function(){
console.log('timeout 0000')
alert('000000') // ④
},0)
function fn(){
console.log('fn()')
}
fn()
console.log('alert()之前') // ①
alert('-----') // ② 暂停当前主线程的执行,同时暂停计时,点击确定后,恢复程序执行和计时
console.log('alert()之后') // ③
</script>
</head>
<body>
</body>
</html>
五、 浏览器的事件循环(轮询)模型
1. 模型的2个重要组成部分
事件管理模块 回调队列
2. 模型的运转流程
① 执行初始化代码,将事件回调函数交给对应模块管理
② 当事件发生时,管理模块会将回调函数及其数据添加到回调队列中
③只有当初始化代码执行完后(可能要一定时间),才会遍历读取回调队列中的回调函数执行
3. 模型原理图
4. 相关重要概念
1)执行栈:execution stack 所有的代码都是在此空间中执行的
2)浏览器内核:browser core 、 js引擎模块(在主线程处理)、其他模块(在主/分线程处理)
3)任务队列(task queue) 4)消息队列(message queue) 5)事件队列(event queue) 3)4)5)属于同一个:callback queue回调队列
6)事件轮询(event loop)从队列中循环取出回调函数放入执行栈中处理(一个接一个)
7)事件驱动模型(event-driven interaction model)
8)请求响应模型(request-response model)
六、Web Workers
1. Web Workers介绍
Web Workers是HTML5提供的一个JavaScript多线程解决方案,我们可以将一些大计算量的代码交由Web Workers运行而不冻结用户界面;但是子线程完全受主线程控制,且不得操作DOM,所以这个新标准并没有改变JavaScript单线程的本质
2. Web Workers的使用
实例:1 1 2 3 5 8 13 21 后一个数等于前两个数之和....
1)直接在主线程操作
上面代码在alert弹出前,计算期间不可以操作界面 因为计算和input获取在一条主线上,下述代码可以让分线程来做长时间的计算而不影响主线程中界面的操作
2)使用Worker在分线程操作
相关API:
Worker:构造函数,加载分线程执行的JS文件Worker.prototype.onmessage:用于接收另一个线程的回调函数
Worker.prototype.postMessage:向另一个线程发送消息
注意:以下代码要在服务器运行,不然报错
另:alert() 是window的方法,在分线程不能调用, 分线程中的全局对象不再是window,所以在分线程不可能更新界面
3. 不足
1)加载慢,相对直接在主线程上来讲
2)不能跨域加载JS
3)worker内代码不能访问DOM(更新UI)
4)不是每个浏览器都支持这个特性