目录
- JS高级
- 1、数据类型
-
- 1.1、分类
- 1.2、判断
- 1.3、类型对象和实例对象的区别
- 1.4、`undefined` 和 `null` 的区别
- 2、内存
-
- 2.1、数据
- 2.2、什么是内存
- 2.3、练习
- 3、函数
-
- 3.1、函数调用
- 3.2、回调函数
- 3.3、IIFE
- 4、this
- 5、分号问题
- 6、原型 `prototype`
-
- 6.1、什么是原型对象
- 6.2、显式原型属性和隐式原型属性
- 6.3、原型链
- 6.4、构造函数/原型/实体对象的关系
- 6.5、结论
- 6.6、原型链属性问题
- 6.7.instanceof
- 6.8、面试题
- 7、变量提升与函数提升
- 8、执行上文
-
- 8.1、代码分类
- 8.2、 全局执行上下文
- 8.3、函数执行上下文
- 8.4、执行上下文栈
- 8.5、面试题
- 9、作用域和作用域链
-
- 9.1、分类
- 9.2、作用
- 9.3、作用域与执行上下文的区别
- 9.4、面试题
- 10、循环遍历加监听
- 11、闭包
-
- 11.1、如何产生闭包
- 11.2、常见闭包
- 11.3、作用
- 11.4、生命周期
- 11.4、应用
- 11.5、闭包的缺点
- 11.6、面试题
- 12、对象创建模式
- 13、原型链继承
JS高级
1、数据类型
1.1、分类
- 基本类型
-
String
-
Number
-
boolean
-
undefined
-
null
-
- 对象类型
-
Object
-
Function
-
Array
-
1.2、判断
-
typeof
-
->null
object
-
->Array
object
- 其余都可以判断
-
-
判断对象的具体类型instanceof
-
===
-
==
var a;
console.log(a, typeof(a)); //undefined, 'undefined'
a = 'aitguigu';
console.log(typeof(a));// 'string'
a = null
console.log(typeof(a));// 'objcet'
1.3、类型对象和实例对象的区别
function Person(name, age) { // 构造函数 类型对象
this.name = name;
this.age = age;
}
var p = new Person(); // 根据类型创见的实例对象
1.4、 undefined
和 null
的区别
undefined
null
-
定义未赋值undefined
-
赋值并定义且值为null
null
2、内存
2.1、数据
- 特点:可传递、可运算
2.2、什么是内存
- 内存中存储两种数据:地址值、内部存储的数据
- 内存分类
- 堆:对象
- 栈:全局变量、局部变量、函数名
2.3、练习
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
3、函数
3.1、函数调用
- 直接调用
- 通过对象调用
- new 调用
- call() 和 apply() 可以让任意函数成为指定对象的方法
var obj = {};
function test() {
this.name = 'atguigu';
}
test.call(obj);
console.log(obj.name);// atguigu
3.2、回调函数
- 定义且没有调用并最终执行的函数
-
事件回调函数DOM
- 定时器回调函数
-
请求回调函数AJAX
- 生命周期回调函数
3.3、IIFE
- 隐藏实现
- 不会污染外部命名空间
- 可以编写JS模块
(function() {// 匿名函数自调用,向外暴露一个全局函数
var a = 1;
function test() {
console.log(a);
}
window.$ = function() {
return {
test: test
}
}
})();
$().test();
4、this
- 任何函数本质上都是通过某个对象来调用
- 所有函数内部都有一个变量
,值是调用函数的对象,如果没有指定,就是this
window
-
test()
window
-
p.test()
p
-
新创建的对象new test()
-
p.call(obj)
obj
function Person() {
getThis = function() {
console.log(this);
}
}
var p = new Person();
p.getThis(); // this 是p
var b = p.getThis;
b(); // this 是 window
5、分号问题
-
前要加分号()
-
前要加分号[]
- JS文件头部没有加分号,合并会导致问题
6、原型 prototype
prototype
6.1、什么是原型对象
- 每个函数都有一个
属性,它默认指向一个prototype
空对象,即原型对象Object
-
中有一个属性prototype
,指向函数对象constructor
- 空对象是指的没有自定义属性
-
console.log(Date.prototype, typeof Date.prototype)// Object
function fun(){}
fun.prototype.test = function(){}
console.log(fun.prototype) // Object
console.log(Date.prototype.constructor == Date) // true
6.2、显式原型属性和隐式原型属性
- 每个函数都有一个显式原型属性
prototype
- 每个实例对象都有一个隐式原型属性
proto
- 对象的隐式原型的值为其对应构造函数的显式原型的值
function Fn() { // 内部语句 Fn.prototype = {}
}
var fn = new Fn();// 内部语句 fn.__proto___ = Fn.prototype
console.log(fn.__proto__===Fn.prototype); // true
6.3、原型链
- 别名:隐式原型链
- 访问一个属性时
- 先从自身属性中查找,找到并返回
- 如果没有再沿着
这条链向上查找,找到并返回__proto__
- 如果最终没找到,返回
undefined
6.4、构造函数/原型/实体对象的关系
-
是一个函数对象,因此拥有Foo
属性,默认指向prototype
函数对象的原型对象Foo
-
同时也是一个实例对象,Foo
因此拥有var Foo = new Function();
属性,指向__proto__
函数对象的原型对象Function
-
是一个函数对象,因此拥有Function
属性与prototype
的Foo
属性都指向__proto__
函数对象的原型对象Function
-
同时也是一个实例对象,Function
因此拥有var Function = new Function();
属性,指向__proto__
函数的原型对象Function
-
是一个函数对象,因此拥有Object
属性,默认指向prototype
函数对象的原型对象Object
-
同时也是一个实例对象,Object
因此拥有var Object = new Function();
属性,指向__proto__
函数对象的原型对象Function
-
的原型对象是Function
的原型对象的实例Object
6.5、结论
- 结论1:函数的显式原型指向的对象默认是空
实例对象(Object
除外)Object
- 结论2:所有函数都是
的实例Function
- 结论3:
的原型对象是原型链的尽头Object
// 验证
console.log(Object.prototype instanceof Object) //false
console.log(Function.prototype instanceof Object) // true
console.log(Function.prototype === Function.__proto__) //true
console.log(Object.prototype.__proto__) // null
6.6、原型链属性问题
- 读取对象属性时会自动在原型链中查找
- 设置对象属性值时,不会自动查找原型链,如果当前对象中没有此属性,直接添加
- 方法一般定义在原型中,属性一般通过构造函数定义在对象本身上
// 读取对象属性时会自动在原型链中查找
function Fn() {}
Fn.prototype.a = 'xxx'
var fn = new Fn();
console.log(fn.a) // xxx
// 设置对象属性值时,不会自动查找原型链,如果当前对象中没有此属性,直接添加
var fn2 = new Fn()
fn2.a = 'yyy'
console.log(fn2.a, fn.a) // yyy, xxx
// 方法一般定义在原型中,属性一般通过构造函数定义在对象本身
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.setAge = function (age) {
this.age = age; // this这个值仍然是指向它原本属于的对象,而不是从原型链上找到它时,它所属于的对象。
}
var person = new Person('xs', 21);
person.setAge(22);
console.log(person);
var person2 = new Person('ws', 31);
person2.setAge(33);
console.log(person2);
console.log(person2.__proto__ === person.__proto__);// true
6.7.instanceof
- 表达式
A instanceof B
-
实例对象A
-
构造函数B
-
- 如果
函数的显式原型对象在B
对象的原型链上,返回A
,否则返回true
false
function Foo(){}
var f1 = new Foo()
console.log(f1 instanceof Foo)
console.log(f1 instanceof Object)
console.log(Object instanceof Function) // true
console.log(Object instanceof Object); // true
console.log(Function instanceof Function) // true
console.log(Function instanceof Object) // true
console.log(Object instanceof Foo) // false
6.8、面试题
// 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
var F = 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() // 报错
F.a() // a
F.b() // b
7、变量提升与函数提升
- 通过
定义(声明)的变量,在定义语句之前就可以访问的到,值为var
undefined
- 通过
声明的函数,在之前就可以调用,值为函数定义(对象)function
var a = 3
function fn() {
console.log(a)
var a = 4;
}
fn() // undefined
8、执行上文
8.1、代码分类
- 全局代码
- 函数代码(局部代码)
8.2、 全局执行上下文
- 在执行全局代码前,将
确定为全局执行上下文window
- 对全局数据进行预处理
-
定义的全局变量赋值为var
,添加为undefined
属性window
-
声明的全局函数赋值并添加为function
方法window
-
赋值为this
window
-
- 开始执行全局代码
8.3、函数执行上下文
- 在调用函数时,准备执行函数体之前,创建对应的函数执行上下文对象
- 对局部数据进行预处理
- 形参变量进行实参赋值添加为执行上下文的属性
-
实参列表赋值添加为执行上下文的属性arguments
-
定义的局部变量赋值为var
添加为上下文属性undefined
-
声明的函数赋值并添加为上下文属性function
-
赋值为调用函数对象this
- 开始执行函数体代码
8.4、执行上下文栈
- 在全局代码执行前,JS引擎会创建一个栈来存储管理所有的执行上下文对象
- 在全局执行上下文
确定后,将其添加到栈中(压栈)window
- 在函数执行上下文创建后,将其添加到栈中(压栈)
- 在当前函数执行完成后,将栈顶的对象移除(出栈)
- 当所有代码执行完后,栈中只剩下
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)
}
bar(10)
8.5、面试题
// 输出结果是什么 一共几个执行上下文对象 5个
console.log("global begin: " + i) // undefined
var i = 1
foo(1)
function foo(i) {
if (i == 4) return
console.log("foo begin: " + i) // 1 2 3
foo(i + 1)
console.log("foo end: " + i); // 3 2 1
}
console.log("global end: " + i); // 1
// 2
// 先执行变量提升,再执行函数提升
function a() {}
var a;
console.log(typeof a); // function
// 3
if (!(b in window)) {
var b = 1;
}
console.log(b) // undefined
// 4
var c = 1
function c(c) {
console.log(c)
}
c(2) // c is not a function
9、作用域和作用域链
- 静态的(相对于上下文对象),编写代码时候已经确定
9.1、分类
- 全局作用域
- 函数作用域
- 没有块作用域(ES6有了)
9.2、作用
- 隔离变量,不同作用域下同名变量不会有冲突
9.3、作用域与执行上下文的区别
- 创建
- 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定,而不是在函数调用时
- 全局上下文是在全局作用域确定后,JS代码马上执行前创建
- 函数上下文是在调用函数时,函数体代码执行前创建
- 性质
- 作用域是静态的,只要函数定义好就不会变化
- 执行上下文是动态的,调用函数时创建,函数结束后销毁
- 联系
- 执行上下文(对象)从属于所在作用域
- 全局上下文对应全局作用域,函数上下文对应函数作用域
9.4、面试题
// 1
var x = 10;
function fn() {
console.log(x);
}
function show(f) {
var x = 20;
f()
}
show(fn) // 10
// 2
var fn = function () {
console.log(fn)
}
fn() //函数
//3
var obj = {
fn2 : function () {
console.log(fn2)
}
}
obj.fn2() // Uncaught ReferenceError: fn2 is not defined
10、循环遍历加监听
var btns = document.getElementsByTagName('button');
// 写法1 错误
for(var i = 0, length = btns.length; i < length; i++) {
var btn = btns[i]
btn.onclick = function() {
alert(i + 1)
}
}
// i 是全局作用域
console.log(i)
// 写法2
for(var i = 0, length = btns.length; i < length; i++) {
var btn = btns[i]
btn.index = i
btn.onclick = function() {
alert(this.index + 1)
}
}
console.log(i)
// 写法3 利用了闭包
for(var i = 0, length = btns.length; i < length; i++) {
(function(i) {
var btn = btns[i]
btn.onclick = function() {
alert(i + 1)
}
})(i)
}
console.log(i)
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
11、闭包
11.1、如何产生闭包
- 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包
- 闭包是嵌套的内部函数
- 包含被引用变量(函数)的对象
- 注意:闭包存在于嵌套的内部函数
- 产生闭包的条件
- 函数嵌套
- 内部函数引用了外部函数的数据(变量/函数)
- 注意:
- 创建闭包是再调用外部函数时创建
- 参数和变量不会被垃圾回收机制回收
- 垃圾回收机制,函数没有被引用执行完以后这个函数的作用域就会被销毁,如果一个函数被其他变量引用,这个函数的作用域将不会被销毁(简单来说就是函数里面的变量会被保存下来,可以理解成全局变量)
function fn1() {
var a = 2
function fn2() {
console.log(a);
}
}
fn1()
11.2、常见闭包
- 将函数作为另外一个函数的返回值
- 将函数作为实参传递给另一个函数
function fn1() {
var a = 2
function fn2() {
a++;
console.log(a);
}
return fn2
}
var f = fn1();
f() // 3
f() // 4
function showDelay(msg, time) {
setTimeout(function () {
alert(msg)
}, time)
}
showDelay(1, 1000)
function fn1() {
var a = 2
function fn2() {
a++;
console.log(a);
}
function fn3() {
a--;
console.log(a);
}
return fn3
}
var f = fn1();
f() // 1
f() // 0
11.3、作用
- 延长了局部变量的生命周期,使用函数内部的变量在函数执行完后,仍存活在内存中
- 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
11.4、生命周期
- 产生:在嵌套内部函数定义执行时就产生了,不是在调用时
- 销毁:在嵌套的内部函数成为垃圾对象时
function fn1() {
var a = 2
function fn2() {
a++;
console.log(a);
}
return fn2
}
var f = fn1();
f = null
11.4、应用
- 定义JS模块
- 具有特定功能的JS文件
- 将所有的数据和功能都封装在一个函数中
- 只向外暴露一个包含n个方法的对象或函数
- 模块的使用者,只需要通过模块暴露的对象调用方法来实现对应功能
function myModel() {
var msg = 'aaa'
function doSomething() {
console.log('doSomething()' + msg.toUpperCase())
}
function doOtherthing() {
console.log('doOtherthing()')
}
return {
doSomething : doSomething,
doOtherthing : doOtherthing
}
}
<script type="text/javascript" src="modelDemo.js"></script>
<script>
var fn = myModel();
console.log(fn.doSomething()); // doSomething() AAA
console.log(fn.doOtherthing()); // undefined
</script>
- 方法2
(function (w) {
var msg = 'aaa'
function doSomething() {
console.log('doSomething()' + msg.toUpperCase())
}
function doOtherthing() {
console.log('doOtherthing()')
}
w.myModel = {
doSomething : doSomething,
doOtherthing : doOtherthing
}
})(window)
<script type="text/javascript" src="modelDemo2.js"></script>
<script>
var fn = window.myModel;
console.log(fn.doSomething());
console.log(fn.doOtherthing());
</script>
11.5、闭包的缺点
- 函数执行完后,函数的局部变量没有释放,占用内存时间会边长
- 容易造成内存泄露
- 意外的全局变量
// 1
function fn() {
a = 3
console.log(a)
}
fn()
// 2
var a = setInterval(function() {
console.log('----')
}, 1000)
// clearInterval(a)
// 闭包
- 解决:
- 尽量避免闭包
- 及时释放
11.6、面试题
// 面试题 1
var name = 'The Window'
var object = {
name: 'myObject',
getNameFunc: function () {
return function () {
return this.name
}
}
}
alert(object.getNameFunc()()); // The Window
// 面试题 2
var name2 = 'The Window'
var object2 = {
name2: 'myObject',
getNameFunc: function() {
var that = this;
return function() {
return that.name2; // 闭包
}
}
}
alert(object2.getNameFunc()())// myObject
// 面试题 3
function fun(n, o) {
console.log(o);
return {
fun: function (m) {
return fun(m,n)
}
}
}
var a = fun(0);// undefined
a.fun(1); // 0
a.fun(2); // 0
a.fun(3); // 0
var b = fun(0).fun(1).fun(2).fun(3);//undefined,1,2,3
var c = fun(0).fun(1);// undefined, 1
c.fun(2); // 1
c.fun(3); // 1
12、对象创建模式
- 构造函数式创建
- 场景:初始时不确定对象内部数据
- 缺点:语句太多
- 字面量式创建
- 场景:初始时对象内部数据确定
- 缺点:如果创建多个对象,有重复代码
- 工厂模式创建
- 场景:需要创建多个对象
- 缺点:对象没有一个具体的类型,都是Object类型
- 自定义构造函数模式
- 场景:需要创建多个类型确定的对象
- 缺点:数据相同,浪费内存
- 构造函数 + 原型 的组合模式
- 场景:属性在函数中初始化,方法添加到原型上
- 缺点:需要创建多个类型确定的对象
// 1
var obj = new Object();
obj.name = 'Joke';
obj.age = '18';
// 2
var obj2 = {name: 'Tom', age: 12}
function createPerson(name, age) {
var obj = {
name: name,
age: age,
}
return obj;
}
// 3
var obj3 = createPerson('HHH', 22);
var obj4 = createPerson('GGG', 23);
// 4
function Person(name, age) {
this.name = name;
this.age = age;
}
// 5
Person.prototype.setName = function (name) {
this.name = name;
}
var person = new Person('AAA', 45);
console.log(obj);
console.log(obj2);
console.log(obj3);
console.log(obj4);
person.setName('张三');
console.log(person)
13、原型链继承
- 子类型的原型为父类型的实例
- 借用构造函数
- 在子类型构造函数中通用
调用父类型构造函数call()
- 在子类型构造函数中通用
- 原型链 + 借用构造函数的组合继承
- 利用原型链实现对父类型对象的方法继承
- 利用
借用父类型构建函数初始化相同属性super()
// 方式一
// 父类型
function Supper() {
this.supProp = 'supper'
}
Supper.prototype.showSupperProp = function () {
console.log("s >>> " + this.supProp);
}
// 子类型
function Sub() {
this.subProp = 'sub'
}
// Sub.prototype = new Supper()
Sub.prototype.showSubProp = function () {
console.log(this.subProp);
}
var sub = new Sub();
sub.showSupperProp() // Uncaught TypeError: sub.showSupperProp is not a function
// 方式二
function Person(name, age) {
this.name = name;
this.age = age;
}
function Student(name, age, price) {
Person.call(this, name, age)
this.price = price;
}
var student = new Student('zs', 23, 299); // Student {name: 'zs', age: 23, price: 299}
// 方法三
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.constructor = Student;
Student.prototype.setPrice = function (price) {
this.price = price;
}
var student = new Student('zs', 29, 30);
student.setName('sss');
console.log(student); // Student {name: 'sss', age: 29, price: 30}