JavaScript笔记 10:高级2(函数高级)

原型与原型链

1. 函数的prototype属性

每个函数都有一个prototype属性,它默认指向一个Object空对象(即原型对象)

function f1() {}
// 默认指向一个空对象
// 空对象的含义是指没有我们定义的属性
console.log(f1.prototype); // object 

原型对象中有一个属性constructor,它指向函数对象

function f1() {}
console.log(f1.prototype.constructor === f1); // true

给原型对象添加属性(一般都是方法)

function F1() {}
// 给原型对象添加属性(一般是方法) ==> 实例对象可以访问
F1.prototype.test = function () {
    console.log("dudu === test()");
}

作用:函数的所有实例对象自动拥有原型中的属性(方法)

// 创建实例对象
var f1 = new F1();
// 调用方法
f1.test();// dudu === test()
2. 显式原型与隐式原型

每个函数都有一个prototype,即为显式原型,默认指向一个空的Object对象

function F1() {}
console.log(F1.prototype);

每个实例对象都有一个__proto__,可称为隐式原型,也指向一个空的Object对象

var f1 = new F1();
console.log(f1.__proto__);

对象的隐式原型的值为其对应构造函数的显示原型的值

console.log(F1.prototype === f1.__proto__); // true

总结:

  • 函数的 prototype 属性:是在定义函数时自动添加的,默认值是一个空Object对象
  • 对象的 __proto__ 属性:创建对象时自动添加的,默认值为构造函数的 prototype 属性值
  • 程序员可以直接操作显式原型,但不能直接操作隐式原型(ES6之前)
3. 原型链

function F1() {
  this.test1 = function () {
    console.log("test1()");
  };
}
F1.prototype.test2 = function () {
  console.log("test2()");
};

访问一个对象的属性时,先在自身属性中查找,找到返回

var f1 = new F1();
f1.test1();// test1()

如果没有,再沿着 __proto__ 这条链向上查找,找到返回

f1.test2();// test2()
console.log(f1.toString()); 

如果最终没找到,返回 undefined

console.log(Object.prototype.__proto__); // null 原型链的尽头
console.log(f1.test3);// undefined
f1.test3(); // 报错:f1.test3 is not a function

  • 别名:隐式原型链
  • 作用:查找对象的属性(方法)

函数的显式原型指向的对象默认是空 Object 实例对象(但 Object 不满足)

console.log(F1.prototype instanceof Object); // true
console.log(Function.prototype instanceof Object); // true
console.log(Object.prototype instanceof Object); // false

所有函数都是 Function 的实例,包括它本身

console.log(F1.__proto__ === Function.prototype); // true
console.log(Function.__proto__ === Function.prototype); // true

Object 的原型对象是原型链的尽头

console.log(Object.prototype.__proto__); // null

function F1() {}
F1.prototype.a = 'dudu';

读取对象的属性值时,会自动到原型链中查找

var f1 = new F1();
console.log(f1.a); // dudu

设置对象的属性值时,不会查找原型链,如果当前对象中没有此属性,直接添加此属性并设置其值

var f2 = new F1();
f2.a = 'haha';
console.log(f1.a); // dudu
console.log(f2.a); // haha

方法一般定义在原型中,属性一般通过构造函数定义在对象本身上

function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.setName = function (name) {
  this.name = name;
}
var p1 = new Person('dudu', 22);
p1.setName('zhou');
console.log(p1);

4. instanceof

A instanceof B 的判断标准:如果B函数的显式原型对象在A对象的原型链上,返回 true,否则返回 false

function F() { }
var f1 = new F();

console.log(f1 instanceof F); // true
console.log(f1.__proto__ === F.prototype); // true

console.log(f1 instanceof Object); // true
console.log(f1.__proto__.__proto__ === Object.prototype); // true

Function 是通过new自己产生的实例

console.log(Object instanceof Function); // true
console.log(Object.__proto__ === Function.prototype); // true

