前端综合学习笔记---变量类型、原型链、作用域和闭包

个人bolg地址

变量类型

  JavaScript 是一种弱类型脚本语言,所谓弱类型指的是定义变量时,不需要什么类型,在程序运行过程中会自动判断类型。

ECMAScript 中定义了 6 种原始类型:

  • Boolean
  • String
  • Number
  • Null
  • Undefined
  • Symbol(ES6 新定义)

注意:原始类型不包含 Object。

第一问:类型判断用到哪些方法?

typeof

typeof xxx得到的值有以下几种类型:undefined、boolean、number、string、object、functionsymbol ,比较简单。这里需要注意的有三点:

  1. typeof null结果是object ,实际这是typeof的一个bug,null是原始值,非引用类型
  2. typeof [1, 2]结果是object,结果中没有array这一项,引用类型除了function其他的全部都是object 
  3. typeof Symbol()typeof获取symbol类型的值得到的是symbol,这是 ES6 新增的知识点

instanceof

用于实例和构造函数的对应。例如判断一个变量是否是数组,使用typeof无法判断,但可以使用[1, 2] instanceof Array来判断。因为,[1, 2]是数组,它的构造函数就是Array。同理:

function Foo(name) {
    this.name = name
}
var foo = new Foo('bar')
console.log(foo instanceof Foo) // true

第二问:值类型和引用类型的区别

  值类型 vs 引用类型

 除了原始类型,ES 还有引用类型,上文提到的typeof识别出来的类型中,只有objectfunction是引用类型,其他都是值类型。

根据 JavaScript 中的变量类型传递方式,又分为值类型和引用类型,值类型变量包括 Boolean、String、Number、Undefined、Null,引用类型包括了 Object 类的所有,如 Date、Array、Function 等。在参数传递方式上,值类型是按值传递,引用类型是按共享传递。

面通过一个小题目,来看下两者的主要区别,以及实际开发中需要注意的地方。

// 值类型
var a = 10
var b = a
b = 20
console.log(a)  // 10
console.log(b)  // 20

上述代码中,a b都是值类型,两者分别修改赋值,相互之间没有任何影响。再看引用类型的例子

// 引用类型
var a = {x: 10, y: 20}
var b = a
b.x = 100
b.y = 200
console.log(a)  // {x: 100, y: 200}
console.log(b)  // {x: 100, y: 200}

上述代码中,a b都是引用类型。在执行了b = a之后,修改b的属性值,a的也跟着变化。因为ab都是引用类型,指向了同一个内存地址,即两者引用的是同一个值,因此b修改属性时,a的值随之改动。

再借助题目进一步讲解一下

说出下面代码的执行结果,并分析其原因。

function foo(a){
    a = a * 10;
}
function bar(b){
    b.value = 'new';
}
var a = 1;
var b = {value: 'old'};
foo(a);
bar(b);
console.log(a); // 1
console.log(b); // value: new

通过代码执行,会发现:

  • a的值没有发生改变
  • b的值发生了改变

这就是因为Number类型的a是按值传递的,而Object类型的b是按共享传递的。

JS 中这种设计的原因是:按值传递的类型,复制一份存入栈内存,这类类型一般不占用太多内存,而且按值传递保证了其访问速度。按共享传递的类型,是复制其引用,而不是整个复制其值(C 语言中的指针),保证过大的对象等不会因为不停复制内容而造成内存的浪费。

引用类型经常会在代码中按照下面的写法使用,或者说容易不知不觉中造成错误!

var obj = {
    a: 1,
    b: [1,2,3]
}
var a = obj.a
var b = obj.b
a = 2
b.push(4)
console.log(obj, a, b)//{a:1,b:[1,2,3,4]},2,[1,2,3,4]

虽然obj本身是个引用类型的变量(对象),但是内部的ab一个是值类型一个是引用类型,a的赋值不会改变obj.a,但是b的操作却会反映到obj对象上。

原型和原型链

JavaScript 是基于原型的语言,原型理解起来非常简单,但却特别重要,下面还是通过题目来理解下JavaScript 的原型概念。

第三问:如何理解 JavaScript 的原型

对于这个问题,可以从下面这几个要点来理解和回答,下面几条必须记住并且理解

  • 所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(null除外)
  • 所有的引用类型(数组、对象、函数),都有一个__proto__属性,属性值是一个普通的对象
  • 所有的的函数,都有一个prototype属性,属性值也是一个普通的对象
  • 所有的的引用类型(数组、对象、函数),__proto__属性值指向它的构造函数的prototype属性值

