基础总结深入
数据类型
1.分类
基本(值)类型
String:任意字符
Number:任意的数字
Boolean:true、false
undefind:undefind
null:null
对象(引用)类型
Object:任意对象
Function:一种特别的对象(可以执行)
Array:一种特别的对象(数值下标属性,内部数据是有序的)
2.判断
typeof:
可以判断:undefined / 数值 / 字符串 / 布尔值 / function
不能判断:null 与 object / object和array
**instanceof:**判断对象的具体类型
== / === :
一个会做数据转换,一个不会
可以判断undefined / null
<!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>Document</title>
</head>
<body>
</body>
<script>
//分类
var obj = {
name: 'Top',
age: 12
}; //对象用来存数据,一般对象内部数据都是无序的
function fun(){ //函数用来存代码
var a = 3;
}
var arr = [3,'abc'];
console.log(arr[1]);
//判断
//1.基本
//typeof返回数据类型的字符串表达式
var a;
console.log(a); //undefined
console.log(a, typeof a); //undefined 'undefined'
console.log(undefined == 'undefined'); //false
console.log(undefined === 'undefined'); //false
//判断一个a是否为undefined
console.log(typeof a===undefined); //false
console.log(typeof a==='undefined'); //true
console.log(a===undefined); //true
a = 3;
console.log(typeof a); //number
console.log(typeof a==='number'); //true
a = "123";
console.log(typeof a==='string'); //true
a = true;
console.log(typeof a==="boolean"); //true
a = null;
console.log(typeof a); //object
console.log(typeof a==="null"); //false
console.log(typeof a==="object"); //true
console.log(a===null); //true
console.log("----------------");
//2.对象
var b1 = {
b2: [1,'abc',console.log],
b3: function(){
console.log("b3");
return function(){
return "套中套";
}
}
}
//判断b1是不是Object类型的实例
console.log(b1 instanceof Object); //true
console.log(b1 instanceof Array); //false
// console.log(b2 instanceof Array); //报错
console.log(b1.b2 instanceof Array); //true
console.log(b1.b2 instanceof Object); //true
console.log(typeof b1.b2); //object 不能用typeof来区别对象和数组
console.log(b1.b3 instanceof Function); //true
console.log(b1.b3 instanceof Object); //true
console.log(typeof b1.b3); //function
console.log(typeof b1.b3==='function'); //true
console.log(typeof b1.b2[2]); //function
b1.b2[2]("hello"); //hello
console.log(b1.b3()());; //b3,套中套
</script>
</html>
相关问题
1.undefined与null的区别?
undefined代表定义未赋值
null定义并赋值了,只是值为null
2.什么时候给变量赋值null呢?
初始赋值,表明将要赋值为对象
结束前赋值,让obj指向的对象成为垃圾对象(被垃圾回收机制回收)
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>Document</title>
</head>
<body>
</body>
<script>
//实例:实例对象
//类型:类型对象
function Person(name,age){
this.name = name;
this.age = age;
}
var p = new Person();
//1.undefined与null的区别?
var a;
console.log(a); //undefined
a=null;
console.log(a); //null
//2.什么时候给变量赋值null呢?
//计划创建一个对象,但是数据还没产生,所以就赋值一个null,来表明是一个对象类型
var obj = null; //初始赋值为null,表明将要赋值为对象
obj = ['abc',12]; //确定对象就赋值
obj = null; //最后,释放这个对象(数组)所占用的内存
//3.严格区别变量类型与数据类型?
var c = {}
</script>
</html>
数据_变量__内存
1.什么是数据?
存储在内存中代表特定信息的 ‘东东’,本质上是0101…
数据的特点:可传递,可运算
一切皆数据
内存中所有操作的目标:数据
算数运算
逻辑运算
赋值
2.什么是内存?
内存条通电后产生的可存储数据的空间(临时的)
内存产生和死亡:内存条(电路板)>通电>产生内存空间==>存储数据>断电==>内存空间和数据都消失
一块小内存的2个数据
内部存储的数据
地址值
内存的分类
栈:局部变量 / 全局变量
堆:对象
3.什么是变量?
可变化的量,有变量名和变量值组成
每个变量都对应的一块小内存,变量名用来查找对应的内存,变量值就是内存中保存的数据
4.内存,数据,变量三者之间的关系?
内存是用来存储数据的空间(临时的)
变量是内存的标识
<script>
var age = 18;
console.log(age); //通过变量名去找到内存中所在的位置,并读取里面的数据
var a = 3;
var b= a + 2;
</script>
变量在内存中到底保存什么
问题:var a = xxx,a内存中到底保存什么?
xxx是基本数据,a保存的就是这个数据 xxx是对象,保存的是对象的地址值 xxx是一个变量,保存的是xxx的内存内容(可能是基本数据,也可能是地址值)
<script>
var a = 3;
//xxx是对象
var a = {} //a保存的是地址值
var f = function(){
console.log("hello");
};
// f也是保存的地址值
f(); //hello
console.log(f);
//xxx是一个变量
var b = 'abc'
a = b;
console.log(a); //abc
b = {};
a = b;
console.log(a); //{}
</script>
关于引用变量赋值问题
2个引用变量指向同一个对象,通过一个变量修改对象内部数据,另一个变量看到的是修改之后的数据
2个引用变量指向同一个对象,让其中一个引用变量指向另一个对象,另一个引用变量依然指向前一个对象
<!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>Document</title>
</head>
<body>
<script>
//2个引用变量指向同一个对象,通过一个变量修改对象内部数据,其他变量看到的是修改之后的数据
var obj1 = {name:"Tom"}
var obj2 = obj1;
obj1.name = "Mi";
console.log(obj2.name); //Mi
fn(obj2);
console.log(obj1.name); //fn
function fn(obj){
obj.name = "fn";
}
//2个引用变量指向同一个对象,让其中一个引用变量指向另一个对象,另一个引用变量依然指向前一个变量
var a = {age:12}
var b = a;
a = {name:"Tom",age:13}
console.log(b); //{age: 12}
console.log(a); //{name: 'Tom', age: 13}
function fn2(obj){
obj = {age:15} //不会改变a的值
// obj.age = 15; //这样才会改变a的值
}
//这个函数不会改变a的值
//因为33行的a和obj指向同一个地址,然后到34行的时候,obj指向了另一个地址,而a还是指向原来的地址
fn2(a);
console.log(a.age); //13
</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>Document</title>
</head>
<body>
<script>
function fun(obj){
console.log(obj.name);
}
var obj = {name:'Tom'};
fun(obj);
</script>
</body>
</html>
JS引擎如何管理内存
1.内存生命周期
分配小内存空间,得到它的使用权
存储数据,可以反复进行操作
释放小内存空间
2.释放内存
局部变量:函数执行完自动释放
对象:成为垃圾对象==>垃圾回收器回收
<!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>Document</title>
</head>
<body>
<script>
//这里占用了3个内存空间,a、obj和对象"{}",其中,对象占的内存空间最大
var a = 3;
var obj = {};
//此时还有两个空间,只有对象"{}",被释放了,obj还在
obj = null;
function fun(){
var b = {};
}
//29行的b只有到函数执行的时候(32行)才会产生空间,并且在函数结束的时候b会"自动释放"
fun(); //注意:b是自动释放的,但是,b所指向的空间是在后面某一时刻,被垃圾回收器回收的
</script>
</body>
</html>
对象
1.什么是对象?
多个数据的封装体
用来保存多个数据的容器
一个对象代表现实中的一个事务
2.为什么要用对象?
统一管理多个数据
3.对象的组成
属性:属性名(字符串)和属性值(任意类型)组成
方法:一种特别的属性(属性值为函数)
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>Document</title>
</head>
<body>
<script>
var obj = {
name: 'Tom',
age: 12,
setName: function(name){
this.name = name
},
setAge: function(age){
this.age = age;
}
}
obj.setName();
obj['setAge'](13);
console.log(obj.name, obj['age']); //undefined,13
</script>
</body>
</html>
什么时候用[‘属性名’]
1.属性名包含特殊字符:- 空格
2.属性名不确定
<!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>Document</title>
</head>
<body>
<script>
var p = {};
//给p对象添加一个属性:content-type:text/json
// p.content-type = "text/json"; //报错
p['content-type'] = 'text/json';
console.log(p['content-type']);
//2.属性名不确定
var propName = 'myAge';
var value = 18;
//此时的属性名就不是myAge,而是propName
// p.propName = value;
// console.log(p.myAge); //undefined
p[propName] = value;
console.log(p['myAge']); //18
</script>
</body>
</html>
函数
1.什么是函数?
实现特定功能的n条语句的封装体
只有函数是可以执行的,其他类型的数据不能执行
2.为什么要用函数?
提高代码复用
便于阅读交流
3.如何定义函数?
函数声明
表达式
4.如何调用(执行)函数?
test():直接调用
obj.test():通过对象调用
new test():通过new 调用
test.call/apply(obj):相当于obj.test()
临时让test成为obj的方法进行调用(obj里面是没有test方法的)
<!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>Document</title>
</head>
<body>
<script>
/*
编写程序实现以下功能需求:
1. 根据年龄输出对应的信息
2. 如果小于18,输出:未成年,再等等
3. 如果大于60,输出:算了吧!
4. 其他,输出:刚好!
*/
function showInfo(age){
if(age<18){
console.log("未成年,再等等");
}else if(age > 60){
console.log("算了吧!");
}else{
console.log("刚好!");
}
}
showInfo(18); //刚好!
showInfo(16); //未成年,再等等
showInfo(61); //算了吧!
//3. 如何定义函数?
function fn1(){ //函数声明
console.log('fun1()');
}
var fn2 = function(){ //表达式
console.log('fun2()');
}
//4. 如何调用(执行)函数?
var obj = {};
function test(){
this.xxx = 'zhangzh'
}
// obj.test(); //报错,不能直接调用,因为obj里面没有test方法
test.call(obj); //相当于obj.test()
console.log(obj.xxx); //zhangzh
</script>
</body>
</html>
回调函数
1.什么函数才是回调函数?
1). 你定义的
2). 你没有调用
3). 但最终它执行 了(在某个时刻或某个条件下)
2.常见的回调函数?
dom事件回调函数–>this为:发生事件的回调函数(此处为btn按钮)
定时器回调函数 -->this为:window
ajax请求回调函数
生命周期回调函数
<!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>Document</title>
</head>
<body>
<button id="btn">点击测试事件</button>
<script>
document.getElementById("btn").onclick = function(){ //dom事件回调函数
alert(this.innerHTML);
};
//定时器
//超时定时器
//循环定时器
setTimeout(function(){ //定时器回调函数
alert("到点了");
},2000);
</script>
</body>
</html>
IIFE
1.理解
全称:Immediately - Invoked Function Expression
立即 调用 函数 表达式
2.作用
隐藏实现
不会污染外部(全局)命名空间
用它来编码JS模块
<!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_IIFE</title>
</head>
<body>
<script>
(function (){ //匿名函数自调用 === IIFE
//这个a如果写道外面就会产生一个全局变量,
//但是用匿名函数的方法就可以将作用域限制到这个函数里面(局部变量)
var a = 3;
console.log(a + 3);
})();
(function(){
var a = 1;
function test(){
console.log(++a);
}
window.$ = function(){ //向外暴露一个全局函数
return {
test:test
}
}
})()
//windwo可以省略
window.$().test(); //2
</script>
</body>
</html>
函数中的this
1.this是什么?
任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是Window
所有函数内部都有一个变量this
它的值是调用函数的当前对象
2.如何确定this的值?
test():window
p.this():p
new test():新创建的对象
p.call(obj):obj
<!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>Document</title>
</head>
<body>
<!--
1.this是什么?
任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是Window
所有函数内部都有一个变量this
它的值是调用函数的当前对象
2.如何确定this的值?
test():window
p.this():p
new test():新创建的对象
p.call(obj):obj
-->
<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是:windwo
var p = new Person("yello"); //this是:p
console.log(p.getColor); //this是:p
var obj = {};
p.setColor.call(obj,"balck"); //this是:obj
var test = p.setColor; //得到一个函数
test(); //this是:window(直接调用)
function fun1(){
function fun2(){
console.log(this);
}
fun2(); //this是:window
}
fun1();
</script>
</body>
</html>
函数高级
原型和原型链
原型
1.函数的prototype属性(图)
每个函数都有一个prototype属性,它默认指向一个Object空对象(即称为:原型对象)
原型对象中有一个属性constructor,它指向函数对象
2.给原型对象添加属性(一般都是方法)
作用:函数的所有实例对象自动拥有原型中的属性(方法)
<!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>Document</title>
</head>
<body>
<script>
// 每个函数都有一个prototype属性,它默认指向一个Object空对象(即称为:原型对象)
console.log(Date.prototype,typeof Date.prototype); //Object 'object'
console.log(Date.prototype.prototype); //undefined
function Fun(){
}
console.log(Fun.prototype); //默认指向一个Object空对象(没有我们的属性)
// 原型对象中有一个属性constructor,它指向函数对象
console.log(Date.prototype.constructor === Date); //true
console.log(Fun.prototype.constructor === Fun); //true
//给原型对象添加属性(一般是方法),实例对象可以访问
//此时fun.prototype就有了test方法
Fun.prototype.test = function(){
console.log('test()');
}
var fun = new Fun();
var fun1 = new Fun();
fun.test(); //test()
fun1.test(); //test()
</script>
</body>
</html>
显式原型与隐式原型
1.每个函数function都有一个prototype,即显式原型
2.每个实例对象都有一个__ proto __,可称为隐式原型
3.对象的隐式原型的值为其对应构造函数的显示原型的值
4.内存结构图
5.总结:
实例对象的隐式原型等于构造函数的显示原型
函数的prototype属性:在定义函数时自动添加的,默认值是一个空Object对象
对象的__ proto __属性:创建对象时自动添加的,默认值为构造函数的prototype属性值
程序员能直接操作显式原型,但不能直接操作隐式原型(ES6之前)
内存结构图
<!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>Document</title>
</head>
<body>
<script>
function Fun(){ //内部语句:this.prototype = {}
}
var fun = new Fun(); //内部代码:this.__proto__ = Fun.prototype
//1.每个函数function都有一个prototype,即显式原型
console.log(Fun.prototype); //object
//2.每个实例对象都有一个__proto__,可称为隐式原型
console.log(fun.__proto__); //object
//3.对象的隐式原型的值为其对应构造函数的显示原型的值
console.log(fun.__proto__ === fun.__proto__); //true
//给原型添加方法
Fun.prototype.test = function(){
console.log('test()');
}
//因为__proto__和prototype指向同一个对象,所以修改prototype的话__proto__也能看得到
fun.__proto__.test(); //test()
//fun.test没有找到的话就会去__proto__里面一层一层找
fun.test(); //test()
</script>
</body>
</html>
原型链
1.原型链(图解)
访问一个对象的属性时:
现在自身属性中找,找到返回
- 如果没有,再沿着__ proto __这条链向上查找,找到返回
如果最终没有找到,返回undefined
别名:隐式原型链
作用:查找对象的属性(方法)
2.构造函数/原型/实体对象的关系(图解)
3.构造函数/原型/实体对象的关系2(图解)
原型链(图解)
构造函数/原型/实体对象的关系(图解)
构造函数/原型/实体对象的关系2(图解)
创建定义函数相当于var Foo = new Function()
所以Foo是Function的实例对象
所以Foo也有隐式原型__ proto __
所有函数的隐式原型都等于Function的显式原型!!!
<!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>Document</title>
</head>
<body>
<script>
function Fun(){
this.test1 = function(){
console.log('test1()');
}
}
Fun.prototype.test2 = function(){
console.log('test2()');
}
var fun = new Fun();
fun.test1(); //test1()
fun.test2(); //test2()
console.log(fun.toString()); //[object Object]
//相当于undefined(),undefined不是一个函数
// fun.test3(); //Uncaught TypeError: fun.test3 is not a function
console.log(fun.test3); //undefined
//实例对象隐式原型等于构造函数显式原型,系统默认会有一个object的对象
console.log(typeof Fun.prototype.__proto__); //object
//默认有一个Object类型的对象,并且是全局的
console.log(Object); //ƒ Object() { [native code] }
console.log(Object.prototype.toString); //ƒ toString() { [native code] }
//构造函数/原型/实体对象的关系2(图解)
//这里相当于var Foo = new Function()
function test(){
}
console.log(test.__proto__ === Function.prototype); //true
//所有函数的隐式原型都等于Function的显示原型!!!
console.log(Function.prototype === Object.__proto__); //true
console.log(test.__proto__ === Object.__proto__); //true
// **************
//Function的隐式原型等于显式原型
//实例对象的隐式原型等于构造函数的显示原型
//相当于 Function = new Function(),用自己来创建的
console.log(Function.__proto__ === Function.prototype); //true
</script>
</body>
</html>
补充
1.函数的显示原型指向的对象默认是空的Object实例对象(但Object不满足)
2.所有函数都是Function的实例(包括Function本身)
3.Object的原型对象,是原型链的尽头
<!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>Document</title>
</head>
<body>
<script>
function Fun(){};
//测试
console.log(Object.prototype.__proto__); //null
console.log(Fun.__proto__.__proto__.__proto__); //null
console.log(Fun instanceof Object); //true
console.log(Fun instanceof Function); //true
//1.函数的显示原型指向的对象默认是空的Object实例对象
console.log(Fun.prototype instanceof Object); //true
console.log(Function.prototype instanceof Object); //true
//但Object不满足
console.log(Object.prototype instanceof Object); //false
// 2.Function是它自身的实例
console.log(Fun instanceof Function); //true
console.log(Function instanceof Function); //true
console.log(Function.__proto__ === Function.prototype); //true
console.log(Fun.__proto__ === Function.__proto__); //true
// Object的原型对象,是原型链的尽头
console.log(Object.prototype.__proto__); //null
</script>
</body>
</html>
属性问题
1.读取对象的属性时:会自动到原型链中查找
2.设置对象的属性值时:不会查找原型链,如果当前对象中没有此属性,直接添加此属性并设置其值
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>04_原型链_属性问题</title>
</head>
<body>
<script>
function Fun(){
}
Fun.prototype.a = 'xxx';
var fun1 = new Fun();
//1.读取对象的属性时:会自动到原型链中查找
console.log(fun1.a); //xxx
//2.设置对象的属性值时:不会查找原型链,如果当前对象中没有此属性,直接添加此属性并设置其值
var fun2 = new Fun();
fun2.a = 'yyy';
console.log(fun1.a); //xxx
console.log(fun2.a); //yyy
console.log(fun2.__proto__.a); //xxx
console.log(fun2.a === fun1.__proto__.a); //false
console.log(fun2.a === fun2.__proto__.a); //false
console.log(fun1.a === fun2.__proto__.a); //true
//3.方法一般定义在原型中,属性一般通过构造函数定义在对象本身
function Person(name, age){
this.name = name;
this.age = age;
}
var p1 = new Person('Tom',18); //此时,name和age都在p1自身里面
Person.prototype.setName = function(name){
this.name = name;
}
p1.setName('MoMo');
console.log(p1.name); //MoMo
console.log(p1);
</script>
</body>
</html>
探索instanceof
1.instanceof是如何判断的?
表达式:A instanceof B
如果B函数的显式原型对象在A对象的原型链上,返回true,否则返回false
2.Function是通过new自己产生的实例
案例一
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
面试题
测试题一
var A= function(){
}
A.prototype.n = 1;
var b = new A(); //此时的prototype指向0x234
//此时prototype指向0x345
A.prototype = {
n:2,
m:3
}
var c = new A() //此时prototype指向0x345
console.log(b.n, b.m, c.n , c.m); //1 ,undefined ,2 ,3
//测试
console.log(A.prototype.__proto__); //相当于Object
console.log(c.__proto__ === b.__proto__); //false
console.log(c.__proto__.__proto__ === b.__proto__.__proto__); //true
console.log(c.__proto__.__proto__.__proto__); //null
console.log(A.prototype.__proto__ === c.__proto__.__proto__); //true
console.log(A.prototype.__proto__ === b.__proto__.__proto__); //true
面试题二
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是在Function,实例对象去不了那里,只有函数可以去到
//此时的F相当于实例对象(new Function() )
F.a(); //b()
F.b(); //b()
console.log(f.__proto__ === F.prototype); //true
console.log(f.__proto__ === Function.prototype); //false
执行上下文与执行上下文栈
变量提升与函数提升
1.变量声明提升
通过var定义(声明)的变量,在定义语句之前就可以访问到
值:undefined
2.函数声明提升
通过function声明的函数,在之前就可以直接调用
值:函数定义(对象)
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>变量提升与函数提升</title>
</head>
<body>
<script>
//面试题
var a = 3;
function fn(){
console.log(a); //undefined
var a = 4;
/*
相当于
var a;
console.log(a);
a = 4;
自身里面已经有了a,所以就会拿自身的值,而打印的时候a是没有值的
*/
}
fn();
// 通过var定义(声明)的变量,在定义语句之前就可以访问到
console.log(b); //undefined 变量提升
var b = 2;
// console.log(c); //报错:colse is not defined
// 通过function声明的函数,在之前就可以直接调用
fn2(); //可以运行 函数提升
fn3(); //不能运行,此时遵循的是变量提升
function fn2(){
console.log('fn2()');
}
var fn3 = function(){
console.log('fn3()');
}
</script>
</body>
</html>
执行上下文
1.代码分类(位置)
全局代码
函数(局部)代码
2.全局执行上下文
在执行全局代码前将window确定为全局执行上下文
对全局数据进行预处理
var定义的全局变量 ==> undefined,添加为window的属性
function声明的全局函数 ==> 赋值(fun),添加为window的方法
this ==> 赋值(window)
开始执行全局代码
3.函数执行上下文
在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象I(虚拟的,存在于栈中)
对局部数据进行预处理
形参变量 ==> 赋值(实参) ==> 添加为执行上下文的属性
arguments(伪数组) ==> 赋值(实参列表),添加为执行上下文的属性
<!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>Document</title>
</head>
<body>
<script>
//全局执行上下文
console.log(a1,window.a1); //undefined undefined
a2(); //a2()
console.log(this); //window
var a1 = 3;
//调试的时候会从34直接跳到41,因为a2已经执行过了
function a2(){
console.log('a2()');
}
fn(2,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()');
}
}
</script>
</body>
</html>
执行上下文栈
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); //10, 5, 15 = 30
}
bar(10);
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个上下文
<!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_执行上下文栈2</title>
</head>
<body>
<script>
console.log('global begin: ' + i); //undefined
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);
</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>
//测试一:函数提升优先级比变量提升要高,且不会被变量声明覆盖,但是会被变量赋值覆盖
//当函数声明与变量名相同时,在变量赋值前,函数声明依旧是函数声明,不会被覆盖
function a(){} //函数提升
var a; //变量提升
console.log(typeof a); //function
//当变量赋值后,函数声明被同变量覆盖
function b(){}
var b = 1;
console.log(typeof b); //number
//测试题二:
if(!(c in window)){ //判断window里面有没有属性c
var c = 1;
}
console.log(c); //undefined
//测试题三:
var d = 1;
function d(d){
console.log(d);
}
//当变量赋值后,函数声明被同变量覆盖
d(2); //报错,d不是一个函数
</script>
</body>
</html>
作用域和作用域链
作用域
1.理解
就是一块"底盘",一个代码段所在的区域
它就是静态的(相对于上下文对象),在编写代码时就确定了
2.分类
全局作用域
函数作用域
没有块作用域(ES6有了)
3.作用
隔离变量,不同作用域下同名的变量不会有冲突
<script>
var a = 10, b = 20;
function fn(x){
var a = 100, c = 100;
console.log('fn()',a,b,c,x); //100,20,100,10
function bar(x) {
var a = 1000,d = 400
console.log('bar()',a,b,c,d,x); //1000,20,100,400,100;1000,20,100,400,200
}
bar(100);
bar(200);
}
fn(10);
</script>
作用域与执行上下文
1.区别1
全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
全局执行上下文环境是在全局作用域确定之后,JS代马上执行之前创建
函数执行上下文环境实在调用函数时,函数体代码执行之前创建
2.区别2
作用域是静态的,只要函数定义好了就一直存在,且不会在变化
上下文环境是动态的,调用函数时创建,函数调用结束时上下文环境就会被自动释放
3.联系
上下文环境(对象)是从属于所在的作用域
全局上下文环境 ==> 全局作用域
函数上下文环境 ==> 对应的函数使用域
作用域链
1.理解
多个上下级关系的作用域形成的链,它的方向是从下向上的(从内到外)
查找变量时就是沿着作用域链来查找的
2.查找一个变量的查找规则
在当前作用域下的执行上下文中查找对应的属性,如果有直接返回,否则进入2
在上一级作用域的执行上下文中查找对应的属性,如果有直接返回,否则进入3
再次执行2的相同操作,直到全局作用域,如果还找不到就抛出找不到的异常
<!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>
var a = 1;
function fn1() {
var b = 2;
function fn2() {
var c = 3;
console.log(c); //3
console.log(b); //2
console.log(a); //1
console.log(d); //报错:d is not defined
}
fn2();
}
fn1();
</script>
</body>
</html>
面试题
面试题一
var x = 10;
function fn() {
//这里找不到,则去全局里面找,并不会去show里面
console.log(x); //10
}
function show(f) {
var x = 20;
f();
}
show(fn);
面试题二
var fn = function() {
console.log(fn); //function () {console.log(fn)}
}
fn();
var obj = {
fn2: function() {
//这个函数没有fn2,则直接会去全局里面找,找不到则报错
console.log(fn2); 报错:fn2 is not defined
//假如想找obj里面的fn2的写法
// console.log(this.fn2); //输出整个函数
}
}
obj.fn2()
闭包
循环遍历加监听
<!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>
<!-- 需求:点击某个按钮,提示"点击的是第n个按钮" -->
<script>
var btns = document.getElementsByTagName("button");
//遍历加监听
//方式一:
//length写在定义里面只会计算一次,不然每一次都会计算,也可以写在外面
// for(var i=0,length=btns.length;i<length;i++){
// //添加一个属性,用来保存下标
// btns[i].index = i;
// btns[i].onclick = function(){
// //这样写是错误的,因为当函数执行的时候,循环已经执行完毕了,所以无论点哪个都是同一个结果
// // console.log('第' + (i+1) + '个');
// console.log('第' + (this.index+1) + '个'); //正常显示
// }
// }
//方式二:利用闭包
for(var i=0,length=btns.length;i<length;i++){
//立即执行函数
(function(i){ //这里的i跟上面for里面的不一样
btns[i].onclick = function(){
console.log('第' + (i+1) + '个'); //正常显示
}
})(i);
}
</script>
</body>
</html>
闭包理解
1.如何产生闭包?
当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包
2.闭包到底是什么?
使用chrome调试查看
理解一:闭包是嵌套的内部函数(绝大部分人)
理解二:包含被引用的变量(函数)的对象(极少部分人)
注意:闭包存在于嵌套的内部函数中
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>02_闭包理解</title>
</head>
<body>
<script>
function fn1() {
var a = 2;
var b = 'abc';
function fn2() {
//[[Scopes]]: Scopes[2]
// 0: Closure (fn1)
// a: undefined
console.log(a);
}
fn2();
}
fn1();
</script>
</body>
</html>
常见的闭包
1.将函数作为另一个函数的返回值
2.将函数作为实参传递给另一个函数调用
<!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>
// 1. 将函数作为另一个函数的返回值
function fn1() {
var a = 2
function fn2() {
a++
console.log(a)
}
return fn2
}
var f = fn1()
f() // 3
f() // 4
// 2. 将函数作为实参传递给另一个函数调用
function showMsgDelay(msg, time) {
setTimeout(function () {
console.log(msg)
}, time)
}
showMsgDelay('hello', 1000)
</script>
</body>
</html>
闭包的作用
1.使用函数内部的变量在函数执行完后,仍然存活在内存中(延长了局部变量的生命周期)
2.让函数外部可以操作(读写)到函数内部的数据(变量/函数)
问题:
1.函数执行完后,函数内部声明的局部变量是否还存在?
一般是不存在,存在于闭包中的变量才可能存在
2.在函数外部能直接访问函数内部的局部变量吗?
不能,但是我们可以通过闭包的形式让外部去操作它
<!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>
// 1.将函数作为另一个函数的返回值
function fn1() {
var a = 2; //当程序执行到这里闭包就已经产生了,因为有函数提升,函数早已经被执行完了
function fn2() {
a++;
console.log('a:' + a);
}
function fn3() {
a--;
console.log(a);
} //当代码执行到这里的时候,fn3也已经被释放了,但是fn3被释放并不代表函数对象成为垃圾对象,因为被全局的f3所引用
fn2();
return fn3;
}
//此时return的是fn2
//为什么a还在?
// 因为局部变量fn2虽然被释放了,但是fn2所保存的函数对象的地址值给了全局变量f
// var f = fn1(); //闭包产生了几个,就看外部函数调用了几次,跟内部函数执行多少次无关
// f(); //a:3
// f(); //a:4
//此时return的是fn3
//现在fn2产生的闭包已经消失了,因为没有变量去引用它
//1.为什么会没释放,是因为函数创建时会生成GO也就是全局对象windows,然后还会产生自己的AO,也就是自己函数的局部变量
// 2.结合老师的代码,fn1创建时会生成GO也就是windows对象,其次会产生自己的AO也就是a=2
// 3.fn3被调用了,所以会生成全局的GO,然后会拿到父函数的AO也就是fn1的局部变量a,再生成自己的AO
var f3 = fn1(); //用的是fn3的闭包
f3(); //1
f3(); //0
</script>
</body>
</html>
闭包的生命周期
1.产生:在嵌套内部函数定义执行完时就产生了(不是在调用)
2.死亡:在嵌套的内部函数成为垃圾对象时
<!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>
function fn1(){
//此时闭包就已经产生了(函数提升,内部函数对象已经创建了)
var a = 2;
function fn2(){
a++;
console.log(a);
}
//如果函数是这样写的话,那么闭包就不是var a时产生,而是在var fn2时产生
//函数定义和函数执行是两码事
var fn2 = function(){
a++;
console.log(a);
}
return fn2;
}
var f = fn1();
f();
f();
console.log(typeof f); //function
f = null; //闭包死亡(包含闭包的函数对象成为垃圾对象)
</script>
</body>
</html>
闭包应用_自定义JS模块
闭包的应用:定义JS模块
具有特定功能的js文件
将所有的数据和功能都封装在一个函数内部(私有的)
只向外部暴露一个包含n个方法的对象或函数
模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能
方法一
自定义JS模块1.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>自定义JS模块1</title>
<script src="myModule1.js"></script>
</head>
<body>
<script>
var m = myModule();
m.doSomething(); //doSomething:MY ZHANG
m.doOtherthing(); //doOtherthing:my zhang
</script>
</body>
</html>
myModule1.js
function myModule() {
//私有属性
var msg = 'My zhang';
//操作数据的函数
function doSomething() {
console.log('doSomething:' + msg.toUpperCase()); //转大写
}
function doOtherthing() {
console.log('doOtherthing:' + msg.toLowerCase()); //转小写
}
//向外暴露(给外部使用的方法)
return {
doSomething: doSomething,
doOtherthing: doOtherthing
}
}
方法二
自定义JS模块1.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>自定义JS模块2</title>
<script src="myModule2.js"></script>
</head>
<body>
<script>
myModule2.doSomething(); //doSomething:MY ZHANG
myModule2.doOtherthing(); //doOtherthing:my zhang
</script>
</body>
</html>
myModule2.js
(function(){
//私有属性
var msg = 'My zhang';
//操作数据的函数
function doSomething() {
console.log('doSomething:' + msg.toUpperCase()); //转大写
}
function doOtherthing() {
console.log('doOtherthing:' + msg.toLowerCase()); //转小写
}
window.myModule2 = {
doSomething: doSomething,
doOtherthing: doOtherthing
};
})()
面试题_重难点 *
面试题一
<!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>08_面试题1</title>
</head>
<body>
<script>
//代码片段一:没有产生闭包
var name = "The Window"
var object = {
name: "My Object",
getNameFunc: function(){
return function(){
return this.name;
}
}
};
//因为object.getNameFunc()调用完返回一个函数,然后用直接调用的方式来调用
//相当于前面省略了window,所以this是window
alert(object.getNameFunc()()); ///My Window
//代码片段二:产生了闭包
var name2 = "The Window";
var object2 = {
name2: "My Object",
getNameFunc: function() {
var that = this;
return function(){
return that.name2;
}
}
}
//这里that保存的是object2的this,所以会打印My Object
alert(object2.getNameFunc()()); //My 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>08_面试题2</title>
</head>
<body>
<script>
function fun(n,o) {
console.log(o);
return {
fun:function(m){
return fun(m,n); //已经产生了闭包,n
}
};
}
//这里a存的只是第一次调用完成后的值(n),所以一直都是0
//如果这里用a = a.fun(1),那么结果就跟b一样
var a = fun(0); //此时产生了一个闭包,值为0
a.fun(1); //此时产生了一个新的闭包值为1(m),但是马上就消失了,因为return的时候并没有变量去接收它
a.fun(2);
a.fun(3); //undefined,0,0,0
//这里就相当于a = fun(0); a=a.fun(1); a=a.fun(2)
// 产生的新闭包有变量去接收
var b = fun(0).fun(1).fun(2).fun(3); //undefined,0,1,2
//fun(0).fun(1)执行完之后,闭包的值(n = 1)
// 而c.fun(2)产生的闭包并没有变量去接收它
// 所以每次打印o都是1
var c = fun(0).fun(1); c.fun(2); c.fun(3); //undefined,0,1,1
</script>
</body>
</html>
详解
我们来看最开始提到的代码,先简化一下
function fun(n,o){
return {
}
}
我们先看这段代码,fun 调用后会怎么样?
很明显会返回一个空对象,记住,fun调用后会返回对象,这点很重要。
function fun(n,o){
console.log(o);
return {
fun:function(m){
return fun(m,n);
}
};
}
var a = fun(0);
这里提一句,当调用函数的时候传入的实参比函数声明时指定的形参个数要少,剩下的形参都将设置为undefined值。
console.log(o);
输出undefined
var a = fun(0);
那a是值是什么,是fun(0),返回的那个对象
{
fun:function(m){
return fun(m,0);
}
}
这个对象,有一个fun的方法,方法返回的结果就是最外面 fun 调用的结果。
var a=fun(0),传入一个参数0,那就是说,函数fun中参数 n 的值是0了,而返回的那个对象中,需要一个参数n,而这个对象的作用域中没有n,它就继续沿着作用域向上一级的作用域中寻找n,最后在函数fun中找到了n,n的值是0,这段话是本文的重点, 明白这段,那问题就容易解决了。
说到这里,这道题基本上可以解决了,希望大家能听明白我上面说的话,下面的就简单了。我们一步一步看。
现在我们知道 a 是
{
fun:function(m){
return fun(m,0);
}
}
这样的一个对象
a.fun(1);
会怎么样?看代码
{
fun:function(1){
return fun(1,0);
}
}
a.fun(1);
返回的结果,就是 fun(1,0),返回的结果
function fun(n,o){ //n的值为1,o的值为0
console.log(o);
return {
fun:function(m){
return fun(m,n);//n的值为1
}
};
}
fun(1,0); //输出0,并返回一个对象,这个对象有一个fun的方法,这个方法调用后,会返回外层fun函数调用的结果,并且外层函数的第二个参数是 n 的值,也就是1
a.fun(2);
返回的结果,就是 fun(2,0),返回的结果
function fun(n,o){ //n的值为2,o的值为0
console.log(o);
return {
fun:function(m){
return fun(m,n); //n的值为2
}
};
}
fun(2,0); //输出0,并返回一个对象,这个对象有一个fun的方法,这个方法调用后,会返回外层fun函数调用的结果,并且外层函数的第二个参数是 n 的值,也就是2
a.fun(3);
就不说了,一样的。
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);
我们继续说b,b和a的不同在于,var a = fun(0);
之后一直用的是a这个对象,是同一个对象,而b每次用的都是上次返回的对象。
如果改成这样
var a = fun(0); a=a.fun(1); a=a.fun(2); a=a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);
把返回的对象,重新赋值给a,这样两行的结果就是一样的了。
var c = fun(0).fun(1); c.fun(2); c.fun(3);
c 与他们的不同,只是var c = fun(0).fun(1);
之后用的是同一个对象罢了。
文章来源:
顺便推荐几篇讲解闭包的文章
对象高级
对象的创建模式
Object构造函数创建
方式一:Object构造函数模式
套路:先创建空Object对象,再动态添加属性/方法
适合场景:起始时不确定对象内部数据
问题:语句太多
<!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_Object构造函数模式</title>
</head>
<body>
<script>
// 一个人:name:"Tom",age:12
var obj = new Object();
obj.name = "Tom";
obj.age = 12;
obj.setName = function(name){
this.name = name;
}
console.log(obj);
</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>
//这里是一条赋值语句
var obj = {
name:"Tom",
age: 12,
setName: function(name){
this.name = name
}
}
console.log(obj);
//如果创建多个对象代码很重复
var obj2 = {
name:"Bob",
age: 13,
setName: function(name){
this.name = name
}
}
</script>
</body>
</html>
工厂模式
方式三:工厂模式
套路:通过工厂函数动态创建对象并返回
适合场景:需要创建多个对象
问题:对象没有一个具体的类型,都是Object类型
<!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>
//返回一个对象的函数 ==> 工厂函数
function createPerson(name,age){
var obj = {
name:name,
age:age,
setName:function(name){
this.name = name;
}
}
return obj;
}
var obj1 = createPerson("Top",12);
var obj2 = createPerson("Bob",13);
//obj1和obj2都是Object类型
function createStudent(name,price){
var obj = {
name:name,
price:price,
}
return obj;
}
var s = createStudent("张三",100);
// s也是Object类型
//此时判定不了哪个是Person哪个是Student
console.log(s instanceof Object); //true
console.log(obj1 instanceof Object); //true
console.log(obj2 instanceof Object); //true
</script>
</body>
</html>
自定义构造函数模式
方式四:自定义构造函数模式
套路:自定义构造函数,通过new创建对象
适用场景:需要创建多个类型确定的对象
问题:每个对象都有相同的数据,浪费内存
<!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>
function Person(name,age){
this.name = name;
this.age = age;
setName = function(name){
this.name = name;
}
}
function Student(name,price){
this.name = name;
this.price = price;
}
var p = new Person("Tom",12);
var s = new Student("张三",100);
console.log(p instanceof Person); //true
console.log(p instanceof Student); //false
console.log(s instanceof Student); //true
//问题:每个对象都有相同的数据,浪费内存
var p2 = new Person("李四",13);
var p3 = new Person("王五",13);
//这里每一个对象都有一个setName方法,但是这个方法在每一个对象里都是一样的,浪费内存空间
console.log(p1,p2);
</script>
</body>
</html>
构造函数+原型组合模式
方式六:05_构造函数+原型组合模式
套路:自定义构造函数,属性在函数中初始化,方法添加到原型上
使用场景:需要创建多个类型确定的对象
<!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>
//在构造函数中只初始化一般属性
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.setName = function(name){
this.name = name;
}
var p1 = new Person("张三",13);
var p2 = new Person("李四",13);
console.log(p1.name,p1.age); //张三 13
console.log(p2.name,p2.age); //李四 13
p1.setName("Tom");
p2.setName("Bob");
console.log(p1.name,p1.age); //Tom 13
console.log(p2.name,p2.age); //Bob 14
</script>
</body>
</html>
继承模式
原型链的继承
方式一:原型链继承
1.套路
1.定义父类型构造函数
2.给父类型的原型添加方法
3.定义子类型的构造函数
4.创建父类型的对象赋值给子类型的原型
5.将子类型原型的构造属性设置为子类型
6.给子类型原型添加方法
7.创建子类型的对象:可以调用父类型的方法
2.关键
1.子类型额度原型为父类型的一个实例对象
<!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>
//父类型
function Super(){
this.supProp = "Super proterty";
}
Super.prototype.showSuperProp = function(){
console.log(this.supProp);
}
//子类型
function Sub(){
this.subProp = "Sub proterty"
}
//这个方法不要添加到这里,因为待会会更改Sub.prototype的指向,这里写的话这个方法将会消失
// Sub.prototype.showSubProp = function(){
// console.log(this.subProp);
// }
//我们之所以能看到Object的方法是因为有以下语句
// Sub.prototype = new Object();
// Sub.prototype = {};
// 这样我们就可以用Object的方法了Sub.toString();
//我们只需要把原型保存的值更改一下就可以看到父类原型的方法
// 子类型额度原型为父类型的一个实例对象
Sub.prototype = new Super();
//子类的方法应该写到Sub.prototype = new Super()的后面,不然会报错
Sub.prototype.showSubProp = function(){
console.log(this.subProp);
}
var s = new Sub();
s.showSuperProp(); //Super proterty
s.showSubProp(); //Sub proterty
console.log(Sub.prototype);
console.log(Super.prototype);
</script>
</body>
</html>
借用构造函数继承(假的)
1.套路
1.定义父类型构造函数
2.定义子类型构造函数
3.在子类型构造函数中调用父类型构造
2.关键:
1.在子类型构造函数中通用call()调用父类型构造函数
<!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>
function Person(name,age){
this.name = name;
this.age = age;
}
function Student(name,age,price){
//相当于this.Person(name,age),但是不能这样写
// 注意,此时Person的this为当前对象
Person.call(this,name,age);
this.price = price;
}
var s = new Student("张三",18,1000);
console.log(s.name,s.age,s.price); //张三 18 1000
</script>
</body>
</html>
组合继承
方式三:原型链+借用构造函数的组合继承
1.利用原型链实现对父类型对象的方法继承
2.利用call()借用父类型构建函数初始化相同属性
<!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>
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.setName = function(name){
this.name = name;
}
function Student(name,age,price){
//相当于this.Person(name,age),但是不能这样写
// 注意,此时Person的this为当前对象
Person.call(this,name,age); //为了能看到父类的属性
this.price = price;
}
Student.prototype = new Person(); //为了能看到父类的方法
Student.prototype.constructor = Student;
Student.prototype.setPrice = function(price){
this.price = price;
}
var s = new Student("Tom",18,13000);
s.setName("张三");
s.setPrice(150000);
console.log(s.name,s.age,s.price); //张三 18 150000
var p = new Person("李四",18);
// p.__proto__.setPrice(13000); //p.setPrice is not a function
// console.log(p.__proto__.price);
</script>
</body>
</html>
线程机制与事件机制
进程与线程
进程(process)
- 程序的一次执行, 它占有一片独有的内存空间
- 可以通过windows任务管理器查看进程
线程(thread)
- 是进程内的一个独立执行单元
- 是程序执行的一个完整流程
- 是CPU的最小的调度单元
相关知识
- 应用程序必须运行在某个进程的某个线程上
- 一个进程中至少有一个运行的线程: 主线程, 进程启动后自动创建
- 一个进程中也可以同时运行多个线程, 我们会说程序是多线程运行的
- 一个进程内的数据可以供其中的多个线程直接共享
- 多个进程之间的数据是不能直接共享的
- 线程池(thread pool): 保存多个线程对象的容器, 实现线程对象的反复利用
相关问题
何为多进程与多线程?
多进程运行: 一应用程序可以同时启动多个实例运行
多线程: 在一个进程内, 同时有多个线程运行
比较单线程与多线程?
多线程
优点
能有效提升CPU的利用率
缺点
创建多线程开销
线程间切换开销
死锁与状态同步问题
单线程
优点
顺序编程简单易懂
缺点
效率低
JS是单线程还是多线程?
js是单线程运行的
但使用H5中的 Web Workers可以多线程运行
浏览器运行是单线程还是多线程?
都是多线程运行的
浏览器运行是单进程还是多进程?
有的是单进程
firefox
老版IE
有的是多进程
chrome
新版IE
如何查看浏览器是否是多进程运行的呢?
任务管理器–>进程
浏览器内核
支撑浏览器运行的最核心的程序
不同的浏览器可能不一样
- Chrome, Safari : webkit
- firefox : Gecko
- IE : Trident
- 360,搜狗等国内浏览器: Trident + webkit
内核由很多模块组成
主线程
- js引擎模块 : 负责js程序的编译与运行
- html,css文档解析模块 : 负责页面文本的解析
- DOM/CSS模块 : 负责dom/css在内存中的相关处理
- 布局和渲染模块 : 负责页面的布局和效果的绘制(内存中的对象)
…
分线程
- 定时器模块 : 负责定时器的管理
- DOM事件响应模块 : 负责事件的管理
- 网络请求模块 : 负责ajax请求
定时器引发的思考
1.定时器真是定时执行吗?
定时器并不能保证真正定时执行
一般会延迟一丁点(可以接受),也有可能延迟很长时间(不能接受)
2.定时器回调函数是在分线程执行的吗?
在主线程执行的,JS是单线程的
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>03_定时器引发的思考</title>
</head>
<body>
<button>启动定时器</button>
<script>
var btn = document.getElementsByTagName("button")[0];
btn.onclick = function () {
//获取当前时间
// var start = new Date().getTime(); //方式一
var start = Date.now(); //方式二
console.log("启动定时器前...");
//算出时间差值
setTimeout(function () {
console.log('定时器执行了', Date.now() - start);
}, 200)
console.log("启动定时器后...");
//定时器执行了 202(不加上下面的循环则没什么太大的影响)
//做一个长时间的工作
for (let index = 0; index < 1000000000; index++) {
}
//定时器执行了 928
}
</script>
</body>
</html>
JS是单线程的
1.如何证明JS执行的单线程的?
setTimeout()的回调函数是在主线程执行的
定时器回调函数只有运行栈中的代码全部执行完后才有可能执行
2.为什么JS要用单线程模式,而不用多线程模式?
javaScript的单线程,与它的用途有关
作为浏览器脚本语言,javaScript的主要用途是与用户互动,以及操作DOM
这决定了它只能是单线程,否则会带来会复杂的同步问题
3.代码的分类
初始化代码
回调函数
4.JS引擎执行代码的基本流程
先执行初始化代码:包含一些特别的代码
设置定时器
绑定监听
发送AJAX请求
后面在某个时刻才会执行回调代码
<!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_JS是单线程的</title>
</head>
<body>
<script>
setTimeout(function(){
console.log('timeout 2222');
},5000);
setTimeout(function(){
console.log('timeout 1111');
},4000);
function fn(){
console.log('fn');
}
fn();
//执行顺序为:fn --> timeout 1111 --> timeout 2222
//alert会暂停当前主线程的执行,
// 同时暂停了计时(2022年7月12日22:39:24:并没有暂停,等五秒按确定之后直接出来了)
console.log("alert之前");
alert("------");
console.log("alert之后");
</script>
</body>
</html>
事件循环模型
1.所有代码分类
初始化执行代码(同步代码):包含绑定DOM事件监听,设置定时器,发送ajax请求的代码
回调执行代码(异步代码):处理回调逻辑
2.JS引擎执行代码的基本流程:
初始化代码===>回调代码
3.模型的2个重要组成部分:
事件管理模型
回调队列
4.模型的运转流程
执行初始化代码,将事件回调函数交给对应模块管理
当事件发生时,管理模块会将回调函数及其数据添加到回调队列中
只有当初始化代码执行完后(可能要一定时间),才会遍历读取回调队列中的回调函数执行
模型原理图
重要相关概念
- 执行栈
- execution stack
- 所有的代码都是在此空间中执行的
- 浏览器内核
- browser core
- js引擎模块(在主线程处理)
- 其它模块(在主/分线程处理)
- 任务队列
- task queue
- 消息队列
- message queue
- 事件队列
- event queue
- 事件轮询
- event loop
- 从任务队列中循环取出回调函数放入执行栈中处理(一个接一个)
- 事件驱动模型
- event-driven interaction model
- 请求响应模型
- request-response model
<!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>
<button>点击测试</button>
<script>
function fn1(){
console.log('fn1()');
}
fn1();
document.getElementsByTagName("Button")[0].onclick = function(){
console.log("点击了btn");
}
setTimeout(function(){
console.log('定时器执行了');
},1000);
function fn2(){
console.log('fn2()');
}
fn2();
//执行顺序为:
// 'fn1()' --> 'fn2()' --> '定时器执行了'/'点击了btn'(看手速)
</script>
</body>
</html>
H5 Web Workers(多线程)
介绍
- Web Workers 是 HTML5 提供的一个javascript多线程解决方案
- 我们可以将一些大计算量的代码交由web Worker运行而不冻结用户界面
- 但是子线程完全受主线程控制,且不得操作DOM。
所以,这个新标准并没有改变JavaScript单线程的本质1.H5规范提供了JS分线程的实现,取名为:Web Workers
2.相关API
Worker:构造函数,加载分线程执行的jS文件
Worker.prototype.onmessage:用于接收另一个线程的回调函数
Worker.prototype.postMessage:向另一个线程发送消息
3.不足
Worker内代码不能操作DOM(更新UI)
不能跨域加载JS
不是每个浏览器都支持这个新特性
图解
使用
创建在分线程执行的js文件
var onmessage =function (event){ //不能用函数声明 console.log('onMessage()22'); var upper = event.data.toUpperCase();//通过event.data获得发送来的数据 postMessage( upper );//将获取到的数据发送会主线程 }
在主线程中的js中发消息并设置回调
//创建一个Worker对象并向它传递将在新线程中执行的脚本的URL var worker = new Worker("worker.js"); //接收worker传过来的数据函数 worker.onmessage = function (event) { console.log(event.data); }; //向worker发送数据 worker.postMessage("hello world");
主线程
<!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_ Web_Workers</title>
</head>
<body>
<input type="text" placeholder="计算">
<button>计算</button>
<script>
var inp = document.getElementsByTagName("input")[0];
document.getElementsByTagName("button")[0].onclick = function(){
var number = inp.value;
//创建一个Worker对象
var worker = new Worker('worker.js'); //路径:worker.js
//向分线程发送消息
worker.postMessage(number);
console.log("主线程向分线程发送数据" + number);
//设置一个监听用来接收分线程的消息
worker.onmessage = function(event){
console.log('主线程接收分线程返回的数据' + event.data);
alert(event.data);
}
}
//斐波那契数列
//此时如果用户输入的数据比较大,那么这段代码就需要运行很久
//并且是在主线程运行的,运行期间用户不能进行任何操作
//所以可以将下列代码放到分线程执行,这样就不会影响主线程的操作
// function fibonacci(n){
// if(n <= 2){
// return 1;
// }
// return fibonacci(n-1) + fibonacci(n-2);
// }
</script>
</body>
</html>
分线程:worker.js
//创建在分线程执行的js文件
function fibonacci(n){
if(n <= 2){
return 1;
}
return fibonacci(n-1) + fibonacci(n-2);
}
var onmessage = function(event){
var number = event.data;
console.log("分线程接收到主线程发送的数据" + number);
var result = fibonacci(number);
postMessage(result);
console.log("分线程向主线程返回数据" + result);
// alert(result); alery是window的方法,在分线程不能调用
}
//分线程中的全局对象不再是window,所以在分线程中不可能更新界面
console.log(this);
/*
DedicatedWorkerGlobalScope
{name: '',
onmessageerror: null,
onmessage: ƒ,
cancelAnimationFrame: ƒ,
close: ƒ,
….....}
*/