console.log(Object instanceof Object); // true
console.log(Object.__proto__.__proto__ === Object.prototype); // true

console.log(Function instanceof Function); // true
console.log(Function.__proto__ === Function.prototype); // true

console.log(Function instanceof Object); // true
console.log(Function.__proto__.__proto__ === Object.prototype); // true

function F() { }
console.log(Object instanceof F); // false
console.log(Object.__proto__ === F.__proto__); // true

执行上下文与执行上下文栈

1. 变量提升与函数提升

变量声明提升:通过var声明的变量,在定义语句之前就可以访问到,值为undefined

var a = 3;
function fn() {
  // 此时a已经声明了
  console.log(a);
  var a = 4;
}
fn(); // undefined

函数声明提升:通过function声明的函数,在声明之前就可以直接调用,值为函数对象

fn(); // fn()
function fn() {
  console.log("fn()");
}

注意:先执行变量提升,再执行函数提升

2. 执行上下文

代码分类(位置)

  1. 全局代码
  2. 函数(局部)代码

全局执行上下文

  • 在执行全局代码前将window确定为全局执行上下文
  • 对全局数据进行预处理
    1. var定义的全局变量 ==> undefined,添加为window的属性
    2. function声明的全局函数 ==> 赋值(fun),添加为window的方法
    3. this ==> 赋值(window)
  • 开始执行全局代码
console.log(window.a1);// undefined
window.a2();// a2()
console.log(this);// Window对象

var a1 = 3;
function a2() {
  console.log("a2()");
}

函数执行上下文

  • 在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象(虚拟对象,存在于栈中)
  • 对局部数据进行预处理
    1. 形参变量 ==> 赋值(实参) ==> 添加为执行上下文的属性
    2. arguments ==> 赋值(实参列表) ==> 添加为执行上下文的属性
    3. var定义的局部变量 ==> undefined,添加为执行上下文的属性
    4. function声明的函数 ==> 赋值(fun),添加为执行上下文的方法
    5. this ==> 赋值(调用函数的对象)
  • 开始执行函数体代码
function fn(a3) {
  console.log(a1);// undefined
  a2();// a2()
  console.log(a3);// dudu
  console.log(this);// Window
  console.log(arguments);// Arguments { 0: "dudu", 1: true, … } 伪数组

  var a1 = 3;
  function a2() {
    console.log("a2()");
  }
  fn("dudu", true);
}

3. 执行上下文栈
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);
  • 在全局代码执行前,JS引擎就会创建一个栈来存储管理所有的执行上下文对象
  • 在全局执行上下文(window)确定后,将其添加到栈中
  • 在函数执行下上文创建后,将其添加到栈中
  • 在当前函数执行完后,将栈顶的对象移除
  • 当所有的代码执行完后,栈中只剩下window

作用域与作用域链

1. 作用域

理解

  • 一个代码段所在的区域
  • 是静态的(相对于上下文对象),在编写代码的时候就确定了

分类

  • 全局作用域
  • 函数作用域
  • 块作用域(ES6新增)

作用:

  • 隔离变量,不同作用域下同名变量不会有冲突
2. 作用域与执行上下文

区别1:

  • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了,而不是在函数调用时确定
  • 全局执行上下文是在全局作用域确定之后,js代码马上执行之前创建
  • 函数执行上下文是在调用函数时,函数体代码执行之前

区别2:

  • 作用域是静态的,只要函数定义好了就一直存在,且不会再变化
  • 执行上下文是动态的,调用函数时创建,函数调用结束时就会自动被释放

联系:

  • 执行上下文对象从属于所在的作用域(全局上下文环境 == 全局作用域;函数上下文环境 == 对应的函数作用域)
3. 作用域链

理解

  • 多个上下级关系的作用域形成的链,它的方向是从内到外(从下到上)的
  • 查找变量时就是沿着作用域链来查找的

