递归
两个要求
- 找规律
- 找出口
其实是比较耗时间的一种方法
js执行三部曲
语法分析
就是最开始通篇扫描检查有无基础错误
预编译
-
函数声明整体提升,就是说,如果里面存在函数,编译器会把函数直接提在程序的最前面
-
变量声明提升
-
imply global 暗示全局变量:即任何变量,如果变量未经声明就赋值,此变量就为全局对象(window)所有
-
一切声明的全局变量,全是window的属性(在全局范围内)
例如
a = 10;
window.a = 10;
window {
a : 123
}
// 这三者的意义是一样的
- window就是全局的域
四部曲
预编译发生在函数执行的前一刻
- 创建AO对象(Activation Object)(执行期上下文)
- 找形参和变量声明,将变量和形参名作为AO的属性名,值为undefined
- 将实参值和形参统一
- 在函数体里面找函数声明,值赋予函数体
在全局里面的第一步有些许不一样,第一步是生成一个GO对象Global Object
GO也就是window
if里面不能声明function
解释执行
就是执行函数
作用域
[[scope]]:每个JavaScript函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供JavaScript引擎存取,这就是其中一个
指的就是我们所说的作用域,其中存储了运行期上下文的集合
作用域链:scope中所存储的执行期上下文对象的集合,这个集合呈链式链接,叫做作用域链
运行期上下文:当函数执行的时,会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行期上下文,当函数执行完毕,所产生的执行上下文被销毁
查找变量:在哪个函数里面查找变量,就从那个函数的作用域链的顶端一次向下查找(感觉类似栈的结构)
function a(){
function b(){
var b = 234;
}
var a = 123;
b();
}
var glob = 100;
a();
b访问的AO就是a产生的AO,指向的地方是一样的
函数执行完之后销毁执行期上下文,就是删除地址,不会删除内存
function a (){
function b() {
var bbb = 234;
document.write(aaa);
}
var aaa = 123;
return b;
}
var glob = 100;
var demo = a();
demo();
将函数内部生成的函数保存在外部就会生成闭包
function a(){
function b(){
num ++;
console.log(num);
}
var num = 100;
return b;
}
var demo = a();
demo();
demo();
上述输出结果为101,102,在这种情况下修改的直接是最开始AO里面的数据
return b;其实就是在b未执行的情况下,先将他定义,他的scope里面此时有a的AO和整体的GO
立即执行函数
定义:此类函数没有声明,只在一次执行过后即释放。适合做初始化工作。
针对初始化功能的函数(只被执行一次,或者希望他只被执行一次的函数)
// 第一种
(function (){
// 上面的括号用来放形参
}());//末尾的括号用来放实参
// W3C建议上面这种
// 第二种
(function (){
})();
var test = function () {
console.log("a");
}();
// 第二种里面两种表达的意思相同
只有表达式才能被执行符号执行
被执行符号(就是小括号)执行的时候,函数名会被自动忽略,也就是说,这个函数变成了立即执行函数
闭包
当内部函数被保存到外部时,将会生成闭包,闭包会导致原有作用域链不释放,造成内存泄漏
闭包作用
- 实现公有变量
eg:函数累加器
function test() {
var num = 100;
function a() {
num ++;
console.log(num);
}
function b() {
num --;
console.log(num);
}
return [a,b];
}
var MyArry = test();
MyArry[0]();
MyArry[1]();
输出结果为101,100,因为MyArry1是在101的基础上进行自减的
- 可以做缓存(存储结构)
eg:eater
function eater() {
var food = "";
var obj = {
eat : function() {
console.log("I am eating " + food);
food = "";
},
push : function (myfood) {
food = myfood ;
}
}
return obj;
}
var eater1 = eater();
eater1.push("banana");
eater1.eat();
}
- 可以实现分装,变量私有化
eg:Person();
function Person(name, wife) {
var girlFriend = 'xiaoliu';
// 此时的girlFriend就是私有化属性,在console调控台上是查看不到的,但是属性是真实存在的,而且在下面三个函数执行的时候,形成闭包,三个函数都指向Person的OA,所以可以对该属性进行修改打印之类的行为
this.name = name;
this.wife = wife;
this.changeWife = function(){
this.wife = girlFriend;
}
this.changeGirlFriend = function(target){
girlFriend = target;
}
this.sayGirlFriend = function(){
console.log(girlFriend);
}
}
var people = new Person("xiaoli","xiaofen");
- 模块化开发,防止污染全局变量
var name = 'bcd';
var init = (function () {
var name = 'abc';
function callName() {
console.log(name);
}
return function (){
callName();
}
}())
init();
// 最后打印结果为abc
- 一个关于闭包的小demo
函数目的:按顺序输出0-9
function test() {
var arr = [];
for(var i = 0; i < 10; i ++){
arr[i] = function () {
document.write(i + " ");
}
}
return arr;
}
var myArry = test();
for(var j = 0; j < 10;j ++ ){
myArry[j]();
}
输出结果如图
为什么会出现如下情况呢?
因为在最初执行的时候,在test函数执行的时候,数组的赋值相当于将函数体赋值在数组里面,就是相当于函数引用,赋值就相当于赋值了整个匿名函数体,里面的匿名函数并没有执行,直到在下面for循环里面进行打印的时候,函数function () {document.write(i + " ");}才执行,此时其实就已经形成了闭包了,test的AO里面的i在结束最初的for循环的时候就变成了10,在匿名函数执行的时候,检索到的i就已经是10了,所以循环打印输出10
就相当于10个函数在外部访问10个i,然后i都是10
解决:
function test() {
var arr = [];
for(var i = 0; i < 10; i ++){
(function (j) {
arr[j] = function () {
document.write(j + " ");
}
}(i))
}
return arr;
}
var myArry = test();
for(var j = 0; j < 10;j ++ ){
myArry[j]();
}
数组的每一个里面的保存的函数都会有一个立即执行函数的AO,每一个都立即执行函数都不一样,所以在那些的立即执行函数里面的j都是不一样的
对象
存在属性值和属性名,类似于c语言中的结构体,属性名方便于让我们找到属性值,每一个属性直接用逗号隔开
- 增加对象的属性
直接在下面用 对象名.新的属性名 = 进行赋值就好。
在对象里面修改属性值,需要给属性值加上对象名,或者用this来代替对象名
-
删除属性
直接delete + 对象名.属性名,然后就会被删除,在对象中未定义的属性返回值都是undefined -
修改属性
直接在定义的下面选择好属性,然后直接修改属性值 -
查看属性
直接在控制台查看,或者console.log查看,不能用document.write查看,用这个查看只能看到输出这个[object Object]
对象的创建方法
-
var obj = {};
plainObject 对象自变量/对象直接量 -
构造函数
1)系统自带的构造函数 Object()
new Object()会返回一个对象,需要一个变量来接受
var obj = new Object();
2)自定义
大驼峰式命名规则:只要遇到单词,就首字母大写,使用构造函数来创建对象时,必须要用大驼峰式命名法来命名
构造函数返回给变量的结果是object类型的
function Car(){
this.name = 'dache',
this.height = 123
}
var car =new Car();
构造函数内部原理
- 在函数体最前面隐式var this{}的空对象
- 执行this.XXX = XXX;
- 最后会return this
包装类
原始数据类型:number,boolean,string,undefined,null
引用数据类型:object
原始值不能有属性和方法
以下三种也称为基本包装类型
var str = new String('abcd');
var num = new Number(123);
var bol = new Boolean(true);
这三种表示方式创建的和直接var创建的不一样的区别是,在没有进行计算的情况下,str,num,bol都是对象,当对num进行运算之后,她就是number类型的数据
当对原始值进行一些属性或者方法的赋值,是经历了如下过程
var num = 4;
num.len = 3;
//此时创建一个new Number(4).len = 3; 然后就把刚刚创建的直接delete了
//当下面要调用的时候,又重新创建new了一个Number类型的对象,而此时的Number(4).len是undefined
console.log(num.len);
字符串的length属性是本身就有的属性