1. 数据类型
1.1 分类
基本(值)类型
- stirng:任意字符串
- number:任意数字(无关整数、小数等)
- boolean:true/false
- undefined:undefined
- null:null
对象(引用)类型
- Object:任意对象都是Object类型
- Function:一种特殊的对象,可以调用执行
- Array:特殊对象,存在数值下标,内部数据是有序的
1.2 判断
typeof
- 返回数据类型的字符串表达
- 可以判断数值,字符串,undefined, boolean, fucntion
- 不可以判断null,object,array,都会返回Object
instanceof
- 返回Boolean值,只能判断对象的具体类型,即使普通对象、函数还是基本类型
- instance 实例
=== (全等,值和类型都相等才返回true)
- 可以判断undefined与null,由于它们的值只有一个;
typeof和instanceof的区别
- 在JavaScript中,判断一个变量的类型常常会用typeof运算符,在使用typeof算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都回’object’ ;
- instanceof运算符用来测试一个对象在其原型链中是否存在一个构造函数prototype属性。语法:object instanceof constructor 参数: object(要检测的对象constructor(某个构造函数)描述:instanceof 运算符用来检测constructor.prototype是否存在于参数object的原型上。
1.2 数据类型2
实例:实例对象 - 根据类型创建的实例对象
(var p = new Person(...))
类型:类型对象 - 构造函数是类型(function Person(...){...})
undefined和null的区别?
- undefined 代表定义未赋值;null 代表定义并赋值了,且值为null;
什么时候给变量赋值为null?
- 起始位置将对象b 赋值为null,表明将要赋值为对象;
- 确定对象以后赋值 (b = [1, 2] / b = {…} 都可以);
- 最后再“b = null”,使b指向的队形成为垃圾对象,被垃圾回收器(浏览器中)回收;
严格区别变量类型与数据类型:
- 数据的类型
- 基本类型
- 对象类型
- 变量的类型(变量内存值得类型)
- 基本类型:保存得就是基本对象的数据
- 引用类型:保存的是地址值
var c = function(){..};
c在栈内存中,保存的是对象在内存空间的地址值;c是引用对象;
typeof c;
是返回c指向的基本对象的类型 “function”,而不是返回"引用对象"
1.3 代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>01_数据类型</title>
</head>
<body>
<script type="text/javascript">
var a;
console.log(a, typeof a);
// undefined 'undefined'
console.log(undefined === 'undefined');
// false
console.log(typeof a ==='undefined', a===undefined);
// true true
var b1 = {
b2:[1,'abc', console.log],
b3:function(){
console.log('b3')
return function(){
return 'kkkk'
}
}
}
console.log(b1 instanceof Object, b1 instanceof Array);
// true false
console.log(b1.b2 instanceof Object, b1.b2 instanceof Array);
// true true
console.log(b1.b3 instanceof Function, b1.b3 instanceof Object);
// true true
console.log(typeof b1.b3 === 'function'); //true
console.log(typeof b1.b2[2]==='function'); //true
b1.b2[2](4); // 4
console.log(b1.b3()());
/*
* b3
* kkk
*/
b1.b3(); // b3
// b1是对象,不能执行,执行的是b3函数
console.log(typeof b1.b2) //object 所以typeof不能用于判断array
</script>
</body>
</html>
2. 数据_变量_内存
2.1 什么是数据?
存储在内存中代表特定信息的东西,本质上是0101;
特点:可传递,可运算;
内存中所有操作的目标:数据, 包括算术运算 、逻辑运算、赋值、运行函数;
2.2 什么是内存?
内存条通电以后可存储数据的空间(临时的);
内存产生和死亡:内存条(电路板)–>通电 -->产生内存空间 -->存储数据 -->处理数据 -->断电 -->内存空间和数据都消失;
一个小内存中有2个数据:内部存储的数据和地址值;
内存的分类:栈(全局变量/局部变量),堆(对象);
过程: 代码加在内存中 -->编译 -->解析执行;
var obj = {name:'Tom'};
// obj在栈空间,将对象的地址值(0x123)保存到obj中(在栈空间),对象内容(name:'Tom')在内存空间中;
var a = obj;
// 是在栈空间中,a = 0x123, 指向同一个内存地址
console.log(0bj.name);
// 找到obj,读值,得0x123,'.'用0x123找到对应内存地址,再根据.后的名称找对应值
2.3 什么是变量?
可变化的量,由变量名和变量值组成;
每个变量都对应一个小内存,变量名用来查找对应的内存,变量值就是内存中保存的数据;
2.4 内存,数据,变量三者之间的关系:
内存是用来存储数据的临时空间, 变量是内存的标识;
2.5 相关问题:var a = xxx, a内存中保存的是什么?
- xxx是基本类型, 保存的是这个基本类型值;
- xxx是引用类型,保存的是这个引用类型的内存地址值;
- xxx是个变量,保存的是这个变量所存储的值(若是基本类型则就是这个值, 若是引用类型则是这个引用类型的内存地址值);
2.6 引用变量赋值问题?
- 多个引用变量指向同一个对象, 通过一个变量修改这个对象的值, 另一个变量只能看到修改后的值;
- 两个引用变量指向同一个对象,将其中一个变量赋值为新对象, 另一个引用变量仍指向原对象;
2.7 在函数调用时是值传递还是引用传递?
理解1: 无论变量类型都是值(基本/地址值)传递;
理解2: 可能是值传递, 也可能是引用传递(传递地址值);
2.8 js如何管理内存?
内存生命周期
- 分配小内存空间, 得到它的使用权
- 存储数据, 可以重复使用
- 释放小内存空间
- 释放内存
释放内存
- 局部变量: 函数执行完自动释放
- 对象: 成为垃圾对象后在某个时间被垃圾处理机制释放
2.9 代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>02_数据_变量_内存</title>
</head>
<body>
<script type="text/javascript">
// 引用变量赋值问题
// 1. 多个引用变量指向同一个对象, 通过一个变量修改这个对象的值, 另一个变量只能看到修改后的值;
var obj1 = {name:'Tom'};
var obj2 = obj1;
obj2.name = 'Amy';
// .xxx = ... 赋值,改变对象的值
console.log(obj1.name) // Amy
function fn1(obj){
obj.name = 'Fun1';
}
fn1(obj1);
console.log(obj1.name); // Fun1
// 2. 两个引用变量指向同一个对象, 将其中一个变量赋值为新对象, 另一个引用变量仍指向原对象;
var obj3 = {age : 15};
var obj4 = obj3;
obj4 = {age: 16, name: 'Jack'}
// ‘ = ’,使得obj4重新指向另一个对象,而不是更改之前对象的内容
console.log(obj4.age, obj4.name, obj3.age); // 16 'Jack' 15
function fn2(obj){
// 进入函数会声明对象
obj = {age: 18};
// 退出后销毁,所以obj3通过形参改变的值被销毁
}
fn2(obj3);
console.log(obj3.age); // 15
// 函数传递
var a = 3;
function fn3(a){
// 对于基本变量,理解成值传递,传递的是基本对象的数据
// 调用函数时,将实参a的值传给实参a,形参a进行加减运行对外部实参a的值没有影响
a=a+1;
}
fn3(a);
console.log(a); // 3
function fn4(obj){
// 对于引用变量,理解成引用传递或者值传递都可以,传递的是指向对象的地址值
// 所以调用函数时可以获得到或者修改地址指向的对象的信息
console.log(obj.age);
obj5.age = 28;
}
var obj5 = {age: 20}
fn4(obj5); // 20
console.log(obj5.age); //28
// 内存释放
function fn5(){
var b = {}
}
fn5()
// 在调用函数开始时,创建声明引用变量b
// 调用函数结束后,自动释放局部变量b
// 而b所指向的对象是在后面某个时刻由垃圾回收器回收的(成为垃圾对象-->由垃圾回收器回收)
</script>
</body>
</html>
3. 对象
3.1 什么是对象?
多个数据的封装体;
用来保存多个数据的容器;
一个对象代表现实中的一个事物, 如Person等;
3.2 为什么要用对象?
统一管理多个数据
3.3 对象的组成
- 属性: 属性名(字符串)与属性值(任意类型)
- 方法: 一种特别的属性(属性值是一个函数)
3.4 如何访问对象内部数据?
- .属性名 编码简单, 有时无法使用
- [‘属性名’] 编码复杂, 但能随意使用
3.5 什么时候必须使用[‘属性名’]?
- 属性名包含特殊字符, 如: - . 空格
- 属性名不确定时, 使用的是变量的值
3.6 代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>03_对象</title>
</head>
<body>
<script type="text/javascript">
// 对象声明
var p={
// 属性: 由属性名(字符串)和属性值(任意类型)组成
name:'tom',
age:12,
// 方法: 由方法名(字符串)和属性值(只能是函数)组成
setName:function(name){
this.name=name;
},
setAge:function(age) {
this.age = age;
}
}
console.log(p.name, p.setAge, p.setAge(18), p.age);
// tom ƒ (age) {...} undefined 18
p["setAge"](23)
console.log(p.age) // 23
// 有特殊字符
var o ={};
// o.content-type = "text-json"; // 语句会报错
o['content-type '] = "text-json";
// 属性名不确定
var propName = 'myAge';
var value = 18;
o.propName = value;
console.log(o.myAge); // undefined
o[propName] = value;
console.log(o.myAge); // 18
</script>
</body>
</html>
4. 函数
4.1 什么是函数?
实现特定功能的n条语句的封装体;
只有函数可以执行, 其他类型的数据不能执行
4.2 为什么要用函数?
- 提高代码复用(调用函数多次实现相同功能)
- 便于阅读交流(对于同一种功能只用读一边函数)
4.3 如何定义函数
- 函数声明 如
function fn(){....}
- 表达式,
var fn = function(){...}
4.4 如何调用(执行)函数?
- 直接调用:
test()
- 通过对象调用:
obj.test()
- new调用:
new test()
test.call/apply(obj)
: 临时让test函数变成obj的方法进行调用
4.5 回调函数
什么函数属于回调函数?
- 自己定义过的
- 自己没有调用的
- 能被执行的(某个时刻或者某种条件下)
常见的回调函数
- dom事件回调函数–>发生事件的dom元素
- 定时器回调函数–>window
- ajax回调函数
- 生命周期回调函数
4.6 IIFE
Immediately-Invoked Function Expression; 匿名函数自调用; 立即调用函数表达式;
格式 :
(function(){
....(函数内容)
})()
作用:
- 隐藏实现
- 不会污染外部作用域
4.7 this
this是什么?
- 任何函数本质都是通过某个对象来调用的, 如果没有指定就是window;
- 所有函数内部都有一个变量this;
- 它的值是调用函数时的当前对象;
- 如何确定this的值?
test()
--> windowp.test()
--> pnew test()
--> 新创建的对象p.call(obj)
--> obj
4.8 代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>04_函数</title>
</head>
<body>
<button>点击此处</button>
<script type="text/javascript">
// 调用函数
var obj = {
name:'Bob'
}
name = 'Win'
function test(){
console.log(this.name);
}
test.call(obj); //Bob 临时让test成为obj的方法调用
test(); // Win
// obj.test(); // Uncaught TypeError: obj.test is not a function
// 回调函数
window.onload = function () {
var btn = document.querySelector('button');
// 这个成为dom事件回调函数
btn.onclick = function () {
alert(this.innerHTML)
}
}
// 定时器回调函数
setTimeout(function () {
alert('到点了')
}, 2000)
// IIFE
// 此部分依次输出: 6 4 1 2
var a = 4;
(function(){
var a = 3
console.log(a+3); // 6
})() // 立即执行
console.log(a); // 4
(function(){
var a = 1;
console.log(a); // 1
function test1(){
console.log(++a);
}
// 像window暴露$
window.$ = function(){
return {
test1:test1
}
};
})()
$().test1(); // 2
// this
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"); // [object Window]***1
var p1 = new Person("yellow"); // [object Object]***1 object是p1
p1.getColor(); // [object Object]***2 object是p1
var obj1={};
//让obj1使用 setColor这个方法
p1.setColor.call(obj1, "black"); // [object Object]***3 object是obj1
var test2 = p1.setColor;
test2(); // [object Window]***3
function fun1(){
function fun2(){
console.log(this +"***4")
}
fun2()
}
fun1(); // [object Window]***4
var a = 1;
a = 2;
window.a = 3;
function Test() {
let a = 4;
// 调用语句为"new Test()", 则这里的this是一个匿名对象
// 给Test的这个匿名对象加了一个属性a
this.a = 5;
console.log(a);
// 4, 先在内部找a(找到a=4),没有的话再去外部
console.log(this.a); //5
setTimeout(function () {
console.log(a);
// 4, 打印的是内部的a
})
setTimeout(function () {
console.log(this.a);
// 3,
// 因为定时器 setInterval setTimeout是window的方法,
// 里面回调函数的this指向window
})
setTimeout(() => {
console.log(a);
// 4,虽然是箭头指向, 但是没有this, 所以是内部的a
}, 30)
setTimeout(() => {
console.log(this.a);
// 5,
// 箭头指向的this:
// 1. 有外层函数, 则是调用外层函数的对象
// 2. 没有外层函数, 则是window
// 这里的this指的是调用Test的匿名对象
}, 40)
}
new Test();
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>04_函数_this面试</title>
</head>
<body>
<script type="text/javascript">
// 练习1
var num = 1;
var myObject = {
num: 2,
add: function() {
this.num = 3;
(function() {
console.log(this.num);
this.num = 4;
})();
console.log(this.num);
},
sub: function() {
console.log(this.num)
}
}
myObject.add();
console.log(myObject.num);
console.log(num);
var sub = myObject.sub;
sub();
/*输出: 1 3 3 4 4
* 解析:
* 1. 执行"myObject.add()"时, add中的this指向myObject,
* 则有myObject.num=3;
* 接着执行自执行函数,
* 没有任何对象调用该函数, 则this指向window, 输出1;
* 输出this.num=4,即window.num=4;
* 自执行函数运行完毕, 跳出;
* 回到add函数中,此时的this是myObject,
* myObject.num=3,输出3;
* 2. 执行"console.log(myObject.num);", 输出3;
* 3. 执行"console.log(num);", 此时没有声明,则this是window,
* 由上可知,window.num=4, 输出4;
* 4. 由"sub = myObject.sub", 意思是给window的sub函数和myObject.sub的sub函数结构相同;
* 5. 执行"sub();", 调用sub的函数为window;
* 则this为window, 输出this.num即输出window.num, 输出4;
*/
// 练习2
var name = 'window'
var person1 = {
name: 'person1',
show1: function () {
console.log(this.name)
},
show2: () => console.log(this.name),
show3: function () {
return function () {
console.log(this.name)
}
},
show4: function () {
return () => console.log(this.name)
}
}
var person2 = { name: 'person2' }
person1.show1() // person1
person1.show1.call(person2) // person2
person1.show2() // window
person1.show2.call(person2) // window **
person1.show3()() // window **
person1.show3().call(person2) // person2
person1.show3.call(person2)() // window **
person1.show4()() // person1 **
person1.show4().call(person2) // person1 **
person1.show4.call(person2)() // person2 **
/* 解析:
* show1略
* show2:
* "person1.show2()" 执行 " () => console.log(this.name)"
* 根据: 箭头函数的this指向外层作用域
* 该箭头外层没有函数了, 所以指向全局作用域,this为window;
*
* "person1.show1.call(person2)" 执行 " () => console.log(this.name)"
* 根据: 箭头函数直接应用bind/call/apply 不起作用
* this依旧指向外层作用域, 即指向全局, 输出window
*
* show3:
* "person1.show3()"return 函数内容,
* 随后接一个(),相当于在全局调用函数, 则this为window;
* "person1.show3()"return 函数内容,
* 随后接".call(person2)", 让person2调用return的函数,this为person2;
* "person1.show3.call(person2)",相当于让person2调用show3函数, return函数内容
* 然后再接一个()调用return的函数, this为window;
*
* show4:
* 同上, "person1.show4()" return函数内容, return "() => console.log(this.name)",
* 1. 后面接一个(), 为寻找外层作用域,找到外层函数"person1.show4()",则this为person1;
* 2. 接".call(person2)", 意思是让person2调用return的函数,但是函数内容箭头找外层函数,
* 找到"person1.show4()", ,则this为person1;
* 3. 执行"person1.show4.call(person2)()",
* "person1.show4.call(person2)"指定让person2执行show4,
* 然后接着一个(),运行return的函数, 箭头找外层作用域,
* 找到调用show4的person2, 输出person2;
*
* 总结: 箭头函数没有自己的this,它的this是:
* 1. 谁调用箭头函数的外层function,箭头函数的this就是指向谁,
* 2. 如果箭头函数没有外层函数,则指向window。
*/
// 练习3
var name = 'window'
function Person(name) {
this.name = name;
this.show1 = function () {
console.log(this.name)
}
this.show2 = () => console.log(this.name)
this.show3 = function () {
return function () {
console.log(this.name)
}
}
this.show4 = function () {
return () => console.log(this.name)
}
}
var personA = new Person('personA')
var personB = new Person('personB')
personA.show1() // personA
personA.show1.call(personB) // personB
personA.show2() // personA**
personA.show2.call(personB) // personA**
personA.show3()() // window
personA.show3().call(personB) // pwrsonB
personA.show3.call(personB)() // window
personA.show4()() // personA**
personA.show4().call(personB) // personA**
personA.show4.call(personB)() // personB**
/*解析:
* 构造函数创建对象后其实多了一层构造函数的作用域;
*/
</script>
</body>
</html>
5. 原型与原型链
5.1 原型
原型属性: 函数的prototype属性
- 每个函数都有一个prototype属性,它默认指向一个Object空对象(原型对象)
console.log(typeof Date.prototype) // object
空对象:obj = {}
- 每个原型对象都会有一个constructor, 指向函数对象;
也就是func.prototype.constructor === func; //true
5.2 显式原型和隐式原型
- 显式原型 : 每个函数function都有一个prototype, 即显式原型;
函数的prototype属性:在定义函数时自动添加的,默认值是一个空Object对象;- 隐式原型 : 每个实例对象都有一个__proto__, 即隐式原型;
对象的__proto__属性:创建对象时自动添加的,默认值为构造函数的prototype属性值;- 对象的隐式原型的值为其构造函数的显示原型的值;
func.__proto__ === Func.prototype //true
- 程序员能直接操作显式原型,但不能直接操作隐式原型(ES6之前);
5.3 原型链
别名:隐式原型链
作用:查找对象属性(方法);
- 先查找函数自身内部的方法,找到返回;
- 如果函数自身内部没有这个方法, 就去找这个函数__proto__内的方法;
- 仍然没有找到,继续沿着__proto__向上查找;
- 找到返回, 如果最终没有依旧没有找到,返回undefined;
Object的显式原型的隐式原型为null, 即原型链的尽头;
补充
- 函数的显式原型指向的对象:默认是空Object的实例对象(但Object自身不满足);
- Function是它自身的实例,所有函数都是Function的实例(包括Function本身);
- Object的显式原型的隐式原型是原型链的尽头 ;
5.4 原型链属性问题
读取 对象的属性值时:会自动到原型链中查找;
设置 对象的属性值时:不会查找原型链,如果当前对象中没有此属性,直接添加此属性并设置其值;
方法一般定义在原型中,属性一般通过构造函数定义在对象本身上;
5.5 探索instanceof
A instanceof B: 探索对象A是否是函数B的实例
- 如果B函数的显式原型对象在A对象的原型链上,返回true;
- 否则返回false;
5.6 代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>05_原型对象</title>
</head>
<body>
<script type="text/javascript">
// 原型
function Fn(){
console.log("函数Fn()")
}
console.log(Fn.prototype); // Object test: ƒ () constructor: ƒ Fn().....
console.log(typeof Fn.prototype); // Object
Fn.prototype.test = function(){}
console.log(Fn.prototype.constructor === Fn); // true
var obj1 = new Fn();
obj1.test(); // 函数Fn()
// 显示原型和隐式原型
function Fn2(){} // 内部语句:this.prototype={}
console.log(Fn2.prototype) // Object{constructor:Function}
var obj2 = new Fn2() // 内部语句:this.__proto__=fn.prototype
console.log(obj2.__proto__) // Object{constructor:Function}
console.log(Fn2.prototype===obj2.__proto__) // true
Fn2.prototype.test2 = function(){
console.log("新方法Test2()")
}
obj2.test2(); // 新方法Test2()
// 原型链
function Fn3(){
this.test3 = function(){
console.log("test3()")
}
}
Fn3.prototype.test4 = function(){
console.log("test4()")
}
console.log(Fn3.prototype)
/*
* {test4: ƒ, constructor: ƒ}
* test4: ƒ ()
* ...
* [[Prototype]]: Object
* ...
* __proto__: (...)
* ....
*
* 意思是说, 函数的prototype属性,默认值是一个空的Object对象;
* 而这个Object对象, 因为拥有__proto__属性, 是一个(Object的)实例对象;
* __proto__属性值也是地址值, 默认值为其构造函数的prototype属性值;
* 注: 每个实例对象都有一个__proto__, 即隐式原型;
* 实例对象.__proto__ === 构造函数.prototype
*/
console.log(Fn3.prototype.__proto__)
/* 以下为Object的内容, 内含一些方法
* 这些方法可以被其的实例对象继承
* {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ
* ...
* hasOwnProperty:...
* toString:...
* valueOf:...
* __proto__: (...)
* ...
*/
console.log(Fn3.prototype.__proto__.__proto__) // null
var obj3 = new Fn3()
obj3.test3() // test3()
obj3.test4() // test4()
console.log(obj3.toString()) // [object Object]
// obj3.test5() // Uncaught TypeError: obj3.test5 is not a function
console.log(obj3.test5) // undefined
/*
* obj3.test3() :
* 获得栈空间中obj3的地址值, 去找Fn3的实例对象obj3
* 在自身找到test3, 返回;
* obj3.test4():
* 获得栈空间中obj3的地址值, 去找Fn3的实例对象obj3
* 在自身找没有test4, 去实例对象的__proto__指向的地址值找
* 有实例对象.__proto__ = 其构造函数.prototype
* 找到test4, 返回;
*
* obj3.toString():
* 获得栈空间中obj3的地址值, 去找Fn3的实例对象obj3
* 在自身找没有test4, 去实例对象的__proto__指向的地址值找
* 有实例对象.__proto__ = 其构造函数.prototype
* 其实际上是定义函数时创建的Object对象(其构造函数.prototype),有__proto__值
* 继续寻找, 找到Object的原型对象, 拥有toString, hasOwnProperty等多个方法, 返回;
*
* obj3.test5():
* 层层查找, 直到找到Object的原型对象, 他找到__proto__
* 但是__proto__值是null
* 返回
* --> 原型链的尽头是null, 且实际上原型链是沿着__proto__向上查找
*/
// 原型链补充
// 函数的显式原型指向的对象:默认是空Object的实例对象(但Object自身不满足);
console.log(Fn3.prototype instanceof Object); // true
console.log(Object.prototype instanceof Object); // false
console.log(Function.prototype instanceof Object); // true
// Function是它自身的实例,所有函数都是Function的实例(包括Function本身);
console.log(Function.prototype === Function.__proto__) // true
// Object的原型对象是原型链的尽头
console.log(Object.prototype.__proto__) // null
// 原型链属性问题
function Fn4(){
}
Fn4.prototype.a = 'xxx';
var obj4 = new Fn4();
console.log(obj4.a); // xxx
var obj5 = new Fn4();
obj5.a = 'yyy';
console.log(obj4.a, obj5.a); // xxx yyy
console.log(obj4);
/*
Fn4 {}
[[Prototype]]: Object
a: "xxx"
...
*/
console.log(obj5);
/*
Fn4 {a: 'yyy'}
a: "yyy"
[[Prototype]]: Object
a:"xxx"
...
*/
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("Amy");
console.log(p1);
/*
Person {name: 'Amy', age: 12}
age: 12
name: "Amy"
[[Prototype]]: Object
setName: ƒ (name)
...
*/
var p2 = new Person("Jack", 15);
console.log(p2);
/*
Person {name: 'Jack', age: 15}
age: 15
name: "Jack"
[[Prototype]]: Object
setName: ƒ (name)
...
*/
console.log(p1.__proto__ === p2.__proto__); // true
// 探索instanceof
function Foo(){
}
var obj6 = new Foo();
console.log(obj6 instanceof Foo); // true
console.log(obj6 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
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>05_原型对象_面试</title>
</head>
<body>
<script type="text/javascript">
// 练习1
var A = function(){};
A.prototype.n = 1;
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
console.log(b);
/*
[[Prototype]]: Object
n: 1
....
*/
console.log(c);
/*
[[Prototype]]: Object
m: 3
n: 2
....
*/
/* 原理解析: b.n = 1, b.3 = undefined:
* 定义A,var A = function(){}, 相当于function A(){}
* A是一个函数, 函数的原型对象默认是一个空的Object对象
* 此时, A.prototype = {};
* 运行"A.prototype.n = 1;" , 有 A.prototype = {n:1}
* 即A.prototype地址值为0x123, 指向内容为{n:1}的对象
* 此时 "var b = new A();" b是A的实例
* 则 b.__proto = A.prototype = 0x123
*
* 由"A.prototype = {n:2, m:3}"赋值更新,
* A.prototype地址值更新,为0x456, 指向另一个对象{n:2, m:3}
* 此时"var c = new A();" c是A的实例
* 则 c.__proto = A.prototype = 0x456
*
*/
// 练习2
function F(){}
Object.prototype.a = function(){
console.log("a");
}
Function.prototype.b = function(){
console.log("b");
}
var f = new F();
F.a(); // a
F.b(); // b
f.a(); // a
f.b(); // f.b() is not a function ***
/* 解析:
* f.b() : f.b() is not a function
* b()在Function.prototype里
* 而f的原型链:
* 因为f = new F(), 则 f.__proto__ = F.prototype
* F.prototype, 默认是一个空的Object对象
* 则 F.prototype.__proto__ = Object.prototype
* 存在Object.prototype.a, 所以f.a()可以运行;
* 再顺着__proto__往下找
* 但是Object.prototype.__proto__ = null
* Object的显式原型的隐式原型是原型链的终点
* 未找到b(), 所以f.b() is not a function ***
*
* F的原型链:
* F由"function F(){}"声明, 相当于 var F = function(){}
* 则F是function Function的实例对象
* 则F.__proto__ = Function.prototype
* 存在Function.prototype.b, 存在F.b();
* 再往下找函数的原型(Function.prototype)默认是一个空的Object对象
* 则 F.prototype.__proto__ = Object.prototype
* 存在Object.prototype.a, 存在F.a();
*
*
*/
</script>
</body>
</html>
6. 执行上下文和指向上下文栈
6.1 变量提升和函数提升
变量声明提升
- 通过var定义的变量在这行定义语句前就能访问到;
- 值: undefined;
函数声明提升
- 通过function声明的函数在声明之前就能访问到;
- 值: 通过function定义的函数本身;
先变量提升再函数提升;
在函数中使用未声明的变量会自动声明成全局变量;
6.2 执行上下文
代码分类
- 全局代码
- 函数代码(局部)
全局执行上下文
- 在执行全局代码前将window确定为全局执行上下文;
- 对全局数据进行预处理
- var定义的全局变量–>undefined, 添加为window的属性
- function声明的全局函数–>赋值为这个函数, 将全局函数添加为window的方法
- this–>赋值为window
函数执行上下文
- 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象
- 对局部数据进行预处理
- 声明形参变量–>赋值为实参的值–>添加为执行上下文的属性
- arguments–>赋值为实参列表, 添加为执行上下文属性
- var定义的局部变量–>赋值为undefined, 添加为执行上下文属性
- function声明的函数–>赋值为这个函数本身, 添加为执行上下文方法
- this–>赋值为调用这个函数的对象
- 开始执行函数上下文
6.3 执行上下文栈
全局代码执行前, JS引擎会创建一个上下文栈来存储管理执行上下文
在全局执行上下文(window)确定后, 将其添加到栈中
在函数执行上下文创建后, 将其添加到栈中
在当前函数执行完后, 将栈顶对象移除
当所有代码执行完毕后栈中只剩下window
6.4 代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>06_执行上下文</title>
</head>
<body>
<script type="text/javascript">
// 变量提升
var a = 3;
function Fn1(){
console.log(a);
var a = 4;
}
Fn1(); // undefined
// 函数提升
console.log(b); // undefined 变量提升
Fn2(); // Fn2() 可调用 函数提升
// Fn3(); // Uncaught TypeError: Fn3 is not a function 不可调用, 变量提升
var b = 3;
function Fn2(){
console.log("Fn2()");
}
var Fn3 = function(){
console.log("Fn3()");
}
// 全局执行上下文
console.log(a1, window.a1); // undefined undefined
a2(); // a2()
console.log(this); // Window {window: Window, self: Window....
var a1=4;
function a2(){
console.log('a2()');
}
console.log(a1); // 4
// 函数执行上下文
function Fn4(a1){
console.log(a1); // 2
console.log(a2); // undefined
a3(); // a3()
console.log(this); //Window {window: Window, self: Window....
console.log(arguments);
/*
* Arguments(2) [2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
* 0: 2
* 1: 3
* callee: ƒ Fn4(a1)
* ...
*/
var a2=3;
function a3(){
console.log('a3()');
}
}
Fn4(2, 3)
// 执行上下文栈
// 1. 进入全局执行上下文
console.log("***** 以下是执行上下文栈 *****")
var c = 10 ;
var bar = function(x){
var b = 5;
foo(x+b); // 3. 进入foo执行上下文
}
var foo =function(y){
var c = 5;
console.log(a+c+y);
}
bar(10); // 2. 进入bar函数执行上下文
// 输出: 23 <-- 不会报错,因为在产生
// 产生了3个执行上下文栈, 1. window, 2. bar(调用时产生), 3 foo
// 栈的意思是:后进先出
// 全局代码执行前, JS引擎会创建一个上下文栈来存储管理执行上下文
// 在全局执行上下文(window)确定后, 将其添加到栈中
// 函数执行上下文创建后, 将其添加到栈中
// 在当前函数执行完后, 将栈顶对象移除
// 当所有代码执行完毕后栈中只剩下window
console.log('global begin: ' + i);
var i = 1;
foo2(1);
function foo2(i){
if(i==4){
return;
}
console.log('foo2() begin: ' + i);
foo2(i+1)
console.log('foo2() end: ' + i);
}
console.log('global end: ' + i);
/* 依次输出:
* global begin: undefined
* foo2() begin: 1
* foo2() begin: 2
* foo2() begin: 3
* foo2() end: 3 (递归,函数堆栈,执行完栈顶函数后弹出,依次执行完直到window)
* foo2() end: 2
* foo2() end: 1
* global end: 1 (形参在函数执行结束后被释放, 此时的i是window.i = 1)
*
* 调用了4次foo
* 产生了5个执行上下文
*/
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>06_执行上下文_面试</title>
</head>
<body>
<script type="text/javascript">
// 练习1
function a(){}
var a;
console.log(typeof a) // function
/* 解析:
* 存在函数提升和变量提升,
* 先执行变量提升, 再执行函数提升,
* 后面的会覆盖前面的;
*/
// 练习2
if(! (b in window)){ // window中是否有b属性
var b = 1;
}
console.log(b); // undefined
/* 解析:
* b in window: window中有没有b属性
* 1. 如果window中存在b, 取反为假, b输出为undefined
* 2. 如果window中不存在b, 取反为真, 执行b=1, 输出为1
* 结果可知, window中会存在b;
*/
// 练习3
var c = 1;
function c(c){
console.log(c);
}
c(2); // Uncaught TypeError: c is not a function
/* 解析:
* 执行顺序:
* var c --> function c --> c=1 -->c(2), 报错, c不是函数
*
*/
</script>
</body>
</html>
7. 作用域与作用域链
7.1 作用域
理解:
- 相当于一块地盘, 一个代码段所在的区域, 相当于内部封闭;
- 静态的(相对于上下文对象), 在编写代码时就已经确定
分类:
- 全局作用域;
- 函数作用域
- 没有块作用域 ('{}'内的内容)
作用:
- 隔离变量, 不同作用域中同名变量不会有冲突
7.2 作用域和执行上下文
区别1
- 函数作用域在函数定义时创建而非函数调用时
- 全局执行上下文在全局作用域创建之后, JS代码执行之前创建
- 函数执行上下文在调用函数时, 函数体代码未执行前创建
区别2
- 作用域是 静态 的, 只要函数定义好后就一直存在且不会变化
- 执行上下文是 动态 的, 调用函数时创建, 函数执行完成后自动销毁
联系
- 作用域从属于所在的执行上下文
- 全局作用域–>全局执行上下文
- 函数作用域–>函数执行上下文
7.3 作用域链
理解
- 多个上下级关系的作用域形成的链的方向是 从内向外 的
- 查找变量时是 沿着作用域链 来查找的
查找上一个变量的查找规则
- 在当前作用域下的上下文中查找对应属性, 有则返回没有进入2
- 在上一级作用下的上下文中查找对应属性, 有则返回没有进入3
- 依次执行2直到处在全局作用域中, 在全局上下文中查找对应属性, 有则返回没有报错
7.4 代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>07_作用域</title>
</head>
<body>
<script type="text/javascript">
// 作用域
// 没有块作用域
if(true){
var z=10;
}
console.log(z); // 可以访问x 10
// 全局作用域
var a = 10;
var b = 20;
function Fn1(x){ // Fn1 作用域
var a = 100;
var c = 200;
console.log('fn()', a, b, c, x); // fn(), 100, 20, 200, 10
function bar(x){ // bar作用域
var a = 1000,
d=400;
console.log('bar()', a, b, c, d, x);
}
bar(100); // bar(), 1000, 20, 200, 400, 100
bar(200); // bar(), 1000, 20, 200, 400, 200
}
Fn1(10);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>07_作用域_面试</title>
</head>
<body>
<script type="text/javascript">
// 练习1
var x = 10;
function Fn1(){
console.log(x);
}
function Show(f){
var x = 20;
f();
}
Show(Fn1); // 10**
/*解析:
* 因为函数作用域在一开始(调用前)就创建好了的, 是静态的
* 所以不会在后面调用的时候动态去寻找
* 所以作用域的关系是:
* 全局(Fn1, Show);
* 所以输出x时, 先在Fn1查找,未找到,去到上一级全局作用域中查找
* 输出window.x = 10
*/
// 练习2
var Fn2 = function(){
console.log(Fn2);
}
Fn2(); // ƒ (){ console.log(Fn2); }
var obj = {
yy: 10,
Fn3 : function (){
console.log(Fn3);
},
Fn4 : function Fn4(){
console.log(Fn4);
},
Fn5 : function (){
console.log(this.Fn5);
},
Fn6 : function (){
console.log(obj.Fn6);
},
Fn7 : function (){
console.log(yy);
},
Fn8 : function (){
console.log(this.yy);
},
}
obj.Fn7(); // Uncaught ReferenceError: yy is not defined
obj.Fn8(); // 10
obj.Fn6(); // ƒ (){ console.log(obj.Fn5); }
obj.Fn5(); // ƒ (){ console.log(this.Fn5); }
obj.Fn4(); // ƒ Fn4(){ console.log(Fn4); }
obj.Fn3(); // Uncaught ReferenceError: Fn3 is not defined **
/*解析:
* 调用Fn3
* 先在函数作用域找, 未找到
* 然后跳出, 到上级作用域找,仍然没有找到
*
* 如果想要输出函数内容, 代码应该是:
* console.log(this.Fn3)
*/
</script>
</body>
</html>
8. 闭包
8.1 闭包的基本概念
如何产生闭包
- 产生闭包:当一个嵌套在外部函数中的内部函数,引入并使用了外部函数的变量就产生了闭包;
与外部函数的变量的值无关;
闭包是什么
- 使用Chrome调试时,可以看到的包含了被引用的(外部函数的)变量的Closure对象;
产生闭包的条件
- 存在函数嵌套(函数(外部函数)内存在函数(内部函数));
- 内部函数引入了外部函数的数据,且内部函数已经别定义;
根据内部函数定义的方式不同,产生闭包的位置可能不同,
闭包在嵌套的内部函数 完成定义 时产生:
- 使用 函数表达式 定义函数:
var Fn = function() ...
, 要执行到这一段代码才产生闭包;- 使用 函数声明 定义函数:
function Fn()...
, 在进入内部函数时就完成了产生闭包;- 外部函数被调用执行
常见的两种闭包
- 将内部函数作为外部函数的返回值进行返回;
- 将函数作为一个实参传给另一个函数;
闭包的作用
- 在执行完外部函数后,它的内部变量(存在在闭包中的)依然存留在内存中,即延长了变量的生命周期,(一般的外部函数调用完成后,内部变量会被释放);
- 一般的(未在闭包中的)外部函数的变量,依旧会在外部函数调用完成后被释放;
- 在函数的外部可以操作(读写)到函数内部的数据,但不是将变量直接暴露给外部;
闭包的生命周期
- 在嵌套的内部函数完成定义时产生;
- 在嵌套的内部函数成为垃圾对象时死亡(例如:
f = null
);
8.2 闭包的应用
自定义js模块(流程)
- 创建具有特点功能的JS文件
- 将所有的数据和功能都封装在一个函数内部(私有的)
- 只向外部暴露一个有n个方法的函数或者对象
- 模块的使用者,只需要通过模块暴露的函数或者对象来使用该模块的功能
8.3 闭包的缺点及解决方法
缺点
- 外部函数执行完以后,函数内部的局部变量没有被释放,占用内存的时间会变长
- 容易造成内存泄漏(内存被占用,但是没有使用)
解决办法
- 尽量不使用闭包
- 记得尽早释放
8.4 内存溢出和内存泄漏
内存溢出
- 程序运行时出现的错误(死循环, 堆栈)
- 当程序运行需要的内存超过了所有的内存,就抛出内存溢出的错误
内存泄漏
- 被占用的内存没有被及时释放
- 内存泄漏过多容易造成内存溢出
- 常见的内存泄漏
- 意外的全局变量(意外的意思是,原本是只想要它是局部变量,但是定义成了全局)
- 没有及时处理的计时器或者回调函数
- 闭包
8.5 代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>01_引入</title>
</head>
<body>
<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<script type="text/javascript">
var btns = document.getElementsByTagName('button');
for(var i=0;i<btns.length;i++){
// 任意点击, 都是第4个
// 当函数执行时, 循环已经结束
// var btn = btns[i];
// btn.onclick = function(){
// alert('第' + (i+1) + '个');
//
// }
// 方法1 : 以下代码功能正常
// btns[i].index=i
// btns[i].οnclick=function(){
// alert("第"+(this.index+1)+"个button")
// }
// 方法2 使用闭包
(function(i){
var btn = btns[i];
btn.onclick = function(){
alert('第' + (i+1) + '个');
}
})(i)
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>02_常见的闭包</title>
</head>
<body>
<script type="text/javascript">
// 1. 将内部函数作为外部函数的返回值进行返回;
function fn1(){
var a = 2;
function fn2(){
// 此处为内部函数,使用了外部函数中的a变量
a++;
console.log(a);
}
return fn2;
}
var f = fn1();
f(); // 3
f(); // 4
var f2 = fn1();
f2(); // 3
f2(); // 4
// 2. 将函数作为一个实参传给另一个函数;
function showDelay(msg, time){
// 外部函数
setTimeout(
// setTimeout() 是属于 window 的方法
// 该方法用于在指定的毫秒数后调用函数或计算表达式。
function(){
// 回调函数
// 此处为内部函数,使用了外部函数中的msg变量
alert(msg);
}, time)
}
showDelay('tt', 2000);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>03_闭包的生命周期</title>
</head>
<body>
<script type="text/javascript">
function fn1(){
// 此时闭包fn2已产生(函数提升,内部函数对象已经创建)
var a = 2;
function fn2(){
a++;
console.log(a);
}
var fn3 = function(){ //闭包fn3在本行才产生
a--;
console.log(a);
}
return fn2;
}
var f = fn1();
f(); // 3
f(); // 4
f = null;
// 闭包fn2死亡
// 原理:是包含闭包的函数对象(object)成为垃圾对象,即没有被引用
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>04_闭包的应用</title>
</head>
<body>
<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript" src="module2.js"></script>
<script type="text/javascript">
// 方法1
// js文件中是函数return的情况
// 这种情况必须先执行函数
// var fn = MyModule();
// fn.doSomeThing(); // doSomeThing() MODULE1
// fn.doOtherThing(); // doOtherThing() module1
// 方法2
// js文件中是匿名函数自调用
// 这种情况只要引入js文件就可以得到想要的对象
console.log(window.myModule2); //{doSomeThing: ƒ, doOtherThing: ƒ}....
myModule2.doSomeThing() // doSomeThing() MODULE2
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>05_闭包_面试</title>
</head>
<body>
<script type="text/javascript">
// 练习1
var name='The Window'
var object={
name:"My Object",
getNameFunc:function(){
return function(){
// 内部函数
return this.name
}
}
}
console.log(object.getNameFunc()()) //The Window
/*解析:
* object.getNameFunc() 返回内部函数
* 接下来执行的是:内部函数()
* this为window
*
* 存在嵌套函数,但是没有构成闭包
* 因为内部函数没有引入外部函数的变量
*/
// 练习2
var name2="The Window2"
var object2={
name2:"My Object2",
getNameFunc:function(){
var that=this
return function (){
return that.name2
}
}
}
console.log(object2.getNameFunc()()) //My Object2
/*解析:
* 执行object2.getNameFunc()
* 执行'that = this'语句,此时的that = this = object2
* 返回 function 内部函数,执行内部函数()
* 输出that.name2, that = object2
* 所以输出 My Object2
*
* 存在嵌套函数,且内部函数使用了外部函数的变量that
* 构成闭包
*/
// 练习3 *******
function fun(n, o){
console.log("fun: fun(n, o)....", n)
console.log(o);
return {
fun: function(m){
console.log("fun: function(m)", m, n)
return fun(m, n);
}
}
}
var a = fun(0);
a.fun(1);
a.fun(2);
a.fun(3);
// undefined, 0, 0, 0
var b = fun(0).fun(1).fun(2).fun(3);
// undefined, 0, 1, 2
var c = fun(0).fun(1);
c.fun(2);
c.fun(3);
// undefined, 0, 1, 1
</script>
</body>
</html>
9. 对象创建模式
9.1 对象创建模式
方法1:Object构造函数模式
- 套路: 先new来创建空对象, 再动态添加新的属性/方法
- 使用场景: 起始时不知道对象内部的数据
- 问题: 语句太多
方法2: 对象字面量模式
- 套路: 使用{}创建空对象, 再动态添加新的属性/方法
- 使用场景: 起始时已知对象内部的数据
- 问题: 如果创建多个对象, 代码会重复
方法3: 工厂模式
- 套路: 使用工厂函数动态创建对象并返回
- 使用场景: 需要创建多个对象
- 问题: 对象没有具体类型, 都是Object
方法4: 自定义构造函数
- 套路: 自定义构造函数, 使用new创建
- 使用场景: 需要创建多个类型确定的对象
- 问题: 如果实例化多次会使同样的方法重复占用内存空间
方法5: 自定义构造函数+原型对象组合使用
- 套路: 自定义构造函数, 使用new创建, 添加方法时使用原型对象来添加
- 使用场景: 需要创建多个类型确定的对象
9.2 继承模式
原型继承
- 缺点是如果父函数有一个变量为引用类型, 任意一个实例修改这个变量会导致所有实例的相关属性被修改
- 套路:
- 构造父函数
- 给父函数的原型添加新方法
- 构造子函数
- 使子函数的原型对象成为父函数的实例(关键一步, 此处使原型链能够继承)
- 给子函数的原型对象添加新方法
借用构造函数继承(假继承, 实际上并没有继承父类型方法)
- 缺点是父类有方法时会被创建多次
- 套路:
- 创建父类型构造函数
- 创建子类型构造函数
- 在子类型构造函数中使用call/apply调用父类型构造函数
寄生式继承
- 缺点是方法没有放到原型中无法复用
- 套路:
- 创建父类型构造函数
- 创建子类型构造函数
- 在子类型构造函数中使用call/apply调用父类型构造函数
组合继承(借用构造函数继承+原型继承)
- 缺点是构造函数执行了两次
- 套路:
- 创建父类型构造函数
- 创建子类型构造函数
- 在子类型构造函数中使用call/apply调用父类型构造函数
9.3 代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>09_创建对象</title>
</head>
<body>
<script type="text/javascript">
// 1.Object构造函数模式
var p1 = new Object()
p1.name = 'TT'
p1.age = 12
p1.setName = function(){
this.name = name;
}
p1.setName('Jack')
// 2.对象字面量模式
var p2 = {
name:'Tom',
age:12,
setName : function(name){
this.name = name;
}
}
// 3.工厂模式(*)
function createPerson(name, age){
var obj = {
name:name,
age:age,
setName : function(name){
this.name = name;
}
}
}
var p3 = createPerson('Amy', 15)
// 4.自定义构造函数模式
function Person(name, age){
this.name = name,
this.age = age,
this.setName = function (name){
this.name = name
}
}
var p4 = new Person('Bob', 15)
// 5.构造函数+原型模式
function Person2(name, age){
this.name = name,
this.age = age
}
Person2.prototype.setName = function (name){
this.name = name
}
var p4 = new Person2('cat', 15)
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>09_创建对象_继承</title>
</head>
<body>
<script type="text/javascript">
// 父类型
function Supper(){
this.supProp = 'supper';
}
Supper.prototype.showSupper = function(){
console.log(this.supProp);
}
// 子类型
function Sub(){
this.subProp = 'sub';
}
// 1. 原型链继承
Sub.prototype = new Supper() // 完成继承
Sub.prototype.showSub = function(){
console.log(this.subProp);
}
var sub1 = new Sub();
sub1.showSub(); // sub
sub1.showSupper(); // supper
console.log(sub1.constructor)
console.log(Sub.prototype.constructor)
// ƒ Supper(){this.supProp = 'supper';}
Sub.prototype.constructor = Sub
console.log(Sub.prototype.constructor)
console.log(sub1.constructor)
// ƒ Sub(){this.supProp = 'supper';}
// 2. 借用构造函数继承(假的)
function Person(name, age){
this.name = name
this.age = age
}
function Student(name, age, price){
Person.call(this, name, age)
// 只是调用方法,没有真的继承
this.price = price
}
var p1 = new Student('XXX', 12, 1200)
console.log(p1.name, p1.age, p1.price)
// XXX 12 1200
// 3. 组合继承
Person.prototype.setName = function(name){
this.name = name;
}
function Teacher(name, age, price){
Person.call(this, name, age)
this.price = price
}
Teacher.prototype = new Person();
Teacher.prototype.constructor = Teacher;
Teacher.prototype.setPrice = function(price){
this.price = price;
}
var t1 = new Teacher('Kim', 25, 15000);
t1.setName('Kim1')
t1.setPrice(16000)
console.log(t1.name, t1.age, t1.price)
// Kim1 25 16000
</script>
</body>
</html>
10. 进程与线程
10.1 基本概念
进程:程序的一次执行,在内存中占用一片独立的内存空间;
线程:是进程里的一个独立执行单元,是程序执行的一个完整的流程,是CPU的最小调度
应用程序必须运行在某个进程的某个线程上;
一个进程中至少有一个运行的线程: 主线程, 进程启动后自动创建;
一个进程中也可以同时运行多个线程;
一个进程内的数据可以让其中多个线程共享;
多个进程之间的数据是相互独立的;
线程池: 保存多个线程对象的容器, 实现线程对象重复利用;
比较单线程与多线程
- 多线程优点
- CPU利用效率高
- 多线程缺点
- 创建多线程开销
- 线程间切换开销
- 死锁与状态同步问题
- 单线程优点
- 顺序编程简单易懂
- 单线程缺点
- 效率低
JS的单线程与多线程
- js单线程运行
- 使用H5的Web Workers可以多线程运行
浏览器运行是单线程还是多线程?
- 多线程
浏览器运行是单进程还是多进程?
- 有单进程如火狐与老版IE 也有多进程如chrome与新版IE
10.2 浏览器内核
支撑浏览器运行的最核心的程序
不同浏览器可能不一样
- chrome, safari: webket
- firefox: Gecko
- IE: Trident
内核由多个模块组成
- 主线程模块
- js引擎模块, 负责js的编译与运行
- html, css文档解析模块, 负责页面文本的解析
- DOM/CSS模块, 负责DOM/CSS在内存中的相关处理
- 布局和渲染模块, 负责页面的布局与效果的绘制
- …
- 分线程模块
- 定时器模块, 负责定时器管理
- 事件响应模块, 负责事件的管理
- 网络请求模块, 负责ajax请求
10.3 定时器引起的思考
定时器真的是定时执行吗
- 无法保证真正定时执行
- 一般会延迟一点, 也可能延迟很长时间
定时器的回调函数是在分线程执行的吗
- 不是, js是单线程的
定时器如何实现的
- 事件循环模型
10.4 JS是单线程执行的
如何证明js执行是单线程的
- setTimeout()函数是在主线程执行的
- 定时器回调代码只有在运行栈中的代码全部执行完后才执行
为什么js要用单线程模式而不是多线程模式
- 作为浏览器脚本语言主要用途在于与用户互动及操作DOM, 这决定必须为单线程执行, 否则有严重的同步问题
代码分类
- 初始化代码
- 回调代码
js引擎执行代码的基本流程
- 先执行初始化代码, 包含一些特殊代码
- 设置定时器
- 绑定监听
- 发送ajax请求
- 某个时刻后再执行回调代码 使用alert()能暂停主线程执行与定时器计时
10.5 事件循环模型
- 执行栈(execution stack)
- 浏览器内核(browser core)
- 回调队列(callback queue)
- 消息队列(task queue)
- 任务队列( event queue)
- 事件队列( event queue)
- 事件轮询(主线程队列与回调队列的事件执行顺序)( event loop)
- 事件驱动模型(同步与异步的执行)(event-driven interaction model)
- 请求响应模型(request-response model)
模型的运转流程(面试时问道可以画图说明)
- 执行初始化代码,将事件回调函数交给对应模块管理
- 当事件发生时,管理模块会将回调函数及其数据添加到回调列队中
- 只有当初始化代码执行完后(可能要一定时间), 才会一个一个遍历读取回调队列中的回调函数执行。
10.6 H5 Web Workers多线程
介绍
- Web Workers是HTML5的多线程解决方案
- 可以将一些大计算量的代码交由Web Workers运行而不冻结用户界面
- 但是子线程完全受主线程控制且不能操作DOM, 因此没有改变JS是单线程的属性
使用
- 创建在分线程执行的js函数
- 向主线程的js中发消息并执行回调
不足
- 慢
- 不能跨域加载js
- worker内代码不能操作dom
- 不是每个浏览器都支持