一 原型与原型链
prototype
每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象或者显示原型)
原型对象prototype中有一个属性constructor, 它指向函数对象
javascript
function a(){}
console.log(typeof a,typeof Date)
console.log(a.prototype, Date.prototype)
console.log(a.prototype.constructor === a,Date.prototype.constructor === Date)
注意仅函数有
javascript
let arr = []
let obj = []
console.log(arr.prototype) // undefined
console.log(obj.prototype) // undefined
可以给原型对象添加属性(一般都是方法)
能让后代拥有同样的方法
javascript
function F() {}
F.prototype.age = 12; //添加属性
F.prototype.setAge = function (age) {
// 添加方法
this.age = age;
};
// 创建函数的实例对象
var f = new F();
console.log(f.age);//12
f.setAge(23);
console.log(f.age);//23
显式原型与隐式原型
显式原型 通常指的是通过构造函数来定义对象的原型。每个构造函数都有一个 prototype
属性,这个属性指向一个对象,用于设置通过该构造函数创建的实例对象的原型
javascript
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log('Hello, ' + this.name);
};
const person = new Person('Alice');
person.sayHello(); // Hello, Alice
隐式原型 指的是通过实例对象访问的 __proto__
属性。每个对象都有一个隐式原型,它指向该对象的构造函数的 prototype
属性。虽然 __proto__
现在已经被标准化,但它不是官方推荐的访问方式(推荐使用 Object.getPrototypeOf()
)。
javascript
const person = new Person('Bob');
console.log(person.__proto__ === Person.prototype); // true
原型链
原型链是 JavaScript 实现继承的核心机制。它是通过对象的原型(prototype
和 __proto__
)属性形成的一种链式结构,用于实现属性和方法的继承与查找。
javascript
function A() {
this.name = "A";
}
A.prototype.sayHello = function () {
console.log("Hello from A");
};
function B() {
this.age = 30;
}
B.prototype = new A(); // B 的原型设置为 A 的实例,实现继承
B.prototype.constructor = B; // 修复 constructor 指向
B.prototype.sayHi = function () {
console.log("Hi from B");
};
const b = new B();
console.log(b.name); // "A" - 从 A 的原型链上找到
b.sayHello(); // "Hello from A" - 从 A 的原型链上找到
b.sayHi(); // "Hi from B" - B 自己的方法
console.log(b.__proto__ === B.prototype); // true
console.log(B.prototype.__proto__ === A.prototype); // true
console.log(A.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null
属性问题
读取会从原型链找,设置不会
javascript
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.setName = function (name) {
this.name = name;
}
Person.prototype.sex = '男';
var p1 = new Person('Tom', 12)
p1.setName('Jack')
console.log(p1.name, p1.age, p1.sex,111)
p1.sex = '女'
console.log(p1.name, p1.age, p1.sex,222)
instanceof
运算符
instanceof
是 JavaScript 中用于检查对象是否是某个构造函数的实例 的运算符。它通过检查对象的原型链,判断某个对象是否继承自某个构造函数的 prototype
。
javascript
function A() {}
const a = new A();
console.log(a instanceof A); // true
console.log(a instanceof Object); // true
二 闭包函数
理解闭包
如何产生闭包?
* 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 外部函数调用,就产生了闭包
产生闭包的条件?
- 函数嵌套
- 内部函数引用了外部函数的数据(变量/函数)
- 调用了外部的函数
常见的闭包
将函数作为另一个函数的返回值
javascript
function outerFunction() {
let count = 0; // 局部变量
return function innerFunction() {
count++; // innerFunction 是闭包
console.log(count);
};
}
const closureFunction = outerFunction();
closureFunction(); // 1
closureFunction(); // 2
将函数作为实参传递给另一个函数调用
javascript
function showMsgDelay(msg, time) {
setTimeout(function () {
console.log(msg)
}, time)
}
showMsgDelay('hello', 1000)
//上面的代码中,闭包是里面的function,因为它是嵌套的子函数,而且引用了外部函数的变量msg。
闭包的作用
(1). 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
(2). 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
javascript
function fun1() {
var a = 3;
function fun2() {
a++; //引用外部函数的变量--->产生闭包
console.log(a);
}
return fun2;
}
var f = fun1(); //由于f引用着内部的函数-->内部函数以及闭包都没有成为垃圾对象
f(); //间接操作了函数内部的局部变量
f();
闭包的缺点
(1). 缺点
* 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
* 容易造成内存泄露
(2). 解决
* 能不用闭包就不用
* 及时释放
内存溢出与内存泄露
内存溢出
- 一种程序运行出现的错误
- 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
内存泄露
- 占用的内存没有及时释放
- 内存泄露积累多了就容易导致内存溢出
- 常见的内存泄露:
-
- 意外的全局变量
- 没有及时清理的事件回调函数
- 闭包
- 没有及时清理的定时器 setTimeout,setInterval
- 大量循环打印控制台日志
三 递归函数
递归是函数的高级用法,本质上是函数自已调用自已,它的行为非常类似循环
递归函数的特性
(1). 重复执行
(2).调用自身
(3). 【必须】要有条件控制,避免死循环,如果递归函数没有条件控制,那么他就是死循环
递归本身是一种循环操作,简单情况下可以替换循环语句的使用
**注意:**递归慎用,能用循环解决的事情,尽量别用递归
javascript
// 递归函数 :在函数内部调用自己,通过条件控制避免死循环
// 一直造成foo函数重复调用-- 死循环
var i = 0;
function foo() {
if (i >= 3) return;//限制条件
i++;
console.log("递归函数");
foo(); // 2.内部调用自己
}
foo(); // 1.外部调用
// 递归三特性-- 重复执行 / 调用自身 / 条件控制避免死循环!
递归函数常用案例
斐波拉契数列
javascript
<!-- 经典案例2:斐波拉契数列
1,1,2,3,5,8,13,21,34,55,89...求第n项 -->
<script>
//递归方法
function fib(n) {
if (n === 1 || n === 2) return n;
return fib(n - 1) + fib(n - 2);
}
console.log(fib(10)); //34
//非递归方法
function fib(n) {
var a = 0;
var b = 1;
var c = a + b;
for (var i = 3; i < n; i++) {
a = b;
b = c;
c = a + b;
}
return c;
}
console.log(fib(10)); //34
</script>