本文共 34702 字,大约阅读时间需要 115 分钟。
值
)类型 String:任意字符串
Number:任意的数字
boolean:true/false
undefined:undefined
null:null
引用
)类型 Object:任意对象
Function:一种特别的对象(可以执行)
Array:一种特别的对象(数值下标, 内部数据是有序的)
typeof
undefined/ 数值 / 字符串 / 布尔值 / function
null与object object与array
instanceof
判断对象的具体类型
===
undefined, null
//1. 基本 // typeof返回数据类型的字符串表达 var a console.log(a, typeof a, typeof a==='undefined',a===undefined ) // undefined 'undefined' true true console.log(undefined==='undefined') a = 4 console.log(typeof a==='number') a = 'atguigu' console.log(typeof a==='string') a = true console.log(typeof a==='boolean') a = null console.log(typeof a, a===null) // 'object'------------------------------------------------------------------------------------- //2. 对象 var b1 = { b2: [1, 'abc', console.log], b3: function () { console.log('b3') return function () { return 'xfzhang' } } } console.log(b1 instanceof Object, b1 instanceof Array) // true false console.log(b1.b2 instanceof Array, b1.b2 instanceof Object) // true true console.log(b1.b3 instanceof Function, b1.b3 instanceof Object) // true true console.log(typeof b1.b2, '-------') // 'object' console.log(typeof b1.b3==='function') // true console.log(typeof b1.b2[2]==='function') b1.b2[2](4) console.log(b1.b3()())
undefined 与 nul l的区别?
// 1. undefined与null的区别? var a console.log(a) // undefined a = null console.log(a) // null
什么时候给变量赋值为 null 呢?
//起始 var b = null // 初始赋值为null, 表明将要赋值为对象 //确定对象就赋值 b = ['atguigu', 12] //最后 b = null // 让b指向的对象成为垃圾对象(被垃圾回收器回收) // b = 2
严格区别变量类型与数据类型?
变量内存值的类型
) 引用类型: 保存的是地址值
var c = function () { }console.log(typeof c) // 'function'
存储在内存中代表特定信息的'东东', 本质上是二进制0101...
可传递, 可运算
内存条通电后产生的可储存数据的空间(临时的)
内存产生和死亡
内存条(电路板)==>通电==>产生内存空间==>存储数据==>处理数据==>断电==>内存空间和数据都消失
内存的空间是临时的,而硬盘的空间是持久的
分配内存
声明变量和函数或创建对象时,JS 引擎会自动为此分配一定大小的内存来存放对应的数据
释放内存
清空内存中的数据,标识内存可以再分配使用(内存不释放就不能复用)
自动释放
垃圾回调器回调
堆空间的垃圾对象
内部存储的数据
地址值
内存分类
栈: 全局变量/局部变量
堆: 对象
可变化的量, 由变量名和变量值组成
变量名用来查找对应的内存, 变量值就是内存中保存的数据
内存是一个容器, 用来存储程序运行需要操作的数据
变量是内存的标识, 通过变量找到对应的内存,进而操作(读/写)内存中的数据
关于赋值和内存的问题
问题: var a = xxx, a内存中到底保存的是什么? * xxx是基本数据, 保存的就是这个数据 * xxx是对象, 保存的是对象的地址值 * xxx是一个变量, 保存的xxx的内存内容(可能是基本数据, 也可能是地址值)
关于引用变量赋值问题
关于引用变量赋值问题 * 2个引用变量指向同一个对象, 通过一个变量修改对象内部数据, 另一个变量看到的是修改之后的数据 * 2个引用变量指向同一个对象, 让其中一个引用变量指向另一个对象, 另一引用变量依然指向前一个对象 var obj1 = { name: 'Tom'} var obj2 = obj1 obj2.age = 12 console.log(obj1.age) // 12 function fn (obj) { obj.name = 'A' } fn(obj1) console.log(obj2.name) //A 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 function fn2 (obj) { obj = { age: 15} } fn2(a) console.log(a.age) // 13
关于数据传递问题
问题: 在js调用函数时传递变量参数时, 是值传递还是引用传递 * 理解1: 都是值(基本/地址值)传递 * 理解2: 可能是值传递, 也可能是引用传递(地址值) var a = 3 function fn (a) { a = a + 1 } fn(a) console.log(a) // 3 function fn2 (obj) { console.log(obj.name) } var obj = { name: 'Tom'} fn2(obj) // Tom
JS引擎如何管理内存
问题: JS引擎如何管理内存?1. 内存生命周期 * 分配小内存空间, 得到它的使用权 * 存储数据, 可以反复进行操作 * 释放小内存空间2. 释放内存 * 局部变量: 函数执行完自动释放 * 对象: 成为垃圾对象==>垃圾回收器回收 var a = 3 var obj = { } obj = undefined function fn () { var b = { } } fn() // b是自动释放, b所指向的对象是在后面的某个时刻由垃圾回收器回收
封装体
保存多个数据
的容器
事物
统一管理多个数据
属性
方法
特别的属性(属性值是函数)
.属性名
: 编码简单, 有时不能用['属性名']
: 编码麻烦, 能通用var p = { name: 'Tom', age: 12, setName: function (name) { this.name = name }, setAge: function (age) { this.age = age } } p.setName('Bob') p['setAge'](23) console.log(p.name, p['age'])
什么时候必须使用['属性名']的方式
属性名包含特殊字符: - 空格
属性名不确定
var p = { } //1. 给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 // p.propName = value //不能用 p[propName] = value console.log(p[propName])
函数
也可以称为对象的属性
如果一个函数作为一个对象的属性保存
方法
调用函数
就说调用对象的方法(method)
名称上的区别,没有其他区别
// 创建一个对象var obj = new Object();// 向对象中添加属性obj.name = 'zs';obj.age = 18;// 对象的属性值可以是任何的数据类型,也可以是个函数obj.sayName = function(){ console.log(obj.name);};function fun(){ console.log(obj.name);}// console.log(obj.sayName);// 调方法obj.sayName();// 调函数fun();
for...in
语句语法
for(var 变量 in 对象){}
for...in
语句 对象中有几个属性,循环体就会执行几次
每次执行时,会将对象中的一个属性的名字赋值给变量
for(var n in obj){ console.log(n) // 属性名 console.log(obj[n]) // 属性值}
作用域指一个变量的作用的范围
直接编写在 script 标签中
的 JS 代码,都在全局作用域
在页面打开时创建,在页面关闭时销毁
全局对象 window
代表的是一个浏览器的窗口
,它由浏览器创建
直接使用
创建的变量都会作为 window 对象的属性保存
创建的函数都会作为 window 对象的方法保存
污染了全局作用域的命名空间
不安全
var a = 10;console.log(window.a) // 10function fun(){ console.log(12)}window.fun() // 12
变量声明提前
var
关键字声明的变量,会在所有的代码执行之前被声明
如果声明变量时不使用 var 关键字,则变量不会被声明提前
全局变量
页面的任意的部分都可以访问的到
console.log(a); // undefinedvar a = 123;
函数声明提前
使用函数声明形式创建的函数 function 函数()
它会在所有的代码执行之前就被创建
使用函数表达式创建的函数,不会被声明提前,所以不能在声明前调用
fun()fun2() // undefined is not a function// 函数声明,会被提前创建function fun(){ console.log(1212)}// 函数表达式,不会被提前创建var fun2 = function(){ console.log(2121)}
调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁
每调用一次函数就会创建一个新的函数作用域
,他们之间时相互独立
的函数作用域
中可以访问到全局作用域的变量
全局作用域
中无法访问到函数作用域的变量
在函数作用域操作一个变量时,它会先在自身作用域中寻找
有就直接使用
没有则向上一级作用域中寻找,直到找到全局作用域
全局作用域中依然没有找到,则会报错 ReferenceError
在函数中要访问全局变量可以使用 window 对象
// 创建一个变量var a = 10;function fun(){ var a = 12 var b = 20; console.log(a) // 12}fun()console.log(b) // b is undefinedconsole.log(a) // 10
函数作用域也有声明提前的特性
使用 var 关键字声明的变量,会在函数中所有的代码执行之前被声明
函数声明也会在函数中所有的代码执行之前执行
function fun3(){ fun4() // console.log(a) // undefined var a = 32 function fun4(){ alert(12) }}fun3()
不使用 var 声明的变量都会成为全局变量
var c = 33function fun5(){ // console.log(c) // undefined // var c = 10 console.log(c) // 33 // d 没有使用 var 关键字,则会设置为全局变量 d = 100}fun5()// 在全局输出cconsole.log(d) // 100
定义形参就相当于在函数作用域中声明了变量
var e = 23function fun6(e){ alert(e) // undefined}fun6()
大批量的创建对象
使用的构造函数都是 Object
创建的对象都是 Object 这个类型
无法区分出多种不同类型的对象
// 创建对象var obj = { name: 'zs', age: 18, sayName: function(){ alert(this.name) }}function createPerson(name, age, gender){ // 创建一个新的对象 var obj = new Object() // 向对象中添加属性 obj.name = name; obj.age = age; obj.gender = gender; obj.sayName: function(){ alert(this.name); } // 将新的对象返回 return obj;}var obj2 = createPerson('zx',21,'男')console.log(obj2)
原型 prototype
prototype
这个属性对应着一个对象
,这个对象就是我们所谓的原型对象
普通函数调用 prototype 没有任何作用
构造函数的形式调用
时 隐含的属性
指向该构造函数的原型对象
通过 __proto__ 来访问该属性
原型对象
就相当于一个公共的区域
所有同一个类的实例都可以访问到这个原型对象
共有的内容,统一设置到原型对象中
先在对象自身中寻找
有,则直接使用
没有,则会去原型对象中寻找,如果找到则直接使用
创建构造函数
时,可以将这些对象共有的属性和方法
统一添加到构造函数的原型对象中
不用分别为每一个对象添加
不会影响到全局作用域
使每个对象都具有这些属性和方法
function MyClass(){ }// 向 MyClass 的原型中添加属性 aMyClass.prototype.a = 123;// 向 MyClass 的原型中添加一个方法MyClass.prototype.sayHello = function(){ alert('hello');};var mc = new MyClass();var mc2 = new MyClass();// console.log(MyClass.prototype); // [object Object]// console.log(mc.__proto__); // [object Object]// console.log(mc2.__proto__ == MyClass.prototype); // trueconsole.log(mc.a) // 123mc.sayHello();
in
检查对象中是否含有某个属性
时 对象中没有,但是原型中有,也会返回 true
hasOwnProperty()
来检查对象自身中是否含有该属性
只有当对象自身中含有属性时,才会返回 true
console.log("name" in mc)console.log(mc.hasOwnProperty("name"))
原型对象也是对象
,所以它也有原型
先在自身中寻找
自身中如果有,则直接使用
如果没有,则去原型对象中寻找,如果原型对象中有,则使用
如果没有,则去原型对象的原型对象中寻找,直到找到 Object 对象的原型
Object 对象的原型没有原型对象
如果在 Object 中依然没有找到,则返回 undefined
console.log(mc.__proto__.__proto__.hasOwnProperty("hasOwnProperty"))
垃圾回收(GC)
垃圾积攒过多以后,会导致程序运行的速度过慢
需要一个垃圾回收的机制,来处理程序运行过程中产生的垃圾
当一个对象没有任何的变量或属性对它进行引用
此时我们将永远无法操作该对象
垃圾
,这种对象过多占用大量的内存空间,导致程序运行变慢
清理
自动的垃圾回收机制
,会自动将这些垃圾对象从内存中销毁
不需要也不能进行垃圾回收的操作
将不再使用的对象设置为 null 即可
var obj = new Object();obj = null
实现特定功能的 n 条语句的封装体
只有函数是可以执行的, 其它类型的数据不能执行
typeof
检查一个函数对象时,会返回 function
// 创建一个函数对象// 可以将要封装的代码以字符串的形式传递给构造函数var fun = new Function("console.log('Hello 这是我的第一个函数');")// 封装到函数中的代码不会立即执行// 函数中的代码会在函数调用的时候执行// 调用函数 语法:函数对象()// 当调用函数时,函数中封装的代码会按照顺序执行fun();/* 使用函数声明来创建一个函数 语法: function 函数名([形参1,形参2...形参N]){ 语句 } */function fun2(){ console.log("第二个函数") alert("哈哈哈") document.write("123")}// console.log(fun2)// 调用 fun2fun2()/* 使用函数表达式来创建一个函数 语法: var 函数名 = function([形参1, 形参2...形参N]){ 语法... } */var fun3 = function(){ console.log("我是匿名函数中封装的代码")};fun3()
提高代码复用
便于阅读交流
/* 编写程序实现以下功能需求: 1. 根据年龄输出对应的信息 2. 如果小于18, 输出: 未成年, 再等等! 3. 如果大于60, 输出: 算了吧! 4. 其它, 输出: 刚好! */ function showInfo (age) { if(age<18) { console.log('未成年, 再等等!') } else if(age>60) { console.log('算了吧!') } else { console.log('刚好!') } } showInfo(17) showInfo(20) showInfo(65)
函数声明
函数表达式
var fun = new Function(){ ...} // 构造函数 function fn1 () { // 函数声明 console.log('fn1()') } var fn2 = function () { // 函数表达式 console.log('fn2()') } fn1() fn2()
test(): 直接调用
obj.test(): 通过对象调用
new test(): new调用
test.call/apply(obj): 临时让 test 成为 obj 的方法进行调用
var obj = { } function test2 () { this.xxx = 'atguigu' } // obj.test2() 不能直接, 根本就没有 test2.call(obj) // obj.test2() // 可以让一个函数成为指定任意对象的方法进行调用 console.log(obj.xxx)
形参
在函数的()中来指定一个或多个形参(形式参数)
多个形参之间使用,隔开
,声明形参就相当于在函数内部声明了对应的变量
并不赋值
function sum(a,b){ console.log(a+b);}
实参
在()中指定实参(实际参数)
实参将会赋值给函数中对应的形参
sum(1,2)
调用函数时解析器不会检查实参的类型
是否有可能会接收到非法的参数
,如果有可能则需要对参数进行类型的检查
函数的实参可以是任意的数据类型
sum(123,'hello')sum(true, false)
解析器也不会检查实参的数量
多余的实参不会被赋值
实参的数量少于形参的数量
,则没有对应实参的形参将是 undefined
sum(123,345,'hello',true,null)sum(123)
return
来设置函数的返回值
语法
return 值
return 后的值
将会作为函数的执行结果
返回,可以定义一个变量,来接收该结果return 后的语句都不会执行
return 语句后不跟任何值就相当于返回一个 undefined
函数中不写 return,则也会返回 undefined
return 后可以跟任意类型的数据类型,也可以是个对象,也可以是个函数
// 创建一个函数,用来计算三个数的和function sum(a,b,c){ var d = a + b + c; return d; // alert('123') // return; // return undefined;}// 调用函数// 变量 result 的值就是函数的执行结果// 函数返回什么 result 的值就是什么var result = sum(4,7,8)console.log(result)
返回值的类型
for(var i=0; i<5; i++){ if(i == 2){ // 使用 break 可以退出当前的循环 // break; // continue 用于跳出当次循环 // continue; // 使用 return 可以结束整个函数 return; } }
立即调用函数表达式
(Immediately-Invoked Function Expression
)作用
隐藏实现
不会污染外部(全局)命名空间
用它来编码js模块
(function () { //匿名函数自调用 var a = 3 console.log(a + 3) // 6 })() var a = 4 console.log(a) // 4 ;(function () { var a = 1 function test () { console.log(++a) // 2 } window.$ = function () { // 向外暴露一个全局函数 return { test: test } } })() $().test() // 1. $是一个函数 2. $执行后返回的是一个对象
每次都会向函数内部传递一个隐含的参数
隐含的参数就是 this,this 指向的是一个对象
函数执行的上下文对象
函数的调用方式的不同
,this 会指向不同的对象
函数
的形式调用时,this 永远都是 window
方法
的形式调用时,this 就是调用方法的那个对象
任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是 window
所有函数内部都有一个变量 this
它的值是调用函数的当前对象
函数
的形式调用时,this 是 window
方法
的形式调用时,谁调用方法 this 就是谁
在构造函数中,this 指向新建对象
function Person(color) { console.log(this) this.color = color; this.getColor = function () { console.log(this) return this.color; }; this.setColor = function (color) { console.log(this) this.color = color; }; } Person("red"); //this是谁? window var p = new Person("yello"); //this是谁? p p.getColor(); //this是谁? p var obj = { }; p.setColor.call(obj, "black"); //this是谁? obj var test = p.setColor; test(); //this是谁? window function fun1() { function fun2() { console.log(this); } fun2(); //this是谁? window } fun1(); //this是谁? window
可以不加分号
编码风格问题
, 没有应该不应该,只有你自己喜欢不喜欢在下面2种情况下不加分号会有问题
小括号开头的前一条语句
中方括号开头的前一条语句
在行首加分号
var a = 3 ;(function () { })() /* 错误理解 var a = 3(function () { })(); */ var b = 4 ;[1, 3].forEach(function () { }) /* 错误理解 var b = 4[3].forEach(function () { }) */
创建方式和普通函数没有区别
首字母大写
调用方式的不同
普通函数是直接调用
构造函数需要使用 new 关键字来调用
function Person(){ }var per = new Person()console.log(per)
执行流程
立刻创建一个新的对象
将新建的对象设置为函数中 this
在构造函数中可以使用 this 来引用新建的对象
逐行执行函数中的代码
将新建的对象作为返回值返回
function Person(name, age){ this.name = name; this.age = age;}var per = new Person('zx',18)console.log(per)
同一个构造函数创建的对象
,称为一类对象,也将一个构造函数称为一个类
通过一个构造函数创建的对象,称为是该类的实例
instanceof
可以检查一个对象是否是一个类的实例
对象 instanceof 构造函数
是,则返回 true
不是,则返回 false
所有的对象都是 Object 的后代
任何对象和 Object 做 instanceof 检查时都会返回 true
console.log(per instanceof Person)
什么函数才是回调函数?
定义
的函数没有调用
这个函数最终这个函数执行
了(在某个时刻或某个条件下
)常见的回调函数
dom 事件
回调函数 ==>发生事件的 dom 元素定时器
回调函数 ===> windowajax 请求
回调函数生命周期
回调函数document.getElementById('btn').onclick = function () { // dom事件回调函数 alert(this.innerHTML) } //定时器 // 超时定时器 // 循环定时器 setTimeout(function () { // 定时器回调函数 alert('到点了'+this) }, 2000)
函数的 prototype 属性(图)
prototype
属性, 它默认指向
一个 Object 空对象(即称为: 原型对象)
原型对象
中有一个属性 constructor
, 它指向函数对象
给原型对象添加属性(一般都是方法)
函数的所有实例对象自动拥有原型中的属性(方法)
// 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象) console.log(Date.prototype, typeof Date.prototype) function Fun () { //alt + shift +r(重命名rename) } console.log(Fun.prototype) // 默认指向一个Object空对象(没有我们的属性) // 原型对象中有一个属性constructor, 它指向函数对象 console.log(Date.prototype.constructor===Date) // true console.log(Fun.prototype.constructor===Fun) // true //给原型对象添加属性(一般是方法) ===>实例对象可以访问 Fun.prototype.test = function () { console.log('test()') } var fun = new Fun() fun.test() // test()
function
都有一个 prototype
,即显式原型(属性)
实例对象
都有一个__proto__
,可称为隐式原型(属性)
对象的隐式原型的值为其对应构造函数的显式原型的值
内存结构(图)
总结
prototype
属性: 在定义函数时自动添加
的, 默认值是一个空 Object 对象
__proto__
属性: 创建对象时自动添加
的, 默认值为构造函数的 prototype 属性值
原型
对象即为当前实例
对象的父对象
直接操作显式原型
, 但不能直接操作隐式原型(ES6之前)
//定义构造函数 function Fn() { // 内部语句: this.prototype = {} } // 1. 每个函数function都有一个prototype,即显式原型属性, 默认指向一个空的Object对象 console.log(Fn.prototype) // 2. 每个实例对象都有一个__proto__,可称为隐式原型 //创建实例对象 var fn = new Fn() // 内部语句: this.__proto__ = Fn.prototype console.log(fn.__proto__) // 3. 对象的隐式原型的值为其对应构造函数的显式原型的值 console.log(Fn.prototype===fn.__proto__) // true //给原型添加方法 Fn.prototype.test = function () { console.log('test()') } //通过实例调用原型的方法 fn.test()
原型链(图解)
先在自身属性中查找,找到返回
如果没有, 再沿着__proto__这条链向上查找, 找到返回
如果最终没找到, 返回undefined
隐式原型链
查找对象的属性(方法)
// console.log(Object) //console.log(Object.prototype) console.log(Object.prototype.__proto__) function Fn() { this.test1 = function () { console.log('test1()') } } console.log(Fn.prototype) Fn.prototype.test2 = function () { console.log('test2()') } var fn = new Fn() fn.test1() fn.test2() console.log(fn.toString()) console.log(fn.test3) // fn.test3() /* 1. 函数的显示原型指向的对象默认是空Object实例对象(但Object不满足) */ console.log(Fn.prototype instanceof Object) // true console.log(Object.prototype instanceof Object) // false console.log(Function.prototype instanceof Object) // true /* 2. 所有函数都是Function的实例(包含Function) */ console.log(Function.__proto__===Function.prototype) /* 3. Object的原型对象是原型链尽头 */ console.log(Object.prototype.__proto__) // null
构造函数/原型/实体对象的关系(图解)
var o1 = new Object();var o2 = { };
构造函数/原型/实体对象的关系2(图解)
function Foo(){ }// 本质var Foo = new Function(){ }Function = new Function()所有函数的__proto__都是一样的
函数的显示原型指向的对象默认是空Object实例对象(但Object不满足)
console.log(Fn.prototype instanceof Object) // true console.log(Object.prototype instanceof Object) // false console.log(Function.prototype instanceof Object) // true
所有函数都是Function的实例(包含Function本身)
console.log(Function.__proto__===Function.prototype)
Object的原型对象是原型链尽头
console.log(Object.prototype.__proto__) // null
原型继承
构造函数的实例对象自动拥有构造函数原型对象的属性(方法)
利用的就是原型链
读取对象的属性值
时: 会自动到原型链中查找
设置对象的属性值
时: 不会查找原型链
, 如果当前对象中没有此属性
, 直接添加此属性并设置其值
方法一般定义在原型中
, 属性一般通过构造函数定义在对象本身上
function Fn() { } Fn.prototype.a = 'xxx' var fn1 = new Fn() console.log(fn1.a, fn1) // xxx var fn2 = new Fn() fn2.a = 'yyy' console.log(fn1.a, fn2.a, fn2) // xxx yyy function Person(name, age) { this.name = name this.age = age } Person.prototype.setName = function (name) { this.name = name } var p1 = new Person('Tom', 12) p1.setName('Bob') console.log(p1) // Bob 12 var p2 = new Person('Jack', 12) p2.setName('Cat') console.log(p2) // Cat 12 console.log(p1.__proto__===p2.__proto__) // true
instanceof 是如何判断的?
A instanceof B
A 是实例对象,B 是构造函数对象
如果 B 函数的显式原型对象在 A 对象的原型链上, 返回 true, 否则返回 false
Function 是通过 new 自己产生的实例
/* 案例1 */ function Foo() { } var f1 = new Foo() console.log(f1 instanceof Foo) // true console.log(f1 instanceof Object) // true
/* 案例2 */ console.log(Object instanceof Function) // true console.log(Object instanceof Object) // true console.log(Function instanceof Function) // true console.log(Function instanceof Object) // true function Foo() { } console.log(Object instanceof Foo) // false
/* 测试题1 */ function A () { } 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
/* 测试题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 is not a function F.a() // a() F.b() // b() console.log(f) console.log(Object.prototype) console.log(Function.prototype)
变量声明提升
var
定义(声明)的变量, 在定义语句之前就可以访问到
undefined
函数声明提升
function
声明的函数, 在之前就可以直接调用
函数定义(对象)
问题: 变量提升和函数提升是如何产生的?
/* 面试题 : 输出 undefined */ var a = 3 function fn () { console.log(a) var a = 4 } fn() // undefined console.log(b) //undefined 变量提升 fn2() //可调用 函数提升 // fn3() //不能 变量提升 var b = 3 function fn2() { console.log('fn2()') } var fn3 = function () { console.log('fn3()') }
代码分类(位置)
全局
代码函数(局部)
代码全局执行上下文
window
确定为全局执行上下文
预处理
var定义的全局变量==>undefined, 添加为window的属性
function声明的全局函数==>赋值(fun), 添加为window的方法
this==>赋值(window)
开始执行
全局代码// 全局执行上下文 console.log(a1, window.a1) // undefined undefined window.a2() // a2() console.log(this) // window var a1 = 3 function a2() { console.log('a2()') } console.log(a1) // 3
函数执行上下文
准备执行函数体之前
, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)
局部数据
进行预处理
形参变量==>赋值(实参)==>添加为执行上下文的属性
arguments==>赋值(实参列表), 添加为执行上下文的属性
var定义的局部变量==>undefined, 添加为执行上下文的属性
function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
this==>赋值(调用函数的对象)
开始执行
函数体代码// 函数(局部)执行上下文 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)
全局代码执行前
, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
全局执行上下文(window)确定
后, 将其添加到栈中(压栈)
函数执行上下文创建后
, 将其添加到栈中(压栈)
当前函数执行完后
,将栈顶的对象移除(出栈)
所有的代码执行完后
, 栈中只剩下window
// 1. 进入全局执行上下文 var a = 10 var bar = function (x) { var b = 5 // 3. 进入foo执行上下文 foo(x + b) } var foo = function (y) { var c = 5 console.log(a + c + y) } // 2. 进入bar函数执行上下文 bar(10)
console.log('gb: '+ i) // i为undefinedvar i = 1foo(1)function foo(i) { if (i == 4) { return } console.log('fb:' + i) foo(i + 1) //递归调用: 在函数内部调用自己 console.log('fe:' + i)}console.log('ge: ' + i)## 测试题1: 先执行变量提升, 再执行函数提升function a() { }var aconsole.log(typeof a) // 'function'## 测试题2:if (!(b in window)) { var b = 1}console.log(b) // undefined## 测试题3:var c = 1function c(c) { console.log(c) var c = 3}c(2) // 报错
理解
静态的(相对于上下文对象)
, 在编写代码时就确定了分类
全局
作用域函数
作用域块作用域
(ES6有了)作用
隔离变量
,不同作用域下同名变量不会有冲突
/* //没块作用域if(true) { var c = 3}console.log(c)*/var a = 10, b = 20function fn(x) { var a = 100, c = 300; console.log('fn()', a, b, c, x) function bar(x) { var a = 1000, d = 400 console.log('bar()', a, b, c, d, x) } bar(100) // bar() 1000 20 300 400 100 bar(200) // bar() 1000 20 300 400 200}fn(10) // fn() 100 20 300 10
区别1
区别2
联系
var a = 10,b = 20function fn(x) { var a = 100, c = 300;console.log('fn()', a, b, c, x)function bar(x) { var a = 1000, d = 400 console.log('bar()', a, b, c, d, x)}bar(100) // bar() 1000 20 300 400 100bar(200) // bar() 1000 20 300 400 200}fn(10) // fn() 100 20 300 10
理解
查找一个变量的查找规则
var a = 1function 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()
var x = 10;function fn() { console.log(x); // 10}function show(f) { var x = 20; f();}show(fn); // 10
var fn = function () { console.log(fn)}fn() // fn函数var obj = { fn2: function () { console.log(fn2) // 找不到fn2 //console.log(this.fn2) // 能找到fn2 }}obj.fn2() // fn2 is not defined
如何产生闭包?
闭包到底是什么?
产生闭包的条件?
function fn1 () { var a = 2 var b = 'abc' function fn2 () { //执行函数定义就会产生闭包(不用调用内部函数) console.log(a) } fn2()}fn1() // 2function fun1() { var a = 3 var fun2 = function () { console.log(a) }}fun1()
将函数作为另一个函数的返回值
将函数作为实参传递给另一个函数调用
// 1. 将函数作为另一个函数的返回值function fn1() { var a = 2 function fn2() { a++ console.log(a) } return fn2}var f = fn1()f() // 3f() // 4// 2. 将函数作为实参传递给另一个函数调用function showDelay(msg, time) { setTimeout(function () { alert(msg) }, time)}showDelay('atguigu', 2000)
延长了局部变量的生命周期
)操作
(读写)到函数内部的数据
(变量/函数)不存在
, 存在于闭中的变量才可能存在
不能
, 但我们可以通过闭包让外部操作它
function fn1() { var a = 2 function fn2() { a++ console.log(a) // return a } function fn3() { a-- console.log(a) } return fn3}var f = fn1()f() // 1f() // 0
产生
嵌套内部函数定义执行完时
就产生了(不是在调用)死亡
嵌套的内部函数成为垃圾对象时
function fn1() { //此时闭包就已经产生了(函数提升, 内部函数对象已经创建了) var a = 2 function fn2 () { a++ console.log(a) } return fn2}var f = fn1()f() // 3f() // 4f = null //闭包死亡(包含闭包的函数对象成为垃圾对象)
定义JS模块
特定功能
的js文件所有的数据和功能都封装在一个函数内部(私有的)
只向外暴露一个包含n个方法的对象或函数
只需要通过模块暴露的对象调用方法来实现对应的功能
myModule.js
function myModule() { //私有数据 var msg = 'My atguigu' //操作数据的函数 function doSomething() { console.log('doSomething() '+msg.toUpperCase()) } function doOtherthing () { console.log('doOtherthing() '+msg.toLowerCase()) } //向外暴露对象(给外部使用的方法) return { doSomething: doSomething, doOtherthing: doOtherthing }}
================================================================
myModule2.js
(function () { //私有数据 var msg = 'My atguigu' //操作数据的函数 function doSomething() { console.log('doSomething() '+msg.toUpperCase()) } function doOtherthing () { console.log('doOtherthing() '+msg.toLowerCase()) } //向外暴露对象(给外部使用的方法) window.myModule2 = { doSomething: doSomething, doOtherthing: doOtherthing }})()
缺点
解决
function fn1() { var arr = new Array[100000] function fn2() { console.log(arr.length) } return fn2}var f = fn1()f()f = null //让内部函数成为垃圾对象-->回收闭包
内存溢出
内存泄露
常见的内存泄露
// 1. 内存溢出var obj = { }for (var i = 0; i < 10000; i++) { obj[i] = new Array(10000000) console.log('-----')}// 2. 内存泄露 // 意外的全局变量function fn() { a = new Array(10000000) console.log(a)}fn() // 没有及时清理的计时器或回调函数var intervalId = setInterval(function () { //启动循环定时器后不清理 console.log('----')}, 1000)// clearInterval(intervalId)// 闭包function fn1() { var a = 4 function fn2() { console.log(++a) } return fn2}var f = fn1()f()// f = null
//代码片段一var name = "The Window";var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; }};alert(object.getNameFunc()()); //? the window 没有闭包//代码片段二var name2 = "The Window";var object2 = { name2 : "My Object", getNameFunc : function(){ var that = this; return function(){ return that.name2; }; }};alert(object2.getNameFunc()()); //? my object 有闭包
function fun(n,o) { console.log(o) return { fun:function(m){ return fun(m,n) } }}var a = fun(0)a.fun(1)a.fun(2)a.fun(3)//undefined,0,0,0var b = fun(0).fun(1).fun(2).fun(3)//undefined,0,1,2var c = fun(0).fun(1)c.fun(2)c.fun(3)//undefined,0,1,1
对象
属于一种复合的数据类型
,在对象中可以保存多个不同数据类型的属性
内建对象
ES 标准
中定义的对象,在任何的 ES 的实现中都可以使用
Math String Number Boolean Function Object...
宿主对象
JS 的运行环境
提供的对象,目前来讲主要指由浏览器提供的对象
BOM DOM
自定义对象
属性名
不强制要求遵守标识符的规范
特殊的属性名
,不能采用 . 的方式来操作
对象["属性名"] = 属性值
读取时也需要采用这种方式
[]
这种形式去操作属性,更加的灵活
[]
中可以直接传递一个变量
,这样变量值是多少就会读取那个属性
var obj = new Object()obj.name = 'zs'obj.var = 'hello'console.log(obj.var)obj["123"] = 789var n = 'nihao'console.log(obj["123"])
属性值
任意的数据类型
对象
in 运算符
检查一个对象中是否含有指定的属性
如果有则返回 true, 没有则返回 false
"属性名" in 对象
obj.test = trueobj.test = nullobj.test = undefined// 创建一个对象var obj2 = new Object()obj2.name = 'zs'// 将 obj2 设置为 obj 的属性obj.test = obj2console.log(obj.test.name) // zsconsole.log(obj.test2) // undefined// 检查 obj 中是否含有 test2 属性console.log("test2", in obj) // false
在创建对象时,直接指定对象中的属性
{属性名: 属性值,属性名: 属性值...}
对象字面量的属性名可以加引号也可以不加
,建议不加使用一些特殊的名字,则必须加引号
名值对结构
:
连接,多个名值对之间使用,
隔开一个属性之后没有其他的属性
了,就不要写,
// 创建一个对象var obj = new Object()// 使用对象字面量来创建一个对象var obj = { }// console.log(typeof obj) // objectobj.name = 'zs'// console.log(obj.name)var obj2 = { name: 'zs', age: 23, gender: '男', test: { name: 'zd'}}console.log(obj2.test)
Object构造函数模式
套路
: 先创建空Object对象, 再动态添加属性/方法适用场景
: 起始时不确定对象内部数据问题
: 语句太多/*一个人: name:"Tom", age: 12 */// 先创建空Object对象var p = new Object()p = { } //此时内部数据是不确定的// 再动态添加属性/方法p.name = 'Tom'p.age = 12p.setName = function (name) { this.name = name}//测试console.log(p.name, p.age)p.setName('Bob')console.log(p.name, p.age)
对象字面量模式
套路
: 使用{}创建对象, 同时指定属性/方法适用场景
: 起始时对象内部数据是确定的问题
: 如果创建多个对象, 有重复代码var p = { name: 'Tom', age: 12, setName: function (name) { this.name = name }}//测试console.log(p.name, p.age)p.setName('JACK')console.log(p.name, p.age)var p2 = { //如果创建多个对象代码很重复 name: 'Bob', age: 13, setName: function (name) { this.name = name }}
工厂模式
套路
: 通过工厂函数动态创建对象并返回适用场景
: 需要创建多个对象问题
: 对象没有一个具体的类型, 都是Object类型function createPerson(name, age) { //返回一个对象的函数===>工厂函数 var obj = { name: name, age: age, setName: function (name) { this.name = name } } return obj}// 创建2个人var p1 = createPerson('Tom', 12)var p2 = createPerson('Bob', 13)// p1/p2是Object类型function createStudent(name, price) { var obj = { name: name, price: price } return obj}var s = createStudent('张三', 12000)// s也是Object
自定义构造函数模式
套路
: 自定义构造函数, 通过new创建对象适用场景
: 需要创建多个类型确定的对象问题
: 每个对象都有相同的数据, 浪费内存//定义类型function Person(name, age) { this.name = name this.age = age this.setName = function (name) { this.name = name }}var p1 = new Person('Tom', 12)p1.setName('Jack')console.log(p1.name, p1.age)console.log(p1 instanceof Person)function Student (name, price) { this.name = name this.price = price}var s = new Student('Bob', 13000)console.log(s instanceof Student)var p2 = new Person('JACK', 23)console.log(p1, p2)
构造函数+原型的组合模式
套路
: 自定义构造函数, 属性在函数中初始化, 方法添加到原型上适用场景
: 需要创建多个类型确定的对象function Person(name, age) { //在构造函数中只初始化一般函数 this.name = name this.age = age}Person.prototype.setName = function (name) { this.name = name}var p1 = new Person('Tom', 23)var p2 = new Person('Jack', 24)console.log(p1, p2)
原型链继承
套路
关键
//父类型 function Supper() { this.supProp = 'Supper property' } Supper.prototype.showSupperProp = function () { console.log(this.supProp) } //子类型 function Sub() { this.subProp = 'Sub property' } // 子类型的原型为父类型的一个实例对象 Sub.prototype = new Supper() // 让子类型的原型的constructor指向子类型 Sub.prototype.constructor = Sub Sub.prototype.showSubProp = function () { console.log(this.subProp) } var sub = new Sub() sub.showSupperProp() // sub.toString() sub.showSubProp() console.log(sub) // Sub
借用构造函数继承(假的)
套路
: 关键
: function Person(name, age) { this.name = name this.age = age}function Student(name, age, price) { Person.call(this, name, age) // 相当于: this.Person(name, age) /*this.name = name this.age = age*/ this.price = price}var s = new Student('Tom', 20, 14000)console.log(s.name, s.age, s.price)
原型链+借用构造函数的组合继承
function Person(name, age) { this.name = name this.age = age}Person.prototype.setName = function (name) { this.name = name}function Student(name, age, price) { Person.call(this, name, age) // 为了得到属性 this.price = price}Student.prototype = new Person() // 为了能看到父类型的方法Student.prototype.constructor = Student //修正constructor属性Student.prototype.setPrice = function (price) { this.price = price}var s = new Student('Tom', 24, 15000)s.setName('Bob')s.setPrice(16000)console.log(s.name, s.age, s.price)
进程
程序的一次执行
, 它占有一片独有的内存空间
线程
独立执行单元
完整流程
CPU
的最小基本调度单位
进程与线程
某个进程的某个线程上
至少有一个运行的线程: 主线程
,进程启动后自动创建
也可以同时运行多个线程
, 我们会说程序是多线程
运行的一个进程内的数据可以供其中的多个线程直接共享
多个进程之间的数据是不能直接共享的
线程池(thread pool)
: 保存多个线程对象的容器, 实现线程对象的反复利用
多进程运行
: 一应用程序可以同时启动多个实例运行多线程
: 在一个进程内, 同时有多个线程运行多线程
优点
缺点
单线程
优点
缺点
单进程
firefox
老版IE
多进程
chrome
新版IE
都是多线程
支持浏览器运行的最核心的程序
Chrome, Safari
: webkitfirefox
: GeckoIE
: Trident360,搜狗等国内浏览器
: Trident + webkit主线程
js引擎模块
: 负责js程序的编译与运行html,css文档解析模块
: 负责页面文本的解析dom/css模块
: 负责dom/css在内存中的相关处理布局和渲染模块
: 负责页面的布局和效果的绘制分线程
定时器模块
: 负责定时器的管理网络请求模块
: 负责服务器请求(常规/Ajax)DOM事件响应模块
: 负责事件的管理定时器真是定时执行的吗?
并不能保证真正定时执行
延迟一丁点(可以接受)
, 也有可能延迟很长时间(不能接受)
定时器回调函数是在分线程执行的吗?
主线程
执行的, js是单线程的
定时器是如何实现的?
事件循环模型
document.getElementById('btn').onclick = function () { var start = Date.now() console.log('启动定时器前...') setTimeout(function () { console.log('定时器执行了', Date.now()-start) }, 200) console.log('启动定时器后...') // 做一个长时间的工作 for (var i = 0; i < 1000000000; i++) { }}
如何证明js执行是单线程的?
主线程
执行的只有在运行栈中的代码全部执行完后才有可能执行
为什么js要用单线程模式, 而不用多线程模式?
代码的分类
js引擎执行代码的基本流程
先执行初始化代码
: 包含一些特别的代码 回调函数(异步执行) 后面在某个时刻才会执行回调代码
setTimeout(function () { console.log('timeout 2222') alert('22222222') // 当弹出111111 1秒后弹出2222222}, 2000)setTimeout(function () { console.log('timeout 1111') alert('1111111') // 当弹出-----1秒后弹出111111}, 1000)setTimeout(function () { console.log('timeout() 00000')}, 0)function fn() { console.log('fn()')}fn()console.log('alert()之前')alert('------') //暂停当前主线程的执行, 同时暂停计时, 点击确定后, 恢复程序执行和计时console.log('alert()之后')
所有代码分类
初始化执行代码(同步代码)
: 包含绑定dom事件监听, 设置定时器, 发送ajax请求的代码回调执行代码(异步代码)
: 处理回调逻辑js引擎执行代码的基本流程
: 初始化代码===>回调代码
重要组成部分
: 事件(定时器/DOM事件/Ajax)管理模块
回调队列
模型的运转流程
执行栈
浏览器内核
同一个: callback queue
任务队列
消息队列
事件队列
事件轮询
事件驱动模型
请求响应模型
function fn1() { console.log('fn1()')}fn1()document.getElementById('btn').onclick = function () { console.log('点击了btn')}setTimeout(function () { console.log('定时器执行了')}, 2000)function fn2() { console.log('fn2()')}fn2()
H5规范提供了js分线程的实现, 取名为: Web Workers
var onmessage =function (event){ //不能用函数声明 console.log('onMessage()22'); var upper = event.data.toUpperCase();//通过event.data获得发送来的数据 postMessage( upper );//将获取到的数据发送会主线程}
//创建一个Worker对象并向它传递将在新线程中执行的脚本的URLvar worker = new Worker("worker.js"); //接收worker传过来的数据函数worker.onmessage = function (event) { console.log(event.data); };//向worker发送数据worker.postMessage("hello world");
直接在主线程
使用Worker在分线程
转载地址:http://rdqwi.baihongyu.com/