js 的作用域
var scope = 'global';
var f = function() {
console.log(scope);// 输出 undefined
var scope = 'f';
}
f();
上面代码和预想的不一样,没有输出 global,而是undefined,这是为什么呢? 这是 JavaScript 的一个特性,按照作用域搜索顺序,在 console.log 函数访问 scope 变 量时,JavaScript 会先搜索函数 f 的作用域,恰巧在 f 作用域里面搜索到 scope 变量, 所以上层作用域中定义的 scope 就被屏蔽了,但执行到 console.log 语句时,scope 还 没被定义,或者说初始化,所以得到的就是 undefined 值了。
我们还可以从另一个角度来理解:对于开发者来说,在访问未定义的变量或定义了但没 有初始化的变量时,获得的值都是 undefined。于是我们可以认为,无论在函数内什么地 方定义的变量,在一进入函数时就被定义了,但直到 var 所在的那一行它才被初始化,所 以在这之前引用到的都是 undefined 值。(事实上,JavaScript 的内部实现并不是这样,未 定义变量和值为 undefined 的变量还是有区别的。)
闭包
闭包的严格定义是“由函数(环境)及其封闭的自由变量组成的集合体”。通俗地讲,JavaScript 中每个的函数都是一个闭包,但通常意义上嵌套的函数更能够体现出闭包的特性。
let generateClosure = function(){
let count = 0
let get = function(){
count++
return count
}
return get
}
var counter = generateClosure()
console.log(counter())
console.log(counter())
console.log(counter())
理解:闭包是能够读取其他函数内部变量的函数
实现:定义在一个函数内部的函数
用途:
- 可以读取函数内部的变量
- 让这些变量的值始终保持在内存中
缺点:
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
- 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
对象
JavaScript 中的对象实际上就是一个由属性组成的关联数组,属性由名称和值组成,值 的类型可以是任何数据类型,或者函数和其他对象。
创建对象
// 方法一
var foo = {}
// 方法二: 显式地创建一个对象
var foo = new Object()
// 使用对象初始化器创建对象
var foo = {
'prop1': 'bar'
}
创建对象(new Object
/ {}
)的区别
前者是用构造函数实例化对象,后者是直接创建JSON对象,后者的初始化比较方便,可以在初始化的时候同时赋值。
添加属性
// 方法一:点运算符
foo.prop1 = 'bar'
// 方法二:关联数组
foo['prop1'] = 'bar'
在 JavaScript 中,使用句点运算符和关联数组引用是等价的,也就是说任何对象(包括 this 指针)都可以使用这两种模式。使用关联数组的好处是,在我们不知道对象的属性名 称的时候,可以用变量来作为关联数组的索引。
var some_prop = 'prop2'
foo[some_prop] = false
上下文对象
call
和 apply
call 和 apply 的功能是 以不同的对象作为上下文来调用某个函数。简而言之,就是允许一个对象去调用另一个对象 的成员函数。灵活使用 call 和 apply 可以节省不少时间,在后面我们可以看到,call 可以用于实现对象的继承。
call 和 apply 的功能是一致的,两者细微的差别在于 call 以参数表来接受被调用函 数的参数,而 apply 以数组来接受被调用函数的参数。call 和 apply 的语法分别是:
func.call(thisArg[, arg1[, arg2[, ...]]])
func.apply(thisArg[, argsArray])
其中,func 是函数的引用,thisArg 是 func 被调用时的上下文对象,arg1、arg2 或 argsArray 是传入 func 的参数。我们以下面一段代码为例介绍 call 的工作机制:
var someuser = {
name: 'byvoid',
display: function(words) {
console.log(this.name + ' says ' + words);
}
}
var foo = {name: 'foobar'}
someuser.display.call(foo, 'hello'); // 输出 foobar says hello
bind
前面说过,可以用 call 或 apply 方法改变被调用函数的上下文。但如果重复使用会不方便,因为每次都要把上下文对象作为参数传递,而且还会使代码变得不直观。针对这种情况,我们可以使用bind
方法来永久地绑定函数的上下文,使其无论被谁调用,上下文都是固定的。bind 语法如下:
func.bind(thisArg[, arg1[, arg2[, ...]]])
其中 func 是待绑定函数,thisArg 是改变的上下文对象,arg1、arg2 是绑定的参 数表。bind 方法返回值是上下文为 thisArg 的 func。通过下面例子可以帮你理解 bind 的使用方法:
var someuser = {
name: 'byvoid',
func: function() {
console.log(this.name);
}
}
var foo = {name: 'foobar' }
foo.func = someuser.func;
foo.func(); // 输出 foobar
foo.func1 = someuser.func.bind(someuser);
foo.func1(); // 输出 byvoid
func = someuser.func.bind(foo);
func(); // 输出 foobar
func2 = func;
func2(); // 输出 foobar
上面代码直接将 foo.func 赋值为 someuser.func,调用 foo.func() 时,this指 针为 foo,所以输出结果是 foobar。foo.func1 使用了 bind 方法,将 someuser 作 为this指针绑定到 someuser.func,调用 foo.func1() 时,this指针为 someuser, 所以输出结果是 byvoid。全局函数 func 同样使用了 bind 方法,将 foo 作为 this 指 针绑定到 someuser.func,调用 func() 时,this 指针为 foo,所以输出结果是 foobar。 而 func2 直接将绑定过的 func 赋值过来,与 func 行为完全相同。
####使用 bind 绑定参数表
bind 方法还有一个重要的功能:绑定参数表
var person = { name: 'byvoid',
says: function(act, obj) {
console.log(this.name + ' ' + act + ' ' + obj)
}
}
person.says('loves', 'diovyb'); // 输出 byvoid loves diovyb
byvoidLoves = person.says.bind(person, 'loves'); byvoidLoves('you'); // 输出 byvoid loves you
可以看到,byvoidLoves 将 this 指针绑定到了 person,并将第一个参数绑定到 loves,之后在调用 byvoidLoves 的时候,只需传入第三个参数。这个特性可以用于创建 一个函数的“捷径”,之后我们可以通过这个“捷径”调用,以便在代码多处调用时省略重 复输入相同的参数。
##数组
数组合并:
- concat
code: arr1.concat( arr2 )
- apply
code: Array.prototype.push.apply(arr1, arr2)
- 循环遍历
var arr1 = [1,2,3], arr2 = [4,5,6];
====================
concat:
var arr3 = arr1.concat(arr2);
console.log(arr3) // 1,2,3,4,5,6
console.log(arr1) // 1,2,3
====================
apply:
方法一:Array.prototype.push.apply(arr1,arr2)
方法二:arr1.push.apply(arr1,arr2)
console.log(arr1) //1,2,3,4,5,6
console.log(arr2) //4,5,6
====================
##对象
对象合并:
- $.extend()
- Obj.assign()
- 遍历赋值
var obj1= {'a': 1};
var obj2= {'b': 2, 'c': 3};
var c = $.extend(obj1, obj2);
判断 数组/对象: Object.prototype.toString
在JavaScript
里使用 typeof 来判断数据类型,只能区分基本类型,即 “number”,”string”,”undefined”,”boolean”,”object” 五种。而对于数组、函数、对象来说,其关系错综复杂,使用 typeof 都会统一返回 “object” 字符串。
Object.prototype.toString
的行为:首先,取得对象的一个内部属性[[Class]],然后依据这个属性,返回一个类似于"[object Array]"的字符串作为结果(看过ECMA标准的应该都知道,[[]]用来表示语言内部用到的、外部不可直接访问的属性,称为“内部属性”)。利用这 个方法,再配合call,我们可以取得任何对象的内部属性[[Class]],然后把类型检测转化为字符串比较,以达到我们的目的。
function isArrayFn (o) {
return Object.prototype.toString.call(o) === '[object Array]';
}