堆栈
堆是堆内存的简称,栈(先进后出)是栈内存的简称。
堆是动态分配内存,内存大小不一,也不会自动释放。栈是自动分配相对固定大小的内存空间,并由系统自动释放.
js数据类型
- 基本数据类型
Undefined、Null、Boolean、String、Number、Symbol都是直接按值直接存在栈中,每种类型的数据占用的内存空间大小都是固定的,并且由系统自动分配自动释放 - 引用数据类型
Object,Array,Function这样的数据存在堆内存中,但是数据指针是存放在栈内存中的,当我们访问引用数据时,先从栈内存中获取指针,通过指针在堆内存中找到数据
代码示例:
let arr_origin = [1,2,3,4,5];
let arr_copy = arr_origin;
let arr2 = arr_origin[2];
执行代码:
arr_copy[1] = 'change1';
arr2 = ‘change2’
我们发现arr_origin中的下标是1的值变成可change1,但是下标是2的值并未变化。原因是:arr_copy是arr_origin栈指针的引用,并未开辟新的内存空间,但是arr2是在栈内存中独立存在,所以会产生上述结果。熟悉此原理,则数据的深浅拷贝就不难理解了!
闭包
定义:能够访问另一个函数作用域的变量的函数
eg:
function outer() {
var a = '变量1'
var inner = function () {
console.info(a)
}
return inner // inner 就是一个闭包函数,因为他能够访问到outer函数的作用域
}
为什么闭包函数能够访问其他函数的作用域 ?
var a = 1;
function fn(){
var b = 2;
function fn1(){
console.log(b);
}
fn1();
}
fn();
1、在执行fn前,此时我们在全局执行环境(windows作用域),且环境里有个变量a
2、执行fn,此时栈内存就会push一个fn的执行环境,这个环境里有变量b和函数对象fn1,这里可以访问自身执行环境和全局执行环境所定义的变量
3、进入fn1,此时栈内存就会push 一个fn1的执行环境,这里面没有定义其他变量,但是我们可以访问到fn和全局执行环境里面的变量,因为程序在访问变量时,是向底层栈一个个找,如果找到全局执行环境里都没有对应变量,则程序抛出underfined的错误。
4、随着fn1()执行完毕,fn1的执行环境被杯销毁,接着执行完fn(),fn的执行环境也会被销毁,只剩全局的执行环境下,现在没有b变量,和fn1函数对象了,只有a 和 fn(函数声明作用域是window下)
当程序在调用某个函数时,做了一下的工作:准备执行环境,初始函数作用域链和arguments参数对象
由于闭包会携带包含它的函数的作用域,因为会比其他函数占用更多内容,过度使用闭包,会导致内存占用过多。
call和apply
作用:一般来说,this总是指向调用某个方法的对象,call和apply可以改变this指向
区别:
apply(thisObj,[arg1,arg2,…])
call(thisObj,arg1,arg2,…)
eg1:
function test () {
console.log(123);
}
test.call()//123
eg2:
定义一个构造函数
function Person (name, age) {
this.name = name;
this.age = age;
}
可以这样用
var person1 = new Person('洋哥', 20);
也可以这样用
var person2 = {};
Person.call(person2, '洋哥', 20) // 效果和上面的一模一样
eg3:
var myObject = {firstName:'my', lastName:'Object'};
function getMessage(sex,age){
console.log(this.firstName + this.lastName + " 性别: " + sex + " age: " + age );
}
getMessage.call(myObject,"未知",22);
函数柯里化
定义
在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术
就是只传递给函数某一部分参数来调用,返回一个新函数去处理剩下的参数(闭包)
类数组转换为真正数组
function list() {
return [].slice.call(arguments,1);;
}
console.log(list(1, 2, 3));//[2,3]
slice 在不接受任何参数的时候会返回 this 本身
arguments是类数组,具有长度属性,但不是数组
那就意味着 arguments.slice()行不通
call(arguments,1)改变this指向,将arguments把this给slice,从1开始切割所以返回[2,3]
简单的add函数
let add = function(x, y){
return x + y
}
add函数柯里化
var addCurrying = function(x) {
return function(y) {
return x + y
}
}
var increment = addCurrying(1)
var addTen = addCurrying(10)
increment(2)// 3
addTen(2)// 12
柯里化函数示例
eg1:
/**
* @description 柯里化函数
* @param {Function} fn 传入的需要运算的函数
* @return {Function} 返回函数
*/
let curry = function (fn) {//fn接收传入的第一个参数,即add函数
let reuseArgs = Array.prototype.slice.call(arguments, 1);//除add函数之外的其他参数数组 [3,4,5]
return function () {
// selfArgs是自身参数
let selfArgs = Array.prototype.slice.call(arguments);//接收到的curryAdd函数传入的值转化为数组 [6]
// 合并自身参数和复用的参数
let mergeArgs = reuseArgs.concat(selfArgs)
// 计算返回结果
return fn.apply(null, mergeArgs)//arguments为Object类型,所以此处把null的this指向fn
}
}
/**
* @description 求和函数
* @return { Number } 和
*/
let add = function () {
let args = Array.prototype.slice.call(arguments);//类数组转数组
let sum = 0
for (let i = 0; i < args.length; i++) {
sum += args[i]
}
return sum;//返回和
}
// 3, 4, 5是固定不变,可复用的参数
let curryAdd = curry(add, 3, 4, 5)
curryAdd(6) // 结果为18
curryAdd(7, 8) // 结果为27
eg2:
// 支持多参数传递
function progressCurrying(fn, args) {
var _this = this
var len = fn.length;
var args = args || [];
return function() {
var _args = Array.prototype.slice.call(arguments);
Array.prototype.push.apply(args, _args);
// 如果参数个数小于最初的fn.length,则递归调用,继续收集参数
if (_args.length < len) {
return progressCurrying.call(_this, fn, _args);
}
// 参数收集完毕,则执行fn
return fn.apply(this, _args);
}
}
eg3(经典面试题):
// 实现一个add方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
function add() {
// 第一次执行时,定义一个数组专门用来存储所有的参数
var _args = Array.prototype.slice.call(arguments);
// 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
var _adder = function() {
_args.push(...arguments);
return _adder;
};
// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
//每个对象的toString和valueOf方法都可以被改写,每个对象执行完毕,如果被用以操作JavaScript解析器就会自动调用对象的toString或者valueOf方法
_adder.toString = function () {
return _args.reduce(function (a, b) {
return a + b;
});
}
return _adder;//注意此处不带(),表示执行对象,隐式执行reduce函数
}
add(1)(2)(3) // 6
add(1, 2, 3)(4) // 10
add(1)(2)(3)(4)(5) // 15
add(2, 6)(1) // 9