文章目录
原型与原型链
原型
1. 函数的prototype属性
每个函数都有一个prototype属性,他默认指向一个Object空对象(即称为:原型对象)
原型对象中有一个属性constructor,它指向函数对象
2. 给原型对象添加属性(一般都是方法)
作用函数的所有实例对象自动拥有原型中的属性(方法)
console.log(Date.prototype,typeof Date.prototype);
function Fun() {
}
console.log(Fun.prototype); // 默认指向一个Object空对象(没有我们的属性)
console.log(Fun.prototype);
// 原型对象中有一个属性constructor,它指向函数对象
console.log(Date.prototype.constructor===Date);
console.log(Fun.prototype.constructor===Fun);
// 给原型对象添加属性(一般是方法) ===>实例对象可以访问
Fun.prototype.test = function() {
console.log('test()');
}
var fun = new Fun();
fun.test()
显示原型与隐式原型
1. 每个函数function都有一个prototype,即显式原型(属性)
2. 每个实例对象都有一个__proto__,可称为隐式原型(属性)
3. 对象的隐式原型的值为其对应构造函数的显示原型的值
4. 内存解构
5. 总结:
- 函数的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();
原型链
1. 原型链(图解)
1. 访问一个对象的属性时,
先在自身属性中查找,找到返回
如果没有,再沿着__proto__这条链向上查找,找到返回
如果最终没找到,返回undefined
别名:隐式原型链
作用:查找对象的属性(方法)2. 构造函数/原型/实体对象的关系(图解)
3. 构造函数/原型/实体对象的关系2(图解)
function Fn() {
this.test1 = function() {
console.log('test1()');
}
}
Fn.prototype.test2 = function() {
console.log('test2()');
}
var fn = new Fn();
fn.test1();
fn.test2();
console.log(fn.toString());
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); // true
3. Objct的原型对象是原型链尽头
console.log(Object.prototype.__proto__); // null
原型链_属性问题
1. 读取对象的属性值时:会自动到原型链中查找
2. 设置对象的属性值时:不会查找原型链,如果当前对象中没有此属性,直接添加此属性并设置其值
3. 方法一般定义在原型中,属性一般通过构造函数定义在对象本身上
本节代码:
function Fn() {}
Fn.prototype.a = "xxx";
var fn1 = new Fn();
console.log(fn1.a, fn1);
var fn2 = new Fn();
fn2.a = "yyy";
console.log(fn1.a, fn2.a, fn2);
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("煌上煌");
/* [[prototype]]和__proto__意义相同,均表示对象的内部属性,其值指向对象原型。
前者在一些书籍、规范中表示一个对象的原型属性,后者则是在浏览器实现中指向对象原型。*/
console.log(p1);
var p2 = new Person("Jack", 12); // 属性在对象自身身上
p2.setName("Cat");
console.log(p2);
// p1 和 p2 的隐式原型相等 原因是:实例对象的隐式原型等于构造函数的显示原型
console.log(p1.__proto__ === p2.__proto__); // true
探索instanceof
1. instanceof 是如何判断的?
表达式: A instanceof B
如果B函数的显式原型对象在A对象的原型链上,返回true,否则返回false
2.Function是通过new自己产生的实例
本节代码:
/*
案例1
*/
// 函数的原型对象是个空的Object实例对象
function Foo() {}; // 创建一个函数
var f1 = new Foo(); // 创建一个函数的实例化对象
console.log(f1 instanceof Foo); // true 判断 Foo 的显式原型对象是否在 f1 的原型链上
console.log(f1 instanceof Object); // true
/*
案例2
*/
console.log(Object instanceof Function); // true
console.log(Object instanceof Object);
console.log(Function instanceof Function);
console.log(Function instanceof Object);
function Foo() {}
console.log(Object instanceof Foo);
执行上下文与执行上下文栈
变量提升与函数提升
1. 变量声明提升
通过var定义(声明)的变量,在定义语句之前可以访问到
值:undfined
2. 函数声明提升
通过function声明的函数,在之前就可以直接调用
值:函数定义(对象)
3. 问题:变量提升和函数提升是如何产生的?
执行上下文
1. 代码分类(位置)
全局代码
函数(局部)代码
2. 全局执行上下文
在执行全局代码前将window确定为全局执行上下文
对全局数据进行预处理
- var定义的全局变量==>undefined,添加为window的属性
- function声明的全局函数==>赋值(fun),添加为window的方法
- this==>赋值(window)
开始执行全局代码
3. 函数执行上下文
在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象(虚拟的,存在于栈中)
对局部数据进行预处理
- 形参变量==>赋值(参数)==>添加为执行上下文的属性
- argyments==>赋值(实参列表),添加为执行上下文的属性
- var定义的局部变量==>undefined,添加为执行上下文的属性
- function声明的函数==>赋值(fun),添加为执行上下文的方法
- this==>赋值(调用函数的对象)
开始执行函数体代码
本节代码:
// 全局执行上下文
console.log(a1===window.a1);
a2();
console.log(a2()===window.a2());
console.log(this);
var a1 = 3;
function a2() {
console.log('a2()');
}
console.log(a1);
// 函数执行上下文
function fn(a1) {
console.log(a1); // 2
console.log(a2); // undefined
a3(); // a3()
console.log(this); // window
// arguments形参列表
console.log(arguments); // 伪数组(2,3)
var a2 = 3;
function a3() {
console.log('a3()');
}
}
fn(2,3)
fn(1);
执行上下文栈
- 在全局代码执行前,JS引擎就会创建一个栈来存储管理所有的执行上下文对象
- 在全局执行上下文(window)确定后,将其添加到栈中(压栈)
- 在函数执行上下文创建后,将其添加到栈中(压栈)
- 在当前函数执行完后,将栈顶的对象移除(出栈)
- 当所有的代码执行完后,栈中只剩下window
执行上下文栈问题
1. 依次输出什么?
gb:undefined
fb:1
fb:2
fb:3
fe:3
fe:2
fe:1
ge:1
2. 整个过程中产生了几个执行上下文?
5
本节代码:
console.log("gb:" + i);
var i = 1;
foo(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 a;
console.log(typeof a); // 'function'
/*
测试题2
*/
if (!(b in window)) {
var b = 1;
}
console.log(b); // undefined
/*
面试题3
*/
var c = 1;
function c(c) {
console.log(c);
}
c(2); // 报错 函数提升优先级高于变量提升,且不会被同名变量声明时覆盖,但是会被变量赋值后覆盖。
作用域与作用域链
作用域
1. 理解
就是一块"地盘",一个代码段躲在的地域
它是静态的(相对于上下文对象),在编写代码时就确定了
2. 分类
全局作用域
函数作用域
没有块作用域(ES6有了)
3. 作用
隔离变量,不同作用域下同名变量不会有冲突
本节代码:
var a = 10,
b = 20;
function 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(200);
}
fn(10);
作用域与执行上下文
1. 区别1
全局作用域之外,每个函数都会创建自己的作用域(n+1),作用域在函数定义时就已经确定了。而不是在函数调用时
全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
函数执行上下文环境是在调用函数时, 函数体代码执行之前创建
2. 区别2
作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
上下文环境是动态的, 调用函数时创建, 函数调用结束时上下文环境就会被自动释放释放
3. 联系
执行上下文环境(对象)是从属于所在的作用域
全局上下文环境==>全局作用域
函数上下文环境==>对应的函数使用域
本节代码:
var a = 10,
b = 20;
function 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(200);
}
fn(10);
/*
问题:
1. 有几个作用域?
2. 产生过几个上下文环境对象?
*/
作用域链
1. 理解
多个上下级关系的作用域形成的链,它的方法是从下向上的(从内到外)
查找变量时就是沿着作用域链来查找的
2. 查找一个变量的查找规则
在当前作用域下的执行上下文中查找对应的属性,如果有直接返回,否则进入2
在上一级作用域的执行上下文中查找对应的属性,如果有直接返回,否则进入3
再次执行2的相同操作,直到全局作用域,如果还找不到就抛出找不到的异常
闭包
1. 如何产生闭包
当一个嵌套的内部(子)函数用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包
2. 闭包到底是什么?
使用chrome调用查看
理解一:闭包是嵌套的内部函数(绝大部分人)
理解二:包含被引用变量(函数)的对象(极少数人)
注意:闭包存在于嵌套的内部函数中
3. 产生闭包的条件?
函数嵌套
内部函数引用了外部函数的数据(变量/函数)
常见的闭包
1. 将函数作为另一个函数的返回值
2. 将函数作为实参传递给另一个函数调用
本节代码:
function fn1() {
// 1. 将函数作为另一个函数的返回值
// 没有闭包的话,执行完fn1变量a就释放了,程序将会报错
var a = 2;
function fn2() {
a++;
console.log(a);
}
return fn2;
}
var f = fn1();
f(); // 3
f(); // 4
// 2. 将函数作为实参传递给另一个函数调用
// 闭包条件: 有内部和外部函数,且内部函数引用了外部函数
function showDelay(msg,time) {
setTimeout(function(){
alert(msg);
},time)
}
showDelay('函数',2000);
闭包的作用
1. 使用函数内部的变量在函数执行后,仍然存活在内存中(延长了局部变量的声明周期)
2. 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
问题:
1. 函数执行完后,函数内部声明的局部变量是否还存在?
- 一般不存在,存在于闭包中的变量才会可能存在
2. 在函数外部能直接访问函数内部的局部变量吗?
- 不能,但我们可以通过闭包让外部操作它
本节代码:
function fn1() {
var a = 2;
function fn2() {
a++;
console.log(a);
}
function fn3() {
a--;
console.log(a);
}
return fn3;
}
var f = fn1();
f(); // 3
f(); // 4
闭包的生命周期
- 产生:在嵌套内部函数定义执行完时就产生了(不是在调用)
- 死亡:在嵌套的内部函数成为垃圾对象时
本节代码:
// 此时闭包就已经产生了(函数提升,内部函数对象已经创建了)
var a = 2;
function fn2() { // 闭包执行完毕
a++;
console.log(a);
}
// var fn2 = function() { // 定义完毕执行才算完毕
// a++;
// console.log(a);
// } // 闭包执行完毕
return fn2;
}
var f = fn1();
f(); // 3
f(); // 4
f = null; // 闭包死亡(包含闭包的函数对象称为垃圾对象)
闭包应用_自定义JS模块化
闭包的应用2 : 定义JS模块
具有特定功能的js文件
将所有的数据和功能都封装在一个函数内部(私有的)
只向外暴露一个包含n个方法的对象或函数
模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能
本节代码:
function myModule() {
// 私有数据
var msg = 'My Nice boy';
// 操作数据的函数
function doSomething() {
console.log('doSomething' + msg.toUpperCase()); // msg.toUpperCase() 将msg的字符都转为大写
}
function doOtherthing() {
console.log("doDtherthing" + msg.toLowerCase()); // msg.toLowerCase 将msg的字符都转为小写
}
// 向外暴露对象(给外部使用的方法)
return {
doSomething: doSomething,
doOtherthing: doOtherthing
}
}
闭包的缺点及解决
- 缺点
函数执行完后,函数内的局部变量没有释放,占用内存时间会变长
容易造成内存泄漏
- 解决
能不用闭包就不用
及时释放
内存溢出与内存泄漏
1. 内存溢出
一种程序运行出现的错误
当程序运行需要的内存超过了剩余的内存时,就抛出内存溢出的错误
2. 内存泄漏
占用的内存没有及时释放
内存泄漏积累多了就容易导致内存溢出
常见的内存泄漏
- 意外的全局变量
- 没有及时清理的计时器或回调函数
- 闭包
// 1.内存溢出
var obj = {};
for (var i = 0; i < 10000; i++) {
obj[i] = new Array(100000000);
}
// 2. 内存泄漏
// 意外的全局变量
function fn() {
a = 3;
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; // 释放内存