查找一个变量的查找原则:

  1. 在当前作用域下的执行上下文中查找对应的属性,如果有直接返回,否则进入2
  2. 在上一级作用域的执行上下文中查找对应的属性,如果有直接返回,否则进入3
  3. 再次执行2的相同操作,直到全局作用域,如果还找不到就报错

闭包

如何产生闭包:当一个嵌套的内部子函数应用了嵌套的外部复函数的变量(函数)时,就产生了闭包。闭包存在于嵌套的内部函数中

function fn1() {
  var a = 2;
  var b = 'abc';
  function fn2() {
    console.log(a);
  }
  // 此时查看浏览器调试,会发现发现fn2中有一个Closure对象,对象中包含fn2中引用的内容(有a但是没有b)
}
fn1();

闭包是什么:

  • 理解一:闭包是嵌套的内部函数
  • 理解二:包含被引用变量(函数)的对象

产生闭包的条件:

  1. 函数嵌套
  2. 内部函数引用了外部函数的数据(变量 / 函数)
  3. 执行内部函数定义(不用调用内部函数)

常见的闭包

  • 将函数作为另一个函数的返回值
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("dudu", 2000);

闭包的作用

  • 使得函数内部的变量在函数执行完后,仍然存活在内存中(延长了局部变量的生命周期)
  • 让函数外部可以操作(读写)到函数内部的数据(变量/函数)

闭包的生命周期

  • 产生:在嵌套的内部函数定义执行完时就产生了(调用外部函数)
  • 死亡:在嵌套的内部函数成为垃圾对象时

闭包的应用:定义JS模块

  • JS模块就是具有特定功能的JS文件
  • 将所有的数据和功能都封装在一个函数内部(私有的),只向外暴露一个包含n个方法的对象或函数
  • 模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能
function myModule() {
  // 私有数据
  var msg = 'Dudu';
  // 操作数据的函数
  function f1() {
    console.log("f1(): " + msg.toUpperCase());
  }
  function f2() {
    console.log("f2(): " + msg.toLowerCase());
  }
  // 向外暴露对象(给外部使用的方法)
  return {
    f1: f1,
    f2: f2
  };
}

// 加上这个参数在代码压缩时比较好
(function (window) {
  // 私有数据
  var msg = 'Dudu';
  // 操作数据的函数
  function f1() {
    console.log("f1(): " + msg.toUpperCase());
  }
  function f2() {
    console.log("f2(): " + msg.toLowerCase());
  }
  // 向外暴露对象(给外部使用的方法)
  window.module2 = {
    f1: f1,
    f2: f2
  };
})(window);
<script type="text/javascript" src="../js/demo.js"></script>
<script>
    var module = myModule();
    module.f1(); // f1(): DUDU
    module.f2(); // f2(): dudu
    module2.f1(); // f1(): DUDU
    module2.f2(); // f2(): dudu
</script>

闭包的缺点:

  • 函数执行完后,函数内的局部变量没有释放,占用内存时间会变长
  • 容易造成内存泄漏

解决:

  • 能不用闭包就不用
  • 及时释放
function fn1() {
  var arr = new Array(100000);
  function f1() {
    console.log(arr.length);
  }
  return f1;
}
var f = fn1();
f();
// 解决:让内部函数成为垃圾对象,回收闭包
f = null;

内存溢出:

  • 一种程序运行出现的错误
  • 当程序运行需要的内存超过了剩余的内存时,就抛出内存溢出的错误

内存泄露:

  • 占用的内存没有及时释放
  • 内存泄露积累多了就容易导致内存溢出

常见的内存泄露

  • 意外的全局变量(在函数中定义变量没有使用var)
function fn() {
  a = 3;
  console.log(a);
}
  • 没有及时清理的计时器或回调函数
setInterval(function () {
  console.log("dudu");
}, 3 * 1000);
  • 闭包
function fn() {
  var a = 3;
  function f() {
    console.log(a++);
  }
  return f;
}
var f = fn();
f();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值