地址:
内存空间详解
堆(heap)、栈(stack)与队列(queue)
一、栈
栈空间的存储原理:先进先出、后进后出
二、堆
堆数据结构是一种树状结构。存取数据的方式和书与书架的关系类似,通过key获取value,类似json
三、队列
四、变量对象与基础数据类型
javaScript的执行上下文生成之后,会创建一个叫做变量对象的特殊对象, javaSscript的基础数据类型往往都会保存在变量对象中。
javaScript中有5个基础数据类型:null、undefined、Boolean、Number、String.。
基础数据类型都是按值访问,因为我们可以直接操作保存在变量中的实际的值。
五、引用数据类型与堆内存
Object(Object、data、array、function、RegExp)
例子:
1 var a1 = 0; // 变量对象 2 var a2 = 'this is string'; // 变量对象 3 var a3 = null; // 变量对象 4 5 var b = { m: 20 }; // 变量b存在于变量对象中,{m: 20} 作为对象存在于堆内存中 6 var c = [1, 2, 3]; // 变量c存在于变量对象中,[1, 2, 3] 作为对象存在于堆内存中
面试题
1 // demo01.js 2 var a = 20; 3 var b = a; 4 b = 30; 5 6 // 这时a的值是多少? 7 8 9 // demo02.js10 var m = { a: 10, b: 20 }11 var n = m;12 n.a = 15;13 14 // 这时m.a的值是多少
六、内存空间管理
因为JavaScript具有自动垃圾收集机制,所以我们在开发时好像不用关心内存的使用问题,内存的分配与回收都完全实现了自动管理。
JavaScript的内存生命周期
1. 分配你所需要的内存2. 使用分配到的内存(读、写)3. 不需要时将其释放、归还
例子:
1 var a = 20; // 在内存中给数值变量分配空间2 alert(a + 100); // 使用内存3 a = null; // 使用完毕之后,释放内存空间
执行上下文
执行上下文可以理解为函数执行的环境,每一个函数执行时,都会给对应的函数创建一个执行环境
每次当控制器转到可执行代码的时候,就会进入一个执行上下文。执行上下文可以理解为当前代码的执行环境,它会形成一个作用域。JavaScript中的运行环境大概包括三种情况。
- 全局环境:JavaScript代码运行起来会首先进入该环境
- 函数环境:当函数被调用执行时,会进入当前函数中执行代码
- eval(不建议使用,可忽略)
1、建立arguments对象。检查当前上下文的参数,建立该对象下的属性与属性值
2、检查当前上下文的函数声明,也就是function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。
如果函数名存在,那么该属性会被新的引用所覆盖
3、检查当前上下文的变量声明,每找到一个变量申明,就在变量对象中以变量名建立一个属性,值为undefined。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性值不会被修改
变量对象和活动对象的区别:
它们是同一个对象,只是处于执行上下文的不同生命周期。只有处于函数调用栈栈顶的执行上下文中的变量对象,才会变成活动对象
备注:变量对象的创建,解释了JavaScript里面的变量提升,也说明了function声明比var声明高级
作用域与闭包
- 在JavaScript中,我们可以将作用域定义为一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找。
这里的标识符,指的是变量名或者函数名
-
JavaScript中只有全局作用域与函数作用域(因为eval我们平时开发中几乎不会用到它,这里不讨论)。
-
作用域与执行上下文是完全不同的两个概念。我知道很多人会混淆他们,但是一定要仔细区分。
- 作用域链(Scope),是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。
闭包是一种特殊的对象。
它由两部分组成。执行上下文(代号A),以及在该执行上下文中创建的函数(代号B)。
当B执行时,如果访问了A中变量对象中的值,那么闭包就会产生。
this
this是在函数被调用的时候确定的
函数与函数式编程
函数声明、函数表达式、匿名函数与自执行函数
在ES5中,没有块级作用域,因此我们使用自执行函数来模拟块级作用域,这样就提供了一个独立的执行上下文,结合闭包,就为模块化提供了基础。而函数自执行其实是匿名函数的一种应用。
例子:
1 (function() { 2 // 私有变量 3 var age = 20; 4 var name = 'Tom'; 5 6 // 私有方法 7 function getName() { 8 return `your name is ` + name; 9 }10 11 // 共有方法12 function getAge() {13 return age;14 }15 16 // 将引用保存在外部执行环境的变量中,形成闭包,防止该执行环境被垃圾回收17 window.getAge = getAge;18 })();
jquery对象
1 ; 2 (function(ROOT) { 3 4 // 构造函数 5 var jQuery = function(selector) { 6 7 // 在jQuery中直接返回new过的实例,这里的init是jQuery的真正构造函数 8 return new jQuery.fn.init(selector) 9 }10 11 jQuery.fn = jQuery.prototype = {12 constructor: jQuery,13 14 version: '1.0.0',15 16 init: function(selector) {17 // 在jquery中这里有一个复杂的判断,但是这里我做了简化18 var elem, selector;19 elem = document.querySelector(selector);20 this[0] = elem;21 22 // 在jquery中返回一个由所有原型属性方法组成的数组,我们这里简化,直接返回this即可23 // return jQuery.makeArray(selector, this);24 return this;25 },26 27 // 在原型上添加一堆方法28 toArray: function() {},29 get: function() {},30 each: function() {},31 ready: function() {},32 first: function() {},33 slice: function() {}34 // ... ...35 }36 37 jQuery.fn.init.prototype = jQuery.fn;38 39 // 实现jQuery的两种扩展方式40 jQuery.extend = jQuery.fn.extend = function(options) {41 42 // 在jquery源码中会根据参数不同进行很多判断,我们这里就直接走一种方式,所以就不用判断了43 var target = this;44 var copy;45 46 for(name in options) {47 copy = options[name];48 target[name] = copy;49 }50 return target;51 }52 53 // jQuery中利用上面实现的扩展机制,添加了许多方法,其中54 55 // 直接添加在构造函数上,被称为工具方法56 jQuery.extend({57 isFunction: function() {},58 type: function() {},59 parseHTML: function() {},60 parseJSON: function() {},61 ajax: function() {}62 // ...63 })64 65 // 添加到原型上66 jQuery.fn.extend({67 queue: function() {},68 promise: function() {},69 attr: function() {},70 prop: function() {},71 addClass: function() {},72 removeClass: function() {},73 val: function() {},74 css: function() {}75 // ...76 })77 78 // $符号的由来,实际上它就是jQuery,一个简化的写法,在这里我们还可以替换成其他可用字符79 ROOT.jQuery = ROOT.$ = jQuery;80 81 })(window);
在jquery源码里面,$.extend里面的方法是静态方法,可以直接调用,比如$.ajax() 、$.each()
$.fn.extend里面的方法是绑定在原型链上的,调用的时候需要new实例化,比如 $('').css()
$('')为什么就是new实例化,这是调用jquery的init方法,它在里面New了
给jquery添加一个方法:
1 2 3
事件循环机制(还不是很了解,记录下来)
ES6相关知识
一、箭头函数
1 // es5 2 var fn = function(a, b) { 3 return a + b; 4 } 5 6 // es6 箭头函数写法,当函数直接被return时,可以省略函数体的括号 7 const fn = (a, b) => a + b; 8 9 // es510 var foo = function() {11 var a = 20;12 var b = 30;13 return a + b;14 }15 16 // es617 const foo = () => {18 const a = 20;19 const b = 30;20 return a + b;21 }
备注: 箭头函数可以替换函数表达式,但是不能替换函数声明
其次还有一个至关重要的一点,那就是箭头函数中,没有this。如果你在箭头函数中使用了this,那么该this一定就是外层的this。
也正是因为箭头函数中没有this,因此我们也就无从谈起用call/apply/bind来改变this指向。记住这个特性,能让你在react组件之间传值时少走无数弯路。
1 var person = { 2 name: 'tom', 3 getName: function() { 4 return this.name; 5 } 6 } 7 8 // 我们试图用ES6的写法来重构上面的对象 9 const person = {10 name: 'tom',11 getName: () => this.name12 }13 14 // 但是编译结果却是15 var person = {16 name: 'tom',17 getName: function getName() {18 return undefined.name;19 }20 };
在ES6中,会默认采用严格模式,因此this也不会自动指向window对象了,而箭头函数本身并没有this,因此this就只能是undefined
1 // 可以稍做改动 2 const person = { 3 name: 'tom', 4 getName: function() { 5 return setTimeout(() => this.name, 1000); 6 } 7 } 8 9 // 编译之后变成10 var person = {11 name: 'tom',12 getName: function getName() {13 var _this = this; // 使用了我们在es5时常用的方式保存this引用14 15 return setTimeout(function () {16 return _this.name;17 }, 1000);18 }19 };
箭头函数中无法访问arguments
对象。
二、模板字符串
模板字符串是为了解决使用+
号拼接字符串的不便利而出现的
1 // es6 2 const a = 20; 3 const b = 30; 4 const string = `${a}+${b}=${a+b}`; 5 6 // es5 7 var a = 20; 8 var b = 30; 9 var string = a + "+" + b + "=" + (a + b);
使用 `` 将整个字符串包裹起来,而在其中使用 ${} 来包裹一个变量或者一个表达式
三、解析结构
1 // 首先有这么一个对象2 const props = {3 className: 'tiger-button',4 loading: false,5 clicked: true,6 disabled: 'disabled'7 }
当我们想要取得其中的2个值:loading与clicked时:
1 // es5 2 var loading = props.loading; 3 var clicked = props.clicked; 4 5 // es6 6 const { loading, clicked } = props; 7 8 // 给一个默认值,当props对象中找不到loading时,loading就等于该默认值 9 const { loading = false, clicked } = props;
1 // 比如 2 // section1 3 import React, { Component } from 'react'; 4 5 // section2 6 export { default } from './Button'; 7 8 // section3 9 const { click, loading } = this.props;10 const { isCheck } = this.state;11 12 // more 任何获取对象属性值的场景都可以使用解析结构来减少我们的代码量
另外,数组也有属于自己的解析结构。
1 // es62 const arr = [1, 2, 3];3 const [a, b, c] = arr;4 5 // es56 var arr = [1, 2, 3];7 var a = arr[0];8 var b = arr[1];9 var c = arr[2];
数组以序列号一一对应,这是一个有序的对应关系。
而对象根据属性名一一对应,这是一个无序的对应关系。根据这个特性,使用解析结构从对象中获取属性值更加具有可用性。四、函数默认参数
之前我们不能直接为函数指定默认参数,因此很多时候为了保证传入的参数具备一个默认值,我们常常使用如下的方法:
1 function add(x, y) {2 var x = x || 20;3 var y = y || 30;4 return x + y;5 }6 7 console.log(add()); // 50
备注:这种方式并不是没有缺点,比如当我传入一个x值为false,这个时候任然会取到默认值,就不是我们的本意了。
来看看ES6的默认值写法:
1 function add(x = 20, y = 30) {2 return x + y;3 }4 5 console.log(add());
在实际开发中给参数添加适当的默认值,可以让我们对函数的参数类型有一个直观的认知。
1 const ButtonGroupProps = {2 size: 'normal',3 className: 'xxxx-button-group',4 borderColor: '#333'5 }6 7 export default function ButtonGroup(props = ButtonGroupProps) {8 ... ...9 }
五、展开运算符
在ES6中用...
来表示展开运算符,它可以将数组方法或者对象进行展开。先来看一个例子它是如何使用的。
1 const arr1 = [1, 2, 3];2 const arr2 = [...arr1, 10, 20, 30];3 4 // 这样,arr2 就变成了[1, 2, 3, 10, 20, 30];
当然,展开对象数据也是可以得到类似的结果
const obj1 = { a: 1, b: 2, c: 3}const obj2 = { ...obj1, d: 4, e: 5, f: 6}// 结果类似于 const obj2 = Object.assign({}, obj1, {d: 4})
展开运算符还常常运用在解析结构之中,例如我们在Raect封装组件的时候常常不确定props到底还有多少数据会传进来,就会利用展开运算符来处理剩余的数据。
1 // 这种方式在react中十分常用 2 const props = { 3 size: 1, 4 src: 'xxxx', 5 mode: 'si' 6 } 7 8 9 const { size, ...others } = props;10 11 console.log(others)12 13 // 然后再利用暂开运算符传递给下一个元素,再以后封装react组件时会大量使用到这种方式,正在学习react的同学一定要搞懂这种使用方式14
展开运算符还用在函数的参数中,来表示函数的不定参。只有放在最后才能作为函数的不定参,否则会报错。
1 // 所有参数之和2 const add = (a, b, ...more) => {3 return more.reduce((m, n) => m + n) + a + b4 }5 6 console.log(add(1, 23, 1, 2, 3, 4, 5)) // 39
六、对象字面量
ES6针对对象字面量做了许多简化语法的处理。
- 当属性与值的变量同名时
1 const name = 'Jane'; 2 const age = 20 3 4 // es6 5 const person = { 6 name, 7 age 8 } 9 10 // es511 var person = {12 name: name,13 age: age14 };
那么这种方式在任何地方都可以使用,比如在一个模块对外提供接口时
1 const getName = () => person.name;2 const getAge = () => person.age;3 4 // commonJS的方式5 module.exports = { getName, getAge }6 7 // ES6 modules的方式8 export default { getName, getAge }
- 除了属性之外,对象字面量写法中的方法也可以有简写方式。
1 // es6 2 const person = { 3 name, 4 age, 5 getName() { // 只要不使用箭头函数,this就还是我们熟悉的this 6 return this.name 7 } 8 } 9 10 // es511 var person = {12 name: name,13 age: age,14 getName: function getName() {15 return this.name;16 }17 };
- 在对象字面量中可以使用中括号作为属性,表示属性名也能是一个变量了。
1 const name = 'Jane';2 const age = 203 4 const person = {5 [name]: true,6 [age]: true7 }
在ant-design的源码实现中,就大量使用了这种方式来拼接当前元素的className,例如:
1 let alertCls = classNames(prefixCls, {2 [`${prefixCls}-${type}`]: true,3 [`${prefixCls}-close`]: !this.state.closing,4 [`${prefixCls}-with-description`]: !!description,5 [`${prefixCls}-no-icon`]: !showIcon,6 [`${prefixCls}-banner`]: !!banner,7 }, className);
JS循环机制:
观看这俩个文章: