1 基础总结
01 数据类型
基本类型:
String: 任意的字符串
Number: 任意的数字
boolean: true / false
undefined: undefined
null: null
Symbol: 不能当做构造函数使用,不能使用 new 关键字。可以用来表示对象的唯一键值
BigInt: 不能当做构造函数使用,不能使用 new 关键字。可以表示任意大的整数。
对象类型
Object: 任意的对象
Array: 一种特殊的对象 (内部数据是有序的)
function: 一种特殊的对象 (可以执行)
判断:
typeof
instanceof
=== : 全等于 / 注意尽量不要用 == 因为会做数据转换
02 undefined 与 null
- undefined 与 null 的区别?
undefined: 是定义了但没赋值
null: 定义了并赋值为 null
- 什么时候会给变量赋值为null?
初始赋值时, 表明将要为变量 a 赋值为对象 (相当于是为对象占位)
结束前, 将变量 a 赋值为 null 使之前对象失去引用变为垃圾对象 (被处理机制回收)
03 变量与内存
- 问题: var a = xxx , a内存中保存的是什么?
- 如果 xxx 是基本数据 如 3 , a内存中保存的是这个基本数据 3
- 如果 xxx 是对象数据 , a内存中保存的是那个对象的地址值
04 对象
- 什么是对象?
对象是多个数据的封装体
一个对象代表现实中的一个事物
- 为什么要用对象?
统一管理多个数据
- 对象的组成?
- 属性: 属性名(字符串)和属性值(任意)组成
- 方法: 一种特别的属性(属性值是函数)
- 如何访问对象内部的数据?
- .属性名: 编码简单, 有事不能用
- ['属性名']: 编码麻烦, 能通用
var p = {
'name': 'Tom',
age: 13,
setName: function (name) {
this.name = name
},
setAge: function (age) {
this.age = age
}
}
p.setName('Amy')
console.log(p.age, p['age'], p['name']); // 13 13 "Amy"
- 什么时候必须使用
['属性名']
的形式?
1. 属性名包含特殊字符时: - 空格
- 给对象p添加一个属性: content type: text/json 此时p.content type = 'text/json' //不可用
2. 属性名不确定时
// 属性名包含特殊字符时: - 空格
var p = {
'content type': 'text/json'
}
console.log(p['content type']); // 'text/json'
// 属性名不确定时
var proName = 'myAge'
var value = 18
//此时 p.proName = value //不能用 用了p中键值对为: 'proName': 18
p[proName] = value // 注意是proName变量 而不是字符串
console.log(p.[proName]); //
05 函数
01 回调函数
- 什么叫回调函数?
- 定义了 但没有调用 他自己执行了
- 常见的回调函数有哪些?
- Dom事件回调函数
- 定时器回调函数
- ajax 请求回调函数
- 生命周期回调函数
document.getElementById('btn').onclick = function () {
alert(this.innerHTML)
}
setTimeout(function () {
alert('到点了')
}, 2000)
02 匿名函数自调用
作用:
- 隐藏实现
- 不会污染外部(全局)的命名空间
// 简单实现
var a = 18
;(function () {
var a = 14
console.log(a);
})()
console.log(a)
;(function () {
var age = 17
function test () {
console.log(age);
}
window.$ = function () {
return {
test: test
}
}
})()
$().test()
console.log($());
此处 $ 是个函数 , $() 为执行 结果得到一个 对象 {} , 对象中拥有 test 方法
03 函数中的 this
- this是什么?
- 任何函数的本质上都是通过某个对象来调用的, 如果没有直接指定那this就是 window
- 所有的函数内部都有一个变量 this
- 值是调用函数的当前对象
- 如何确定this的值?
- test(): window
- p.test(): p
- var p = new test(): p
- p.call(obj): obj
function Person (color) {
console.log(this)
this.getColor = function () {
console.log(this)
}
}
Person('red') // this: window
var p = new Person("yello") // this: p
var obj = {}
p.getColor.call(obj, "pink") // this: obj
var test = p.getColor
test() // this: window
function fun1() {
console.log(this)
function fun2() {
console.log(this)
}
fun2() // this: window
}
fun1() // this: window
2 函数高级
01 原型与原型链
01 原型 prototype
- 函数的prototype属性
- 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
- 原型对象中有一个属性constructor, 它指向函数对象
- 给原型对象添加属性(一般是方法)
- 作用: 函数的所有实例对象自动拥有原形中的属性(方法)
// 每个函数都有一个prototype属性, 它默认默认指向一个Object空对象(即称为: 原型对象)
console.log(Date.prototype)
function Fun() {}
console.log(Fun.prototype) // 默认指向一个空的对象(没有我们自己的属性)
// 原型对象中有一个属性constructor, 它指向函数对象
console.log(Fun.prototype.constructor === Fun)
console.log(Date.prototype.constructor === Date)
// 给原型对象添加属性(一般是方法) ====> 实例对象可以访问
Fun.prototype.test = function () {
console.log('test')
}
var fun = new Fun()
fun.test()
02 显式原型与隐式原型
1. 每个函数function都有一个prototype, 即显式原型(属性)
2. 每个实例对象都有一个__proto__, 即隐式原型(属性)
3. 实例对象的隐式原型的值等于构造函数的显式原型的值, 即 prototype 与 __proto__地址值相同
4. 函数的prototype属性: 在定义函数时自动添加的, 默认值为一个空的Object对象的地址值
5. 实例对象的__proto__属性: 创建实例对象时自动添加的, 默认值为构造函数的prototype属性值
画图:
03 原型链
- 原型链
别名:(隐式原型链), 作用: 查找对象的属性或方法
- 访问一个对象的属性时:
- 先在自身属性查找, 找到返回
- 如果没有,再沿着__proto__这条链找, 找到返回
- 如果最终找不到, 返回undefined
画图:
04 构造函数 原型对象与 实例对象关系图
05 面试题
02 变量提升与函数提升
在 JavaScript 中函数是一等公民, 函数提升的优先级比变量提升的优先级高
- 变量提升
- 通过var定义(声明)的变量, 在定义语句之前就可以访问到
- 值: undefined
- 函数提升
- 通过function声明的函数, 在之前就可以直接调用
- 值: 函数对象
/**
* 面试题
* */
var a = 3
function fn() {
console.log(a); // 变量提升 undefined
var a = 4
}
fn()
xxx() // 可调用 函数提升
function xxx() {
console.log('你好');
}
obj() // 不能调用 变量提升 值: undefined // obj is not a function
var obj = function () {
console.log('obj');
}
03 执行上下文与执行上下文栈
01 执行上下文
- 代码分类 (位置)
- 全局代码
- 函数(局部)代码
- 全局执行上下文
1. 在执行全局代码之前将 window 确定为全局执行上下文对象
2. 对全局代码进行预处理
- 将 var 定义的全局变量赋值为 undefined, 添加为 window 的属性
- 将 function 声明的全局函数 赋值 , 添加为 window 的方法
- 将 this 赋值为 window
3. 开始执行全局代码
- 函数执行上下文
1. 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象
2. 对局部代码进行预处理
- 形参变量==> 赋值(实参)==>添加为执行上下文的属性
- arguments==> 赋值(实参列表), 添加为执行上下文的属性
- var定义的局部变量==> undefined, 添加为执行上下文的属性
- function声明的函数==> 赋值(fun), 添加为执行上下文的方法
- this==> 赋值(调用函数的对象)
3. 开始执行函数体代码
/*全局执行上下文*/
console.log(a1, window.a1) // undefined undefined
window.a2() // 'a2()'
console.log(this) // window
var a1 = 3
function a2() {
console.log('a2()');
}
/*函数执行上下文*/
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, 4)
02 执行上下文栈
1. 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
2. 在全局执行上下文( window )确定后, 将其压入栈中(入栈)
3. 在函数执行上下文创建后将其压入栈中(入栈)
4. 在当前函数执行完成后将栈顶的上下文对象移除(出栈)
5. 当所有的代码执行完成后, 栈中只剩下 window
03 面试题
// 上方函数解析之后得到下方函数
console.log('最开始 i 值:' + i) // undefined
var i = 1
foo(i)
function foo(i) {
if (i === 4) {
return
}
console.log('foo()调用前 i 值:' + i)
foo(i + 1) // 递归调用: 在函数内部自己调用自己
console.log('foo()调用后 i 值:' + i)
}
-------------------------------------------------------------
function foo(i) {
if (i === 4) {
return
}
console.log('foo()调用前 i 值:' + i) // 1
// foo(i + 1) // 递归调用: 在函数内部自己调用自己
function foo(2) {
if (2 === 4) {
return
}
console.log('foo()调用前 i 值:' + i) // 2
// foo(2 + 1) // 递归调用: 在函数内部自己调用自己
function foo(3) {
if (3 === 4) {
return
}
console.log('foo()调用前 i 值:' + i) // 3
// foo(3 + 1) // 递归调用: 在函数内部自己调用自己
function foo(4) {
if (4 === 4) {
return
}
}
console.log('foo()调用后 i 值:' + i) // 3
}
console.log('foo()调用后 i 值:' + i) // 2
}
console.log('foo()调用后 i 值:' + i) // 1
}
// JavaScript中 函数是一等公民 先执行函数提升 然后执行 变量提升
// 面试题 1
console.log(typeof a) // function
function a() {}
var a
// 面试题 2
console.log(typeof a) // function
function a() {}
var a = 1
// 面试题 3
function a() {}
var a = 1
console.log(typeof a) // number
/*
面试题 4
* */
if (!(b in window)) { // === var b
var b = 1
}
console.log(b) // undefined
/*
面试题 5
* */
var c = 1
function c(c) {
console.log(c)
}
c(2) // c is not a function
/*
上式等价于
*/
var c
function c(c) {
console.log(c)
}
c = 1
c()
04 作用域面试题
作用域分为: 全局作用域 和 函数作用域
找不到就会向上一层查找, 所以 x 值为: 10
var fn = function() {
console.log(fn) // ƒ () { console.log(fn) }
}
fn()
var obj = {
fn2: function() {
// console.log(fn2)
console.log(this.fn2)
console.log(obj.fn2)
console.log(window.obj.fn2)
}
}
obj.fn2()
04 闭包
- 如何产生闭包?
- 当一个内部子函数引用了外部父函数的变量(函数)时, 就产生了闭包
- 闭包到底是什么?
- 使用chrome调试查看: ==>Sources ==>打断点 ==>Scope ==>Local ==>[[Scopes]] ==>Closure(闭包)
- 理解一: 闭包是嵌套的内部函数(绝大数人)
- 理解二: 闭包是包含被引用变量(函数)的对象(极少数人)
- 注意: 闭包存在于嵌套的内部函数中
- 产生闭包的条件?
- 函数嵌套
- 内部函数引用了外部函数的数据(变量/函数)
- 闭包的作用
- 使用闭包函数执行完成后,内部函数引用外部函数的数据(变量/函数),还存在于内存中
- 使函数外部可以操作(读写)到函数内部的数据(变量/函数)
- 闭包的生命周期
1. 产生: 在内部嵌套函数定义执行(不是真实执行)完成时就产生了(不是在调用)
2. 死亡: 在内部嵌套函数成为垃圾对象时
function fn1() {
// 程序执行到此处时, 先函数提升然后变量提升(内部函数对象创建) 闭包出现
// 因为有 var a = undefined
var a = 2
function fun() {
console.log(++a)
}
return fun
}
var f = fn1()
f() // 3
f() // 4
// 此时没有死亡
f = null // 此时内部函数无引用指向 死亡
- 闭包的应用_定义js模块
- 具有特定功能的js文件
- 将所有的数据和方法都封装到一个函数的内部(私有的)
- 只向外部暴露一个包含n个方法的对象或函数
- 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
my_module.js模块 方法二更好*
/*方法一*/
function my_module() {
var msg = 'songRuiXue'
function doSomething() {
console.log('doSomething' + msg.toUpperCase()) // msg: 全部大写
}
function doOtherthing() {
console.log('doOtherthing' + msg.toLowerCase()) // msg: 全部小写
}
return {
doSomething: doSomething,
doOtherthing: doOtherthing
}
}
/*方法二*/
(function(window) {
var msg = 'songRuiXue'
function doSomething() {
console.log('doSomething' + msg.toUpperCase()) // msg: 全部大写
}
function doOtherthing() {
console.log('doOtherthing' + msg.toLowerCase()) // msg: 全部小写
}
window.obj = {
doSomething: doSomething,
doOtherthing: doOtherthing
}
})(window)
调用
/*方法一*/
var obj = my_module()
obj.doSomething()
obj.doOtherthing()
/*方法二*/
obj.doSomething()
obj.doOtherthing()
- 内存溢出与内存泄漏
- 内存溢出
- 一种程序运行时出现的错误
- 当程序运行需要的内存超过了剩余的内存时, 就会抛出内存溢出的错误
- 内存泄漏
- 占用的内存没有及时的释放
- 内存泄漏过多就容易导致内存溢出
- 常见的内存泄漏:
1. 意外的全局变量(没有用var定义的变量)
2. 没有及时清理的定时器或回调函数
3. 闭包
例子
- 内存溢出
/*意外的全局变量*/
function fn() {
a = new Array(100000)
*a没有用var定义*
console.log(a)
}
fn()
/*开启循环定时器后不清理*/
var intervalId = setInterval(function() {
console.log('1111111111')
}, 2000)
// clearInterval(intervalId) 清理循环定时器
/*闭包*/
function fn1() {
var a = 4
function fn2() {
console.log(++a)
}
return fn2
}
var f = fn1()
f()
// f = null 使fn2函数失去引用成为垃圾对象
面试题
05 对象高级
01 对象的创建模式
- Object构造函数模式
var obj = new Object() // var obj = {} obj.name = 'Tom' obj.setAge = function(age) { this.age = age // this 指向 obj } obj.setAge(12) console.log(obj);
- 对象字面量模式
var obj = { name: 'Tom', age: 12, setName: function (name) { this.name = name // obj调用方法 所以 this 指向 obj } } obj.setName('Amy') console.log(obj);
- 构造函数模式
function Person(name, age) { this.name = name // this 指向 new 出来的实例对象 this.age = age this.setName = function(name) { this.name = name // this 指向 new 出来的实例对象 } } var p = new Person('Tom', 12) console.log(p);
- 构造函数+原型的组合模式
- 将实例对象共有的方法或属性添加到构造函数的原型对象上( 优化代码 )
function Person(name, age) { this.name = name this.age = age // this 指向 new 出来的实例对象 } Person.prototype.setName = function(name) { this.name = name // this 指向 new 出来的实例对象 } var p = new Person('Tom', 12) p.setName('Amy') console.log(p);
02 js中的继承
在ES6之前, 需要借助于构造函数+原型对象实现继承, 现在我们可以利用ES6的extends方法实现继承
01 call函数的使用
fun.call(this, name, age, price)
this: 指向的是当前调用函数的对象
name, age, price: 传入的是一个参数列表,而不是单个数组。
function son() {
console.log(this) // this 指向 window
}
son.call() // this 指向 window
var obj = {}
son.call(obj) // this 指向 obj
02 借用构造函数继承父类型属性
核心思想:通过call()把父类型的this指向子类型的this,这样就可以实现子类型继承父类型的属性。
// 借用父构造函数继承属性
//父构造函数
function Father (name, age) {
//this指向父构造函数的对象实例
this.name = name
this.age = age
}
// 子构造函数
function Son (name, age){
// this指向子构造函数的实例对象
//借助于call,this指向子构造函数实例对象 new Son('Amy',18)
Father.call(this, name, age)
}
var son = new Son('Amy',18)
console.log(son)
3 借用原型对象继承父类型方法
// 父构造函数
function Father (uname, age) {
//this指向父构造函数的对象实例
this.uname = uname
this.age = age
}
// 父原型方法
Father.prototype.money = function() {
console.log(10000)
}
// 子构造函数
function Son (uname, age){
// this指向子构造函数的实例对象
//借助于call,this指向子构造函数的实例对象
Father.call(this, uname, age)
}
// Son.prototype = Father.prototype 这样直接赋值会有问题,如果修改了子原型对象,
//父原型对象也会变化
// 将父级构造函数的实例对象赋值给子级的原型
Son.prototype = new Father()
//手动改constructor指回原来的构造函数
Son.prototype.constructor = Son
Son.prototype.get= function() {
console.log('----')
}
var son = new Son('Amy',18)
console.log(son)
06 函数重载
重载(overload)
什么是: 多个同名的函数,但是形参列表不同,在调用时,可自动根据传入实参列表的不同,选择匹配的函数执行
问题: js不支持其他语言那种重载的语法: 因为js不允许多个同名函数同时存在!如果同时定义多个同名函数,则最后一个同名函数,会覆盖之前所有同名函数!
解决: 借助于arguments对象 来变通实现, 只定义一个函数! 且不要定义形参! 每个函数内都有一个arguments对象:
什么是arguments: 专门接受传入函数的所有实参值的类数组对象(长得像数组的对象)
相同: 下标, length 不同: 不是数组类型,而是对象类型,所以类数组对象无法使用数组类型的函数!
何时使用: 今后在js中,如果一个函数的实参值个数不确定,就必须用arguments代替形参列表!
//定义付款函数,支持手机支付,现金支付,刷卡支付
function fn(){
//如果没有传入实参值
if(arguments.length==0){
console.log("足球")
}else if(arguments.length==1){//否则如果只传入一个实参值
console.log(`1`)
}else{//否则
console.log(`刷卡支付,从您卡号${arguments[0]}中扣款成功!`)
}
}
//想手机支付:
fn();
//想现金支付:
fn(100);
//想刷卡支付:
fn("6553 1234","123456");
原生函数中重载的例子: arr.splice()
- 删除元素: arr.splice(i, 几个)
- 插入新元素: arr.splice(i, 0, 新值1, 新值2,…)
- 替换现有元素: arr.splice(i, 几个, 新值1, 新值2,…)
07 函数提升与变量提升原型链综合面试题
function Foo() {
getName = function() {
console.log(1)
}
return this
}
Foo.getName = function() {
console.log(2)
}
Foo.prototype.getName = function() {
console.log(3)
}
function getName() {
console.log(5)
}
var getName = function() {
console.log(4)
}
Foo.getName() // 2
getName() // 4 // JavaScript中函数是一等公民, 函数提升比变量提升的优先级高
console.log(Foo())
Foo().getName() // 1 Foo()返回 this 指向 window, 就相当于 this.getName() => window.getName()
getName() // 1
new Foo.getName() // 2
new Foo().getName() // 3
new new Foo().getName() // 3