1.基本数据类型
答:js有六大数据类型,其中包括五种基本数据类型和一种复杂类型。如下:
五种基本数据类型:undefined null Boolean Number String
复杂类型为:Object
ES6新出的类型:Symbol
symbol:
1)http://es6.ruanyifeng.com/#docs/symbol
2)用于声明属性名是独一无二的,故任何两个相同的属性名无论是否值相同都是不能等同的。还有,symbol是类型,而不是对象,不能通过new实例化。
常见注意点:
1)undefined为变量已声明但未定义
2)null表示空对象指针,如:
var car=null;
console.log(typeof car );//null
3)undefined是派生自null的,所以
undefined==null;//true
4)对于Boolean
var foo;
alert(!foo);//undifined情况下,一个感叹号返回的是true;
alert(!goo);//null情况下,一个感叹号返回的也是true;
var o={flag:true};
var test=!!o.flag;//等效于var test=o.flag||false;
alert(test);
5)Number中
alert(NaN == NaN); //false
2,原型prototype _proto_ 和原型链
1)prototype和_proto_的区别
var a = {};
console.log(a.prototype); //undefined
console.log(a.__proto__); //Object {}
var b = function(){}
console.log(b.prototype); //b {}
console.log(b.__proto__); //function() {}
2)_proto_指向谁
_proto_的指向取决于对象创建时的实现方式。以下图标列出了三种常见方式创建对象后,_proto_分别指向谁
/*1、字面量方式*/
var a = {};
console.log(a.__proto__); //Object {}
console.log(a.__proto__ === a.constructor.prototype); //true
/*2、构造器方式*/
var A = function(){};
var a = new A();
console.log(a.__proto__); //A {}
console.log(a.__proto__ === a.constructor.prototype); //true
/*3、Object.create()方式*/
var a1 = {a:1}
var a2 = Object.create(a1);
console.log(a2.__proto__); //Object {a: 1}
console.log(a.__proto__ === a.constructor.prototype); //false(此处即为图1中的例外情况)
3)原型链
由于_proto_是任何对象都有的属性,而js万物皆有对象,所以会形成一条_proto_连起来的链条,递归访问_proto_必须最终到头,并且值为null
当js引擎找对象的属性时,先查找对象本身是否存在该属性,如果不存在,会在原型链上查找,但不会查找自身的prototype
var A = function(){};
var a = new A();
console.log(a.__proto__); //A {}(即构造器function A 的原型对象)
console.log(a.__proto__.__proto__); //Object {}(即构造器function Object 的原型对象)
console.log(a.__proto__.__proto__.__proto__); //null
3.闭包
作用域
先来谈谈变量的作用域
变量的作用域无非就是两种:全局变量和局部变量。
全局作用域:
最外层函数定义的变量拥有全局作用域,即对任何内部函数来说,都是可以访问的:
<script>
var outerVar = "outer";
function fn(){
console.log(outerVar);
}
fn();
</script>
局部作用域:
和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,而对于函数外部是无法访问的,最常见的例如函数内部
<script>
function fn(){
var innerVar = "inner";
}
fn();
console.log(innerVar);
</script>
需要注意的是,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!
<script>
function fn(){
innerVar = "inner";
}
fn();
console.log(innerVar);
</script>
再来看一个代码:
<script>
var scope = "global";
function fn(){
console.log(scope);
var scope = "local";
console.log(scope);
}
fn();
</script>
很有趣吧,第一个输出居然是undefined,原本以为它会访问外部的全局变量(scope=”global”),但是并没有。这可以算是javascript的一个特点,只要函数内定义了一个局部变量,函数在解析的时候都会将这个变量“提前声明”:
<script>
var scope = "global";
function fn(){
var scope;
console.log(scope);
scope = "local";
console.log(scope);
}
fn();
</script>
然而,也不能因此草率地将局部作用域定义为:用var声明的变量作用范围起止于花括号之间。
javascript并没有块级作用域
那什么是块级作用域?
像在C/C++中,花括号内中的每一段代码都具有各自的作用域,而且变量在声明它们的代码段之外是不可见的,比如下面的c语言代码:
for(int i = 0; i < 10; i++){
//i的作用范围只在这个for循环
}
printf("%d",&i);//error
但是javascript不同,并没有所谓的块级作用域,javascript的作用域是相对函数而言的,可以称为函数作用域:
<script>
for(var i = 1; i < 10; i++){
}
console.log(i);
</script>
作用域链(Scope Chain)
那什么是作用域链?
我的理解就是,根据在内部函数可以访问外部函数变量的这种机制,用链式查找决定哪些数据能被内部函数访问。
想要知道js怎么链式查找,就得先了解js的执行环境
执行环境(execution context)
每个函数运行时都会产生一个执行环境,而这个执行环境怎么表示呢?js为每一个执行环境关联了一个变量对象。环境中定义的所有变量和函数都保存在这个对象中。
全局执行环境是最外围的执行环境,全局执行环境被认为是window对象,因此所有的全局变量和函数都作为window对象的属性和方法创建的。
js的执行顺序是根据函数的调用来决定的,当一个函数被调用时,该函数环境的变量对象就被压入一个环境栈中。而在函数执行之后,栈将该函数的变量对象弹出,把控制权交给之前的执行环境变量对象。
举个例子:
<script>
var scope = "global";
function fn1(){
return scope;
}
function fn2(){
return scope;
}
fn1();
fn2();
</script>
上面代码执行情况演示:
了解了环境变量,再详细讲讲作用域链。
当某个函数第一次被调用时,就会创建一个执行环境(execution context)以及相应的作用域链,并把作用域链赋值给一个特殊的内部属性([scope])。然后使用this,arguments(arguments在全局环境中不存在)和其他命名参数的值来初始化函数的活动对象(activation object)。当前执行环境的变量对象始终在作用域链的第0位。
以上面的代码为例,当第一次调用fn1()时的作用域链如下图所示:
(因为fn2()还没有被调用,所以没有fn2的执行环境)
可以看到fn1活动对象里并没有scope变量,于是沿着作用域链(scope chain)向后寻找,结果在全局变量对象里找到了scope,所以就返回全局变量对象里的scope值。
标识符解析是沿着作用域链一级一级地搜索标识符地过程。搜索过程始终从作用域链地前端开始,然后逐级向后回溯,直到找到标识符为止(如果找不到标识符,通常会导致错误发生)—-《JavaScript高级程序设计》
那作用域链地作用仅仅只是为了搜索标识符吗?
再来看一段代码:
<script>
function outer(){
var scope = "outer";
function inner(){
return scope;
}
return inner;
}
var fn = outer();
fn();
</script>
outer()内部返回了一个inner函数,当调用outer时,inner函数的作用域链就已经被初始化了(复制父函数的作用域链,再在前端插入自己的活动对象),具体如下图:
一般来说,当某个环境中的所有代码执行完毕后,该环境被销毁(弹出环境栈),保存在其中的所有变量和函数也随之销毁(全局执行环境变量直到应用程序退出,如网页关闭才会被销毁)
但是像上面那种有内部函数的又有所不同,当outer()函数执行结束,执行环境被销毁,但是其关联的活动对象并没有随之销毁,而是一直存在于内存中,因为该活动对象被其内部函数的作用域链所引用。
具体如下图:
outer执行结束,内部函数开始被调用
outer执行环境等待被回收,outer的作用域链对全局变量对象和outer的活动对象引用都断了
像上面这种内部函数的作用域链仍然保持着对父函数活动对象的引用,就是闭包(closure)
闭包
闭包有两个作用:
第一个就是可以读取自身函数外部的变量(沿着作用域链寻找)
第二个就是让这些外部变量始终保存在内存中
关于第二点,来看一下以下的代码:
<script>
function outer(){
var result = new Array();
for(var i = 0; i < 2; i++){
result[i] = function(){
return i;
}
}
return result;
}
var fn = outer();
console.log(fn[0]());
console.log(fn[1]());
</script>
返回结果很出乎意料吧,你肯定以为依次返回0,1,但事实并非如此
来看一下调用fn[0]()
的作用域链图:
可以看到result[0]函数的活动对象里并没有定义i这个变量,于是沿着作用域链去找i变量,结果在父函数outer的活动对象里找到变量i(值为2),而这个变量i是父函数执行结束后将最终值保存在内存里的结果。
由此也可以得出,js函数内的变量值不是在编译的时候就确定的,而是等在运行时期再去寻找的。
那怎么才能让result数组函数返回我们所期望的值呢?
看一下result的活动对象里有一个arguments,arguments对象是一个参数的集合,是用来保存对象的。
那么我们就可以把i当成参数传进去,这样一调用函数生成的活动对象内的arguments就有当前i的副本。
改进之后:
<script>
function outer(){
var result = new Array();
for(var i = 0; i < 2; i++){
function arg(num){
return num;
}
result[i] = arg(i);
}
return result;
}
var fn = outer();
console.log(fn[0]);
console.log(fn[1]);
</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
虽然的到了期望的结果,但是又有人问这算闭包吗?调用内部函数的时候,父函数的环境变量还没被销毁呢,而且result返回的是一个整型数组,而不是一个函数数组!
确实如此,那就让arg(num)函数内部再定义一个内部函数就好了:
这样result返回的其实是innerarg()函数
<script>
function outer(){
var result = new Array();
for(var i = 0; i < 2; i++){
function arg(num){
function innerarg(){
return num;
}
return innerarg;
}
result[i] = arg(i);
}
return result;
}
var fn = outer();
console.log(fn[0]());
console.log(fn[1]());
</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
当调用outer,for循环内i=0时的作用域链图如下:
由上图可知,当调用innerarg()时,它会沿作用域链找到父函数arg()活动对象里的arguments参数num=0.
上面代码中,函数arg在outer函数内预先被调用执行了,对于这种方法,js有一种简洁的写法
function outer(){
var result = new Array();
for(var i = 0; i < 2; i++){
result[i] = function(num){
function innerarg(){
return num;
}
return innerarg;
}(i);
}
return result;
}
关于this对象
关于闭包经常会看到这么一道题:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());
《javascript高级程序设计》一书给出的解释是:
this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象调用时,this等于那个对象。不过,匿名函数具有全局性,因此this对象同常指向window
4,js中this指向问题
js中this指向问题老生常谈的问题了,在这里说一下我所理解的层面的this。
this 指的是当前对象,如果在全局范围内使用this,则指代当前页面window;如果在函数中使用this,则this指代什么是根据当前函数是在什么对象上调用。我们可以使用call和apply改变函数中this的具体指向。
console.log(this === window) // true
console.log(window.alert === this.alert) // true
console.log(this.parseInt("021",10)) // 21
parseInt(string,radix);
// 当只使用一个参数时,我们都知道是将其转为整数;
// radix 取值为 2~36,表示当前数是几进制,并将这个数转成十进制,当不在这个范围时,会输出NaN;
函数中的this是在运行时候决定的,而不是函数定义时。
function foo(){
console.log(this.fruit);
}
// 定义一个全局变量,等同于window.fruit = "banana";
var fruit = "banana";
// 此时函数中的this指向window;
foo(); // "banana"
var o = {
fruit : "apple",
foo : foo
};
// 此时函数中的this指向o
o.foo(); // "apple"
全局函数apply和call可以用来改变this的指向,如下:
function foo(){
console.log(this.fruit);
}
// 定义一个全局变量,等同于window.fruit = "banana";
var fruit = "banana";
var o = {
fruit : "apple"
};
foo.apply(window); // "banana";
foo.call(o); // "apple";
apply和call的唯一区别,就是在传参的时候,apply的参数需要放在一个数组里面,而call不需要;
因为在JavaScript中,函数也是对象,我们看下面这个例子:
1 function foo(){
2 if(this === window){
3 console.log("this is window");
4 }
5 };
6 // 函数foo也是对象,可以为对象定义属性,然后属性为函数
7 foo.boo = function(){
8 if(this === foo){
9 console.log("this is foo");
10 }else if(this === window){
11 console.log("this is window");
12 }
13 };
14
15 // 等价于 window.foo();
16 foo(); // "this is window";
17 // 可以看到函数中this的指向调用函数的对象
18 foo.boo(); // "this is foo";
19 // 可以使用call改变函数中this指向
20 foo.boo.call(window); // "this is window";
对象中的嵌套函数的this指向不是当前对象,而是window,看如下例子:
var name = "window.name";
var obj = {
name : "obj.name",
getName:function(){
console.log(this.name);
return function(){
console.log(this.name);
}
}
}
obj.getName()(); // "obj.name" "window.name"
同样是obj调用的getName和getName里面的方法,结果却是不同的值,这就说明嵌套函数中的this已经不指向当前对象了,而指向window。
那么,我们要怎样解决上述问题呢?主要有三种解决办法,如下:
1.使用函数的bind方法,绑定当前this;
1 var name = "window.name";
2 var obj = {
3 name : "obj.name",
4 getName:function(){
5 console.log(this.name);
6 return function(){
7 console.log(this.name);
8 }.bind(this);
9 }
10 };
11 obj.getName()(); // "obj.name" "obj.name"
2.使用变量将上面的this接收一下,然后下面不使用this,使用那个变量;
1 var name = "window.name";
2 var that = null;
3 var obj = {
4 name : "obj.name",
5 getName:function(){
6 that = this;
7 console.log(this.name);
8 return function(){
9 console.log(that.name);
10 }
11 }
12 }
13 obj.getName()(); // "obj.name" "obj.name"
3.使用ES6的箭头函数,可以完美避免此问题;
1 var name = "window.name";
2 var obj = {
3 name : "obj.name",
4 getName:function(){
5 console.log(this.name);
6 return () => {
7 console.log(this.name);
8 }
9 }
10 }
11 obj.getName()(); // "obj.name" "obj.name"