精华内容
下载资源
问答
  • js 高级程序设计

    2019-02-27 10:09:38
    JS高级程序设计,高清版PDF...《JavaScript高级程序设计》是2006年人民邮电出版社出版的图书,作者是(美)(Nicholas C.Zakas)扎卡斯。本书适合有一定编程经验的开发人员阅读,也可作为高校相关专业课程的教材。
  • js高级程序设计

    2015-10-13 08:10:57
    js高级程序设计第三版,很适合初学或者有一定js基础的人,外国人写的,比较专业
  • JS高级程序设计

    2015-06-05 23:40:31
    JS高级程序设计(第三版)适合于对于WEB开发有需求的人使用,详细介绍了语法,以及一些应用的例子,是很好的入门教材
  • Js高级程序设计

    2012-12-20 14:53:57
    JavaScript高级程序设计第三版 程序代码
  • anuglar js高级程序设计

    2018-06-20 17:10:47
    anuglar js高级程序设计,讲解非常细致,详细,相信每个人都可以从中获益
  • Ext JS高级程序设计

    2010-05-16 15:25:13
    Ext JS高级程序设计Ext JS高级程序设计Ext JS高级程序设计Ext JS高级程序设计Ext JS高级程序设计Ext JS高级程序设计
  • JS高级程序设计
  • js高级程序设计书的源代码,有很多书上的代码例子,便于更好的阅读js高级程序设计
  • JS高级程序设计读书笔记描述了js的全部的知识点,望各位早日get到,加油!!!
  • JS高级程序设计--可交互的网页 制作人JAVA开发项目组 课程内容 1 可交互的网页 代码展示 谢谢观看
  • js高级程序设计(第三版)原本代码
  • Ext JS高级程序设计 电子书,付源代码
  • js高级程序设计笔记

    2019-08-08 10:24:19
    js高级程序设计笔记 第三章基本概念 语法 区分大小写 b 正确的标识符 以字母,下划线,美元符号开头 以字母,下划线,美元符号,数字组成的 严格模式 1.不能定义eval或argument的变量,否则导致语法错误 2....

    js高级程序设计笔记

    大模块

    函数

    1. isFinite判断是否有限的数值

    2. isNaN()函数
      isNaN(“blue”) //true

    类型转换

    Number()函数

    Number("")    //0
    Number(undefined)    //NaN
    Number(null)    //0
    Number("Hello world!"); //NaN
    

    parseInt()

    parseInt(""); // NaN
    parseInt(22.5); // 22
    parseInt("1234blue"); // 1234
    parseInt("0xf"); // 15(十六进制数)
    

    在 ECMAScript 3 JavaScript 引擎中,"070"被当成八进制字面量,因此转换后的值是十进制的 56。而在 ECMAScript 5 JavaScript 引擎中,parseInt()已经不具有解析八进制值的能力

    parseInt("AF", 16); //175
    

    parseFloat()
    只解析十进制值
    它始终都会忽略前导的零

    parseFloat("22.34.5"); //22.34
    

    toString()
    null 和 undefined没有这方法

    var found = true; 
    var foundAsString = found.toString(); // 字符串"true"
    
    var num = 10; 
    alert(num.toString()); // "10" 
    alert(num.toString(2)); // "1010" 
    alert(num.toString(8)); // "12" 
    alert(num.toString(10)); // "10" 
    alert(num.toString(16)); // "a"
    

    String()

    如果值是 null,则返回"null";  如果值是 undefined,则返回"undefined"。

    var value1 = 10; 
    var value2 = true; 
    var value3 = null; 
    var value4; 
    alert(String(value1)); // "10" 
    alert(String(value2)); // "true" 
    alert(String(value3)); // "null" 
    alert(String(value4)); // "undefined"
    

    第三章基本概念

    语法

    • 区分大小写

    • b 正确的标识符

      • 以字母,下划线,美元符号开头
      • 以字母,下划线,美元符号,数字组成的
    • 严格模式

      1.不能定义eval或argument的变量,否则导致语法错误

      2.八进制字面量无效

      3.严格模式下不能使用with

      4.不能把函数和参数命名为eval和argument

      5.函数argument赋值不行

      6.set,get都要一起设置

    *数据类型

    简单数据类型:Null,Undefined,(这俩就是字面量,没方法), String,Number,Boolean(这三被默认成为对象赋予一些操作)

    对象类型: Array,Date,Function

    typeof

    是一个操作符,不是函数

    instance

    [1,2] instanceof Array //检测引用类型的类型
    

    特殊的点

    typeof null -> object
    typeof 正则表达式 ->safari和谷歌7  function  其他有可能是 objecj
    *在没有声明和没有初始化的变量typeof 都为undefined
    

    null类型

    null == undefined  ->true(null派生的)
    null === undefined -> false
    在变量没有值时可以先null
    

    Boolean类型

    字面值true,false区分大小写

    True,False为标识符

    转换为false的值
    “” , 0,NaN, null,undefined

    Number类型

    不同进制的表示

    var num = 07 //八进制(数字范围0-7,超出视为十进制)

    var num2 = 0xA //十六进制

    浮点数

    本身是整数的浮点数表示会被自动变成整数(1.0->1)
    var floatNum1 = 1.; // 小数点后面没有数字——解析为 1
    var floatNum2 = 10.0; // 整数——解析为 10
    科学计数法 3.14e7 || 3.14E7

    浮点数相加不要和整数比较相等

    0.1 + 0.2 == 0.3 //不等于
    0.1+0.2 == 0.30000000004
    

    数值范围

    最大值:Number.MAX_VALUE —> Infinity

    最小值:Number.MIN_VALUE —> -Infinity

    NaN(数值运算返回不是数值或其他的情况)

    自己不等于自己

    任何值和它操作都为NaN

    任何值除以0都为NaN,不影响其他代码执行

    String类型

    字符串是不可变的

    操作符

    位操作符

    第四章变量,作用域,内存问题

    基本类型引用类型特点

    • 基本类型不能添加属性,引用类型可以

      var person = new Object()
      person.name ="小花"
      var xiaoming = "小明"
      xiaoming.age = 18 //不行
      
    • var num1 = 5
      var num2 = num1 //num2=5,变量中存的就是5的值
      //引用类型变量存的是指针
      
    • 所有函数都是按值传递的

    执行环境及作用域

    作用域链

    保证执行环境对可访问的变量和函数的一个有序访问

    var color = "red"
    function yanse(){
    	var age = 18
    	function aa(){
    		var sex = "女"
    	}
    	return aa;
    }
    
    作用域链 :
    window---
            |
            |
            ------color
            |
            |
            ------yanse
            		  |
            		  |
            		  ------age
            		  |
            		  |
            		  ------aa
            		         |
            		         |
            		         -------sex
    

    延长作用域链

    with

    catch

    具体见作用域详解篇

    没有块级作用域

    当变量声明没有var时,就是全局变量

    垃圾收集

    • 标记清除
    • 引用计数

    解除引用,将不用的设为null,减少内存占用

    第五章引用类型

    Object类型

    var person = new Object()

    var person ={ //常用,属性也可以为字符串

    ​ name:“小明” ,

    ​ age:17

    }

    访问

    person.name//常用

    person[name] //可以用来访问属性是变量或者.格式错误的

    var pro = name
    person[pro]
    person["firsr name"]
    
    

    Array类型

    创建数组的形式

    • var arr = new Array()

      var arr  = new Array(20)
      var arr  = new Array("a","b","c")
      
      
    • var arr = [“a”,“b”,“c”] // []

    length作用

    可以控制数组的长度,向末尾添加项删减项

    var arr = [0,1,2]
    arr.length = 2      //arr ==>[0,1]
    arr.length = 5   //arr==>[0,1,undefined,undefined,undefined]
    
    

    数组方法

    函数 用法
    Array.isArray() 检测数组,即使多种全局执行环境也能分辨,instanceof适用于一个执行环境
    valueOf() var arr=[1,2,3] 转换方法(对象都具有的)
    toString() arr.toString()//1,2,3
    toLocaleString()
    join() arr.join("&")//1&2&3
    push() 数组元素的添加删除
    pop()
    shift()
    unshift()
    delete delete arr[0]//[null,2,3]
    reserve() 数组的排序
    sort() 默认按字符串的排序,从小到大,可以传入比较函数
    concat() var a = [1];
    a.concat(2,[3,4,6])
    //[1,2,3,4,6]
    数组操作方法
    slice() 第一个是起始位,第二个结束位,结束位不包括
    splice() 1.起始index
    2.删除个数
    3.插入的值
    a.splice(0,1,2,3,4)//[2,3,4]
    indexOf() 从前往后找
    var number = [1,2,3,4,5,6,6,4]
    number.indexOf(4,5)查找4的index,从5的位置开始找
    知道值找index方法
    lastIndexOf() 从后往前找
    every() 迭代 方法
    filter()
    forEach()
    map()
    some()
    reduce() 归并方法
    reduceRight()

    sort

    arr.sort(function(a,b){
    	return a - b //升序
    	retrun b - a //降序
    })
    
    

    迭代

    var num = [1,2,3,4,5,6]
    num.every(function(item,index,array){
    	return item>2
    })      //false 只要一个不符合就为false
     num.some(function(item,index,array){
     	return item>2
     })//true 一个符合就对
     num.filter(function(item,index,array){
     	return item>4
     }))  //[5,6]
     num.forEach(function(item,index,array){
     	
     }))
     num.map(function(item,index,array){
     	return item*2
     })//[2,4,6,8,10,12]
    
    

    归并

    var value = [1,2,3]
    value.reduce(function(pre,cur,index,array){
    	return pre+cur //6
    })
    
    

    Date类型

    创建日期对象

    var date = new Date()  //获取当前日期,时间  
    ---> Mon Jul 29 2019 09:12:00 GMT+0800 (中国标准时间)
    var date = new Date("2018-09-09") //获取特定日期时间
    ---> Sun Sep 09 2018 08:00:00 GMT+0800 (中国标准时间)
    
    

    Date.parse()

    输入的日期以当地时间为准

    将日期字符串转为日期毫秒数

    将毫秒数传入Date会被转为当前的日期格式

    var date = new Date(Date.parse(“May 25,2004”))

    ===

    var date = new Date(“May 25,2004”)

    传入的日期格式

    1. 6/13/2019
    2. January 12,2004
    3. Tue May 25 2004 00:00:00 GMT-0700
    4. YYYY-MM-DDTHH:mm:ss:sssZ//2019-07-19T19:23:10
    date = Date.parse("2018-09-09") //获取特定日期时间
    --->1536451200000
    
    
    date = Date.parse("2018-09-09") //获取特定日期时间
    console.log(date) --->1536451200000
    date = new Date(date) //获取特定日期时间
    console.log(date) --->Sun Sep 09 2018 08:00:00 GMT+0800 (中国标准时间)
    
    

    Date.UTC()

    输入的日期以GMT为准

    传入的日期格式

    Date.UTC(2018,0,19)//以逗号形式分割,月份比实际少一

    注意

    date = new Date(Date.UTC(2018,8,9))//GMT时间(加时差8小时)
    console.log(date)//Sun Sep 09 2018 08:00:00 GMT+0800 (中国标准时间)
    date = new Date(2018,8,9)//当地时间(不用转时区,是什么值就是什么值)
    console.log(date)//Sun Sep 09 2018 00:00:00 GMT+0800 (中国标准时间)
    
    

    Date.now()

    获取当前时间

    var start = +new Date() //会将日期格式转为毫秒数
    doSomething()
    var stop = +new Date()
    console.log(stop-start)  //可以替换now
    
    

    继承方法

    • toLocalString() //返回当地的日期格式,无地区

    • toString() //返回标准格式,有地区

    • valueOf()//返回毫秒,可直接比较日期

      var date = new Date(2018,09,09)
      var date2 = new Date(2018,09,08)
      date > date2
      
      

    日期格式化方法

    日期转字符串,输出格式因浏览器不同而不同

    • toDateString()
    • toTimeString()
    • toLocalDateString()
    • toLocalDateString()
    • toUTCtring()

    日期方法

    在这里插入图片描述

    RegExp 类型

    表达式

    var expression = / pattern / flags

    需要转义的元字符

    ( { \ ^ $ | ) ? * + . } ]

    不同的表现形式

    字面量形式:var reg = /[0-9]/g

    构造函数形式:var reg = new RegExp("[0-9]",g) //里面可有变量

    不同形式的表示

    字面量模式 构造函数(双重转义)
    /\ [bc \ ]at/ \ \ [bc\ \ ]at
    /\ \hello / \ \ \ hello

    字面量形式的会共享一个RegExp实例

    构造函数每次都是新建一个

    实例属性

    • global
    • ignoreCase
    • lastIndex //下一个匹配项的字符位置
    • multiline
    • sourse //转换为字面量字符串

    在这里插入图片描述

    实例方法

    • exec() //捕获组

      var a = reg.exec(str)
      a.index  //匹配项的位置
      a.input //应用正则表达式的字符串
      a[0]
      a[1]
      
      
    • test()

      var a = reg.exec(str) //匹配成功返回true
      
      

    构造函数属性

    短属性名要 pattern[$_]

    在这里插入图片描述

    存储捕获组的构造函数属性

    RegExp.$1~RegExp.$9

    Function类型

    函数声明与表达式

    声明会提升,表达式不会

    函数内部的属性

    • arguments //保存函数的参数

      arguments.callee 指向函数自己
      
      
    • this

    • caller 保存当前函数的函数的引用,全局为null

    函数的属性和方法

    • length
    • prototype
    • apply()
    • call()
    • bind()

    基本包装类型

    内置类型

    面向对象

    属性类型

    Object.defineProperty()

    可以修改属性特性

    • 数据属性

      • configurable //能否删除属性,修改属性的特性

        当修改成立false则不能重新配置回true

      • enumerable //属性是否可枚举

      • writable //能否修改属性的值

      • value //属性的值

    • 访问器属性 主要作用:设置一个属性的值另一个属性的值跟着变化

      • configurable

      • enumerable

      • get

      • set

            var person = {
                _year : 2019,  //要通过对象方法才能访问
                edition : 90,
            }
            Object.defineProperty(person,"year",{   //给year属性设置了get,set方法,在调取和修改会自动掉用这两个方法
                get: function(){
                    return this._year;
                },
                set:function(newValue){
                    this._year = newValue;
                    this.edition = 200+this._year;
                }
            })
            console.log(person.year)
            person.year = 100
            console.log(person.edition)
        
        

        原始方法设置get,set

            person.__defineGetter__("year",function(){
                return this._year;
            })
            person.__defineSetter__("year",function(newValue){
                this._year = newValue;
                this.edition = 200 + this._year;
            })
        
        

        Object.defineProperties()

        同时定义多个属性的特性

            Object.defineProperties(person,{
                year:{
                    get:function(){
                        return this._year;
                    },
                    set:function(newValue){
                        this._year = newValue;
                    }
                },
                edition:{
                    writable:false
                }
            })
        
        

    Object.getOwnPropertyDescriptor()

    Object.getOwnPropertyDescriptor(person,"year")
    //只能用于实例属性,要取得原型对象的属性特性,要直接在原型对象上掉用
    
    

    *创建对像的不同模式

    工厂模式

    构造函数模式

    实质就是new的时候先创建了一个对象,然后将对象的属性填好再返回

    不new创建时就是普通函数

    • 优点 可以判断对象的实例指向的对象类型

      person1 instanceof Person  //true
      person1 instanceof Object  //true
      person1.constructor == Person //实例构造器属性指向构造函数
      
      
    • 缺点 实例共有的函数和属性每次都要创建一遍

       function Person(name,age){ //构造函数
            this.name = name;
            this.age = age;
            this.run = function(){
                console.log("我会跑步");
            }
       }
       var person1 = new Person("小明",18)
       var person2 = new Person("小红",18)
       console.log(person1.name)
       console.log(person2.name)
    
    

    原型模式

    每个函数创建都有一个prototype属性

    Person.prototype原型对象,用于存对象共享的属性,方法

    Person.prototype.constructor指向Person

    *实例对象有一个__ proto __ 的属性,指向构造函数的原型对象,不是构造函数

    在这里插入图片描述

    判断实例对象的原型属性指向isPrototypeOf(),Object.getPrototypeOf()
    • isPrototypeOf()

      Person.prototype.isPrototypeOf(person1)
      
      
    • Object.getPrototypeOf() //可以直接取得实例对象的原型

      Object.getPrototype(person1)
      
      
    原型属性与实例属性hasOwnProperty()

    实例不可以重写原型属性,重名会覆盖

    但设置了重名属性,若重新设置为null,属性也不会访问原型中原来的属性

    要重新访问原型属性,要删除实例属性

    delete person1.name
    person1.name //原型属性
    
    

    判断原型属性与实例属性

    • hasOwnProperty() // 判断实例有的实例属性

    • 属性名 in 实例对象 // 只要有,不管原型还是实例都为true

      判断原型属性

      function hasPrototypeProperty(obj,proname){
      	return !obj.hasOwnProperty(pername) && (proname in obj)
      }
      
      
    获取实例属性
    • for in
    • Object.keys() //返回一个数组
    • Object.getOwnPropertynames() //可以得到所有属性
    更简单的原型语法

    原来

       function Person(name,age){
            this.name = name;
            this.age = age;
       }
       Person.prototype.run = function(){
        console.log("我会跑步");
       }
       Person.prototype.sleep = function(){
           console.log("我会睡觉")
       }
    
    

    现在

       function Person(name,age){
            this.name = name;
            this.age = age;
       }
       Person.prototype = {
            run:function run(){
            	console.log("我会跑步"),
           }
           sleep:function (){
           		console.log("我会睡觉")
       	   }
       }
    
    

    但是之前是原型对象自己创建的,现在重写

    Person.prototype.constructor不指向Person

    person1 instanseof 	Person  //true
    person1.constructor == Person //false
    
    

    所以

       Person.prototype = {
       	   constructor:Person,  //显性修改一下,但是constructor的Enumerable为true,本来应为false
           run:function run(){
            	console.log("我会跑步"),
           }
           sleep:function (){
           		console.log("我会睡觉")
       	   }
       }
    
    

    注意

    之前先生成实例再添加修改原型属性,实例都能改变,但重写之后不行,实例创建要在原型对象添加完之后

    之前

       function Person(name,age){
            this.name = name;
            this.age = age;
       }
       Person.prototype.run = function(){
        console.log("我会跑步");
       }
       var person1 = new Person("小明",18)
       Person.prototype.sleep = function(){
           console.log("我会睡觉")
       }
       person1.sleep() 
    
    

    之后

    在实例前重写原型对象,实例还能指向原型,之后重写,实例访问不到

       function Person(name,age){
            this.name = name;
            this.age = age;
       }
       Person.prototype = {
       	   constructor:Person,  //显性修改一下,但是constructor的Enumerable为true,本来应为false
           run:function run(){
            	console.log("我会跑步")
           },
           sleep:function (){
           		console.log("我会睡觉")
       	   }
       }
       var person1 = new Person("小明",18)  
       Person.prototype.eye = function(){
           console.log("我会眨眼")
       }
       person1.eye()
    
    

    在这里插入图片描述

    组合使用构造函数和原型模式

       function Person(name,age){
            this.name = name;
            this.age = age;
       }
       Person.prototype.run = function(){
        console.log("我会跑步");
       }
       Person.prototype.sleep = function(){
           console.log("我会睡觉")
       }
    
    

    动态原型模式

    function Person(name,age){
    	this.name = name;
    	this.age = age;
    	if(typeof this.say != "function"){
    		Person.prototype.say = function(){  //只能以这种方式创建
    			 pass
    		}
    	}
    }
    
    

    寄生构造函数模式

    可以对已存在的对象进行二次加工

    function Person(name,age){
    	var obj = new Object()
    	obj.name = name;
    	obj.age = age;
    	return obj;
    }
    var person = new Person("小明",17) //与工厂模式一样,就是用new来调用函数
    
    
    function SpecialArray(){
    	var arr = new Array()
    	arr.push.apply(arr,arguments)
    	arr.toPip = function(){
    		return this.join("|")
    	}
    	return arr
    }
    
    

    稳妥构造函数模式

    *继承

    原型链

    借用构造函数

    *组合继承

    原型式继承

    寄生式继承

    *寄生组合式继承

    函数表达式

    *递归的表示

    • 存在着函数名被修改的情况

      function factorial(num){
      	if(num <= 1){
      		return 1;
      	}else{
      		return num*factorial(num-1)
      	}
      }
      var another = factorial
      factorial = null
      another(4) //error
      
      
    • arguments.callee不会被修改,但是严格模式不能用

      function factorial(num){
      	if(num <= 1){
      		return 1;
      	}else{
      		return num*arguments.callee(num-1)
      	}
      }
      
      
    • 最佳

      var factorial = (function f(num){
      	if(num <= 1){
      		return 1;
      	}else{
      		return num*f(num-1)
      	}
      })
      var a = factorial(3)
      console.log(a)
      
      

    *闭包

    展开全文
  • 函数表达式JS定义函数的方式有两种:一种是函数声明,另一种就是函数表达式。函数声明的语法是这样的。function functionName(arg0, arg1, arg2) { //function body}函数声明,它的一个重要特征就是函数声明提升...

    函数表达式

    JS定义函数的方式有两种:一种是函数声明,另一种就是函数表达式。函数声明的语法是这样的。

    function functionName(arg0, arg1, arg2) {

    //function body

    }

    函数声明,它的一个重要特征就是函数声明提升(function declaration hoisting),意思是在执行代码之前会先读取函数声明。这就意味着可以把函数声明放在调用它的语句后面。

    sayHi()

    function sayHi(){

    alert("Hi!")

    }

    第二种创建函数的方式是使用函数表达式。函数表达式有几种不同的语法形式。下面是最常见的一种形式。

    var functionName = function(arg0, arg1, arg2){

    //function body

    }

    这种形式实际上跟常规的变量赋值没有区别, 就是创建一个变量, 然后将函数赋值给变量, 但是我们需要注意的是, 上面这种方式创建的函数function后面是没有跟函数名的, 我们叫这种函数为匿名函数或者拉姆达函数

    这里需要注意的是, 函数表达式没有函数提升

    函数表达式在递归中的应用

    假设我们有以下阶乘函数

    function factorial(num) {

    if(num < 1) {

    return 1

    }

    return num * factorial(num - 1)

    }

    这个函数看起来并没有什么问题, 但实际上确有一个隐藏的漏洞, 例如下面这种情况

    let fn = factorial

    factorial = null

    console.log(fn(5))

    上面的代码中, 我们先将factorial赋值给fn, 然后将factorial指向null, 当我们调用fn函数时, 递归函数在运行到return num * factorial(num - 1)这句, 由于factorial指向了null, 所以函数就会报错

    我们可以通过arguments.callee来解决这个问题, arguments.callee时一个指向正在执行的函数的指针, 所以可以实现递归调用

    function factorial(num) {

    if(num < 1) {

    return 1

    }

    return num * arguments.callee(num - 1)

    }

    但是, arguments.callee在es标准中已经渐渐被抛弃, 并且在严格模式下不能访问arguments.callee, 因此我们需要更好的方法来解决这个问题, 用函数表达式就是一个不错的选择

    let factorial = (function fn(num) {

    if(num < 1) {

    return 1

    }

    return num * fn(num - 1)

    })

    上面的代码我们创建了一个fn具名函数表达式, 然后将fn赋值给factorial, 这时候, 即使把函数赋值给另外的其他变量, fn仍然有效, 就避免了上面的问题

    闭包

    在《JavaScript高级程序设计》中, 对于闭包有着以下定义:

    闭包是指有权访问另一个函数作用域中的变量的函数。

    创建闭包的常见方式, 就是在一个函数内部创建另一个函数

    function fn() {

    var a = 1

    function foo() {

    console.log(a)

    }

    return foo

    }

    let bar = foo()

    bar() //2 闭包

    上面的代码就是一个典型的闭包, 我们在全局环境中访问到了fn函数作用域中的变量a, 为了了解清楚这种情况的细节, 我们必须要从作用域链入手

    当某个函数被调用时, 会创建一个执行环境(execution context)及相应的作用域链。然后, 使用arguments和其他命名参数的值来初始化函数的活动对象(activation object)。但在作用域链中, 外部函数的活动对象始终处于第二位, 外部函数的外部函数的活动对象处于第三位, 以此类推, 直到全局执行环境为止。

    函数执行时, 为了读取或者写入变量的值, 就需要在作用域链中查找变量, 假设我们有下面的例子

    function compare(value1, value2) {

    if(value1 < value2) {

    return -1

    }else if(value1 > value2) {

    return 1

    }else {

    return 0

    }

    }

    let result = compare(5, 10)

    当我们调用compare()时, 会创建一个compare的执行环境, 该环境会初始化一个包含arguments, value1和value2的活动对象。在创建执行环境的同时还会创建其作用域链, 这条作用域链包含两个执行环境, 一个compare执行环境和全局执行环境(全局执行环境中有一个包含result和compare的活动对象)。 compare执行环境处于作用域链的第一位, 全局执行环境则处在作用域链的第二位

    作用域链

    每个执行环境都有一个表示变量的对象——变量对象。全局环境的变量对象始终存在,而像compare()函数这样的局部环境的变量对象,则只在函数执行的过程中存在。在创建compare()函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中。当调用compare()函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对象构建起执行环境的作用域链。此后,又有一个活动对象(在此作为变量对象使用)被创建并被推入执行环境作用域链的前端。对于这个例子中compare()函数的执行环境而言,其作用域链中包含两个变量对象:本地活动对象和全局变量对象。显然,作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。

    无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量。一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。但是,闭包的情况又有所不同。

    假设我们有下面这个闭包

    function createComparisonFunction(propertyName) {

    return function(object1, object2){

    var value1 = object1[propertyName]

    var value2 = object2[propertyName]

    if (value1 < value2){

    return -1

    } else if (value1 > value2){

    return 1

    } else {

    return 0

    }

    }

    }

    var compareNames = createComparisonFunction('name')

    var result = compareNames({ name: 'Nicholas'}, { name: 'Greg' })

    //解除对匿名函数的引用(以便释放内存)

    compareNames = null

    当一个函数定义在另一个函数内部时, 该函数会将它的上层函数的活动对象也添加到他的作用域中, 因此上面这个例子中的匿名函数function(object1, object2){}的作用域链中, 不但包含它自己的活动对象, 还包含了createComparisonFunction()和全局活动对象, 这样, 匿名函数就可以访问createComparisonFunction()中定义的所有变量。更重要的一点, createComparisonFunction()函数在执行完毕后, 由于匿名函数的作用域链仍然引用这个活动对象, 所以这个活动对象不会被销毁, 知道匿名函数被销毁后, 它才会被销毁。

    由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多。虽然像V8等优化后的JavaScript引擎会尝试回收被闭包占用的内存,但请大家还是要慎重使用闭包。

    闭包和变量

    作用域链有一个副作用我们必须要注意, 就是闭包只能取得变量的最后一个值。因为闭包保存的时整个活动对象而不是某个特殊的变量值

    function fn() {

    var result = []

    for(val i=0; i<10; i++) {

    result[i] = function() {

    return i

    }

    }

    return result

    }

    这个函数会返回一个函数数组。表面上看,似乎每个函数都应该返自己的索引值,即位置0 的函数返回0,位置1 的函数返回1,以此类推。但实际上,每个函数都返回10。因为每个函数的作用域链中都保存着fn() 函数的活动对象, 所以它们引用的都是同一个变量i 。当fn()函数返回后,变量i 的值是10,此时每个函数都引用着保存变量i 的同一个变量对象,所以在每个函数内部i 的值都是10

    我们可以通过一个匿名函数强制让闭包符合预期行为

    function fn() {

    var result = []

    for(var i=0; i<10; i++) {

    result[i] = (function(num) {

    return function() {

    return num

    }

    })(i)

    }

    return result

    }

    在重写了前面的fn()函数后,每个函数就会返回各自不同的索引值了。在这个版本中,我们没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋给数组。这里的匿名函数有一个参数num,也就是最终的函数要返回的值。在调用每个匿名函数时,我们传入了变量i。由于函数参数是按值传递的,所以就会将变量i 的当前值复制给参数num。而在这个匿名函数内部,又创建并返回了一个访问num 的闭包。这样一来,result 数组中的每个函数都有自己num 变量的一个副本,因此就可以返回各自不同的数值了。

    关于this对象

    在闭包中使用this也需要特别注意, 比如下面的例子

    var name = "The Window";

    var object = {

    name : "My Object",

    getNameFunc : function(){

    return function(){

    return this.name

    }

    }

    }

    alert(object.getNameFunc()()); //"The Window"(在非严格模式下)

    按照作用域链来理解, 匿名函数应该会一层一层向上找活动对象, 那么这个this最终应该会指向其上层对象, 但是实际情况this似乎并没有指向其上层对象, 而是指向了window, 造成这个结果的原因是因为: 对于this和arguments这个两个特殊变量, 内部函数只会搜索到其自身的活动对象为止, 并不会通过作用域链访问其外层活动对象

    展开全文
  • js高级程序设计

    变量、作用域和内存问题

    基本类型和引用类型的值

    1.基本类型值(简单的数据段):undefined null string NAN number boolean
    2.引用类型值(可能由多个值构成的对象):obj

    在将一个值赋给变量时,解析器必须确定这个值是基本类型还是引用类型值。

    基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值。
    引用类型的值是保存在内存中的对象。与其他语言不同,js不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象(这种说法其实不严密,当复制和保存着对象的某个变量时,操作的是对象的引用。但在为对象添加属性时,操作的是实际的对象)。

    为此引用类型的值是按引用访问的。

    在很多语言中,字符串以对象的形式来表示,因此被认为是引用类型的。ECMAScipt放弃了这一传统。

    动态的属性

    定义基本类型值和引用类型值的方式是类似的:创建一个变量并为该变量赋值。但是,当这个值保存到变量中以后,对不同类型值可以执行的操作却大相径庭。
    对于引用类型的值,我们可以为其添加属性和方法,也可以改变和删除其属性和方法,例子如下:

        var person = new Object();
        person.name = "Amy";
        console.log(person.name);  //Amy

    以上代码创建来一个对象并将其保存在了变量person中,然后,我们为该对象添加了一个名为name的属性,如果对象不被销毁或者这个属性不被删除,则这个属性将一直存在。

    但是我们不能给基本类型的值添加属性,尽管这样做并不会导致任何错误。例如:

        var name = "Amy";
        name.age = 23;
        console.log(name.age); //undefined

    在这个例子中,我们为字符串name定义了一个名为age的属性,并为该属性赋值23。但是在下一行访问这个属性时,发现该属性不见了。说明只能给引用类型值动态添加属性,以便将来使用。

    复制变量值。

    除了保存的方式不同之外,在从一个变量向另一个变量复制基本类型和引用类型时,也存在不同。
    如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上,来看一个例子:
    
    ```
        var num1 = 5;
        var num2 = num1;
    ```
    
    在此 ,num1中保存的值是5,当使用num1的值来初始化num2时,num2中也保存来值5,但是num2中的5与num1中的5是完全独立的。该值只是num1中5的一个副本,此后,这两个变量可以参与任何操作而不会互相影响,下图形象地展示来复制基本类型值的过程:
    复制前的变量对象
    | 变量| 值 |
    | num1| 5(Number类型)|
    
    复制后的变量对象
    | 变量 | 值 |
    |num2  | 5 (Number类型)|
    |num1  | 5(Number类型)|
    
    当从一个变量向另一个变量复制引用类型的值的时候,同样也会存储在变量对象的值复制一份放到为新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象。复制操作结束厚,两个变量实际上将应用同一个对象,因此改变其中一个变量,就会影响另一个变量,例子如下所示:
    
        ```
            var obj1 = new Object();
            var obj2 = obj1;
            obj.name = "Amy";
            console.log(obj2.name);//Amy
        ```
    
        首先,变量obj1保存了一个对象的新实例。然后,这个值被复制到了obj2中,换句话说,obj1和obj2都指向同一个对象。这样,当为obj1添加name属性后,可以通过obj2来访问这个属性。因为这两个变量引用的都是同一个变量。
    

    传递参数

    ECMAScript中,所有函数的参数都是按值传递的。
    也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。
    基本类型值的传递如同基本类型变量的复制一样,而引用类型值的传递,则如同引用类型变量的复制一样。
    有不少开发人员在这一点上可能会感到困惑,因为访问变量有按值和按引用两种方式,而参数只能按值传递。
    
    在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量(即命名参数,或者用ECMAScript的概念来说,就是arguments对象中的一个元素)。在向参数传递引用类型的值时,会把这个值在内存中的地址复制给局部变量,因为这个局部变量的变化会反应在函数的外部。请看下面的例子:
    
    ```
        function addTen(num){
            num += 10;
            return num;
        }
    
        var count = 10;
        var result = addTen(count);
        console.log(count);//10
        console.log(result);//20
    ```
    
    这里的函数addTen()有一个参数num,而参数实际上是函数的局部变量。在调用这个函数时,变量count作为参数被传递给函数,这个变量的值是10。于是,数值10被复制给参数num,以便在addTen()中使用。在函数内部,参数num的值被加上了10,但是这一变化不会影响函数外部的count变量。参数num与count互不相识,它们仅仅是具有相同的值,假如num是按引用传递的话,那么变量count的值也将变为20,从而反映函数内部的修改。当然,使用数值等基本类型值来说明按值传递参数比较简单。但是如果使用对象,那问题就不怎么好理解来,再举一个例子:
    ```
        function setName(obj){
            obj.name = "Amy";
        }
        var person = new Object();
        setName(person);
        console.log(person.name);// Amy
    ```
    
    以上代码中创建一个对象,并将其保存在来变量person中,然后这个变量被传递到setName()函数中之后就被复制给了obj,在这个函数内部,obj和person引用的是同一个对象。换句话说,即使这个变量是按值传递的,obj也会按照引用来访问同一对象。于是,当在函数内部为obj添加name属性后,函数外部的person也将有所反映;因为person指向的对象在堆内存中只有一个,而且是全局对象。有很多开发人员错误的认为,在局部作用域中修改的对象会在全局作用域中反映出来,就说明参数是按引用传递的。为了证明对象是按值传递的,我们再看一看下面这个经过修改的例子:
    ```
            function setName(obj){
                obj.name = "Amy";
                obj = new Object();
                obj.name = "Shieh";
            }
            var person = new Object();
            setName(person);
            console.log(person.name);// Amy
        ```
    
    这个例子与前一个例子的唯一区别就是在setName()函数中添加来两行代码。在把person传递给setName()后,其name属性被设置为"Amy";然后又将一个新对象赋值给变量obj,同时将其name属性设置为"Shieh",如果person是按引用传递的,那么person就会被自动修改为指向其name属性值为"Shieh"的新对象。但是当接下来再访问person.name时,显示的仍然是"Amy",这说明即使在函数内部修改来参数的值,但原始的引用仍然保持不变,实际上,当在函数内部重写obj时,这个变量引用的就是一个局部对象了,而这个局部对象会在函数执行完毕后立即被销毁。
    
    可以把ECMAScript函数的参数想象成局部变量。
    

    检测类型

    要检测一个变量是不是基本数据类型?typeof操作符是最佳的工具。说得更具体一点,typeof操作符是确定一个变量是字符串、数值、布尔值,还是undefined的最佳工具。如果变量的值是一个对象或null,则typeof操作符会像下面例子中所示的那样返回"object":
    `
        var s = "Amy";
        var b = true;
        var i = 22;
        var u;
        vau n = null;
        var o = new Objec();
    
        alert(typeof s); // string
        alert(typeof b); //boolean
        alert(typeof i); //number
        alert(typeof u); //undefined
        alert(typeof n); //object
        alert(typeof o); //object
    `
    
    虽然在检测基本数据类型时typeof是非常得力的助手,但是在检测引用类型的值时,这个操作符的用处不大。通常我们并不想知道某个值是对象,而是想知道它是什么类型的对象。为此,ECMAScript提供了instanceof操作符,其语法如下所示:
    `
        result = variable instanceof constructor
    `
    如果变量是给定引用(根据它的原型链来识别;第6章将介绍原型链)的示例,那么instanceof操作符就会返回true,请看下面的例子:
    `
        console.log(person instanceof Object);
        console.log(color instanceof Array);
        console.log(pattern instanceof RegExp);
    `
    
    根据规定,所有引用类型的值都是Object的实例。因此,在检测一个引用类型值和Object构造函数时,instanceof操作符始终会返回true。当然如果使用instanceof操作符检测基本类型的值,该操作符始终会返回false,因为基本类型不是对象。
    
    使用typeof操作符检测函数时,该操作符会返回"function"。
    在safari 5 及之前版本和Chrome7及之前版本中使用typeof检测正则表达式时,由于规范的原因,这个操作符也返回"function"。ECMA-262规定任何在内部实现[[Call]]方法的对象都应该在应用typeof操作符时返回"function"。由于上述浏览器中的正则表达式也实现了这个方法,因此对正则表达式应用typeof会返回"function"。在IE和firefox中,对正则表达式应用typeof会返回"object"。
    

    执行环境及作用域

    执行环境(execution context,为简单起见,有时候也称为“环境”)是js中最为重要的一个概念。执行环境定义了变量或函数有权访问的其他数据,决定了它们个自己的行为。每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台用到它。
    
    全局执行环境是最外围的一个执行环境。根据ECMAScript实现所在的宿主环境不同,表示执行环境的对象也不一样。在web浏览器中,全局执行环境被认为是window对象(第7章将详细讨论),因此所有全局变量和函数都是作为window对象的属性和方法创建的。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之被销毁(全局执行环境直到应用程序退出,例如关闭网页或浏览器时才会被销毁)。
    
    每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行后,栈将其环境弹出,把控制权返回给之前的执行环境。ECMAScript程序中的执行流正是由这个方便的机制控制着。
    当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对象在最开始时只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自于下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。
    标识符解析是沿着作用域链一级一级地搜索标识符的过程,搜索过程始终从作用域链的前端开始,然后逐渐地向后回溯,直至找到标识符为止(如果找不到标识符,通常会导致错误发生)。
    请看下面的实例代码:
    `
        var color = "blue";
    
        function changeColor(){
            if(color === "blue"){
                color = "red";
            }else{
                color = "blue";
            }
        }
    
        changeColor();
        console.log("Color is now "+ color); //red
    `
    在这个简单的例子中,函数changeColor()的作用域链包含两个对象:它自己的变量对象(其中定义着arguments对象)和全局环境的变量对象。可以在函数函数内部访问变量color,就是因为可以在这个作用域链中找到它。
    
    此外,在局部作用域中定义的变量可以在局部环境中与全局变量互换使用,如下面这个例子所示:
    
    `
        var color = "blue";
        function changeColor(){
            var anotherColor = "red";
    
            function swapColor(){
                var tempColor = anotherColor;
                anotherColor = color;
                color = tempColor;
                //这里可以访问 color,anotherColor,tempColor
            }
            //这里可以访问color和anotherColor,但不能访问tempColor
            swapColor();
        }
        changeColor();
        //这里只能访问colors
    `
    
    以上代码共涉及3个执行环境:全局环境、changeColor()的局部环境和swapColor()的局部环境。全局环境中有一个变量color和一个函数changeColor()。changColor()的局部环境中有一个名为anotherColor的变量和一个名为swapColor()的函数,但它也可以访问全局环境中的变量color。swapColor()的局部环境中有一个变量tempColor,该变量只能在这个环境中访问到。无论全局环境还是changeColor()的局部变量都无权访问tempColor。然而在swapColor()内部则可以访问其他两个环境中的所有变量,因为那两个环境是它的父执行环境。
    
    内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。这些环境之间的联系是线性、有次序的。每个环境都可以向上搜索作用域链,以查询变量和函数名;但任何环境都不能通过向下搜索作用域链而进入另一个执行环境。对于这个例子中的swapColor()而言,其作用域链中包含3个对象:swapColor()的变量对象、changeColor()的变量对象和全局变量对象。swapColor的局部环境开始会先在自己的变量对象中搜索变量和函数名,如果搜索不到则再搜索上一级作用域链。changeColor()的作用域链中只包含两个对象:它自己的变量对象和全局变量对象。这也就是说,它不能访问swapColor()的环境。
    
    
    函数参数也被当做变量来对待,因此其访问规则与执行环境中的其他变量相同。
    

    延长作用域链

    虽然执行环境的类型总共只有两种——全局和局部(函数),但还是有其他办法来延长作用域链。这么说是因为有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。在这两种情况下会发生这种现象。具体来说,就是当执行流进入下列任何一个语句时,作用域链就会得到加长:
    
    1.try-catch语句的catch块。
    2.with语句。
    
    这两个语句都会在作用域链的前端添加一个变量对象。
    对with语句来说,会将制定的对象添加到作用域链中。
    对catch语句来说,会创建一个新的变量对象,其实包含的是被抛出的错误对象的声明。
    
    下面看一个例子:
    `
        function buildUrl(){
            var qs = "?debug=true";
    
            with(location){
                var url = href + qs;
            }
    
            return url;
        }
    `
    
    在此,with语句接收的是location对象,因此其变量对象中就包含了location对象的所有属性和方法,而这个变量被添加到了作用域链的前端。buildUrl()函数中定义了一个变量qs。当在with语句中引用变量href时(实际引用的是location.href),可以在当前执行环境的变量对象中找到。当引用变量qs时,引用的则是在buildUrl()中定义的那个变量,而该变量位于函数环境的变量对象中。至于with语句内部,则定义了一个名为url的变量,因而url就成了函数执行环境的一部分,所有可以作为函数的值返回。
    
    在IE8及之前版本的js实现中,存在一个与标准不一致的地方,既在catch语句中补捕获的错误对象会被添加到执行环境的变量对象,而不是catch语句的变量对象中,换句话说,即使在catch块的外部也可以访问到错误对象。IE9修复了这个问题。
    

    ###没有块级作用域
    js没有块级作用域经常会导致理解上的困惑。在其他类C的语言中,由花括号封闭的代码块都有自己的作用域(如果用ECMAScript的话来讲就是它们自己的执行环境),因而支持根据条件来定义变量。例如,下面的代码在js中并不会得到想要的结果。

    if(true){
    var color = "blue";
    }
    alert(color); //"blue"

    这里是在一个if语句中定义了变量color,如果是在C、C++或java中,color会在if语句执行完毕后被销毁。但在js中,if语句中的变量申明会将变量添加到当前的执行环境(在这里是全局环境)中。在使用for语句时尤其要牢记这一差异,例如:
    
    `
        for(var i = 0; i < 10; i++){
            doSomething(i);
        }
        alert(i); //10
    `
    对于有块级作用域的语言来说,for语句初始化变量的表达式所定义的变量,只会存在于循环的环境中。而对于js来说,由for语句创建的变量i即使在for循环执行结束后,也依然会存在于循环外部的执行环境中。
    #####1.声明变量
        使用var声明的变量会自动被添加到最接近的环境中。在函数内部,最接近的环境就是函数的局部环境;在with语句中,最近的环境是函数环境。如果初始化变量时没有使用var声明,该变量会自动被添加到全局环境,如下所示:
        `
            function add(num1,num2){
                var sum = num1+num2;
                return sum;
            }
    
            var result = add(10,20) //30
            alert(sum);//由于sum不是有效的变量,因此会导致错误。
        `
    
    以上代码中的函数add()定义了一个名为sum的局部变量,该变量包含加法操作的结果。虽然结果值从函数中返回了,但变量sum在函数外部是访问不到的。如果省略这个例子中的var关键字,那么当add()执行完毕后,sum也将访问到:
    
        `
            function add(num1,num2){
                sum = num1+num2;
                return sum;
            }
    
            var result = add(10,20) //30
            alert(sum);//30
        `
    
        在这个例子中的变量sum在被初始化赋值时没有使用var关键字。于是当调用完add()之后,添加到全局环境中的变量sum将继续存在;即使函数已经执行完毕,后面代码依旧可以访问它。
    
        在编写js代码的过程中,不声明而直接初始化变量是一个常见的错误做法,因为这样写可能会导致意外。我们建议在初始化变量之前一定要先声明,这样就可以避免类似问题。在严格模式下,初始化未经声明的变量会导致错误。
    
    展开全文
  • JS高级程序设计(第3版),前端必备书籍,非扫描pdf版,图文清晰,放大不失真,良心资源,欢迎下载!

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,637
精华内容 1,454
关键字:

js高级程序设计