通过代码解释一下,大家可自行运行以下代码,看结果

// 要点一:自由扩展属性
var obj = {}; obj.a = 100;
var arr = []; arr.a = 100;
function fn () {}
fn.a = 100;

// 要点二:__proto__
console.log(obj.__proto__);
console.log(arr.__proto__);
console.log(fn.__proto__);

// 要点三:函数有 prototype
console.log(fn.prototype)

// 要点四:引用类型的 __proto__ 属性值指向它的构造函数的 prototype 属性值
console.log(obj.__proto__ === Object.prototype)

原型

先写一个简单的代码示例。

// 构造函数
function Foo(name, age) {
    this.name = name
}
Foo.prototype.alertName = function () {
    alert(this.name)
}
// 创建示例
var f = new Foo('zhangsan')
f.printName = function () {
    console.log(this.name)
}
// 测试
f.printName()
f.alertName()

  执行printName时很好理解,但是执行alertName时发生了什么?这里再记住一个重点 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的__proto__(即它的构造函数的prototype)中寻找,因此f.alertName就会找到Foo.prototype.alertName

那么如何判断这个属性是不是对象本身的属性呢?使用hasOwnProperty,常用的地方是遍历一个对象的时候。

var item
for (item in f) {
    // 高级浏览器已经在 for in 中屏蔽了来自原型的属性,但是这里建议大家还是加上这个判断,保证程序的健壮性
    if (f.hasOwnProperty(item)) {
        console.log(item)
    }
}

 第四问:如何理解 JS 的原型链

还是接着上面的示例,如果执行f.toString()时,又发生了什么?

 

// 省略 N 行

// 测试
f.printName()
f.alertName()
f.toString()

 

因为f本身没有toString(),并且f.__proto__(即Foo.prototype)中也没有toString。这个问题还是得拿出刚才那句话——当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的__proto__(即它的构造函数的prototype)中寻找。

如果在f.__proto__中没有找到toString,那么就继续去f.__proto__.__proto__中寻找,因为f.__proto__就是一个普通的对象而已嘛!

  1. f.__proto__Foo.prototype,没有找到toString,继续往上找
  2. f.__proto__.__proto__即Foo.prototype.__proto__Foo.prototype就是一个普通的对象,因此Foo.prototype.__proto__就是Object.prototype,在这里可以找到toString
  3. 因此f.toString最终对应到了Object.prototype.toString

这样一直往上找,你会发现是一个链式的结构,所以叫做“原型链”。如果一直找到最上层都没有找到,那么就宣告失败,返回undefined。最上层是什么 —— Object.prototype.__proto__ === null

原型链中的this

 所有从原型或更高级原型中得到、执行的方法,其中的this在执行时,就指向了当前这个触发事件执行的对象。因此printNamealertName中的this都是f

作用域和闭包

作用域和闭包是前端面试中,最可能考查的知识点。例如下面的题目:

第五问:现在有个 HTML 片段,要求编写代码,点击编号为几的链接就alert弹出其编号;

  • 编号1,点击我请弹出1
  • 2
  • 3
  • 4
  • 5

一般不知道这个题目用闭包的话,会写出下面的代码

var list = document.getElementsByTagName('li');
for (var i = 0; i < list.length; i++) {
    list[i].addEventListener('click', function(){
        alert(i + 1)
    }, true)
}

实际上执行才会发现始终弹出的是6,这时候就应该通过闭包来解决:

var list = document.getElementsByTagName('li');

for (var i = 0; i < list.length; i++) {

    list[i].addEventListener('click', function(i){
        return function(){
            alert(i + 1)
        }
    }(i), true)

}

要理解闭包,就需要我们从「执行上下文」开始讲起。

执行上下文

这个在我另一篇文章里讲过  点击链接

先讲一个关于 变量提升 的知识点,面试中可能会遇见下面的问题,很多候选人都回答错误:

  第六问:说出下面执行的结果(这里我就直接注释输出了)

console.log(a)  // undefined
var a = 100

fn('zhangsan')  // 'zhangsan' 20
function fn(name) {
    age = 20
    console.log(name, age)
    var age
}

console.log(b); // 这里报错
// Uncaught ReferenceError: b is not defined
b = 100;

在一段 JS 脚本(即一个

你可能感兴趣的:(前端综合学习笔记---变量类型、原型链、作用域和闭包)