JavaScript进阶之路(一)

1.原型(Prototype)&原型链(Prototype Chain)

1.1 原型 (Prototype)

在 JavaScript 中,每个对象都有一个内置的 [[Prototype]] 属性(可以通过 __proto__Object.getPrototypeOf() 访问),这个属性指向该对象的原型对象。

1.2 prototype及__proto__

  1. prototype是函数对象的属性(非函数对象无prototype属性),用于定义通过该函数创建的实例对象的原型。
  2. __proto__是每一个对象都有的属性,用于访问和修改对象的原型
javascript 复制代码
function Person(name) {
  this.name = name;
}
Person.prototype.sayHello = function () {
  console.log("Hello, my name is " + this.name);
};
let person1 = new Person("Alice");
let person2 = new Person("Bob");
person1.sayHello(); // 输出Hello, my name is Alice
person2.sayHello(); // 输出Hello, my name is Bob
console.log(Person.prototype === person.__proto__)//输出true

1.3 constructor

constructor是原型上的一个属性,原型无法指向实例,因为一个构造函数可以生成多个实例,但是原型可以通过constructor属性指向关联的构造函数。

javascript 复制代码
function Person() {

}

var person = new Person();

console.log(person.__proto__ == Person.prototype) // true

console.log(Person.prototype.constructor == Person) // true

console.log(Object.getPrototypeOf(person) === Person.prototype) // true

1.4实例与原型&&原型链

当访问一个对象的属性时,JavaScript 会:

  1. 先在对象自身属性中查找
  2. 如果找不到,则在其原型对象上查找
  3. 继续在原型的原型上查找,直到找到或到达原型链末端 (null)

2.闭包

2.1什么是闭包?

闭包是指能够访问其他函数作用域中变量的函数,即使那个外部函数已经执行完毕。

简单说:当一个函数记住并访问它所在的词法作用域,即使这个函数在其词法作用域之外执行,就产生了闭包。

2.词法作用域和动态作用域

2.1 什么是作用域

go 复制代码
作用域是指程序源代码中定义变量的区域。
作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。
JavaScript 采用词法作用域`(lexical scoping`),也就是静态作用域。

2.2 静态作用域和动态作用域

因为 JavaScript 采用的是词法作用域,函数的作用域在函数定义的时候就决定了。

而与词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的。

JavaScript 复制代码
var value = 1;

function foo() {
    console.log(value);
}

function bar() {
    var value = 2;
    foo();
}

bar();

// 结果是 ???

因为JavaScript采用的是词法作用域即静态作用域,其在执行foo函数时,先从foo函数内部查找是否有value变量,如果没有,就根据书写位置,查找上面一层的代码,也就是value = 1,所以打印结果会是1。

假设JavaScript采用动态作用域,让我们分析下执行过程:

执行 foo 函数,依然是从 foo 函数内部查找是否有局部变量 value。如果没有,就从调用函数的作用域,也就是 bar 函数内部查找 value 变量,所以结果会打印 2。

前面我们已经说了,JavaScript采用的是静态作用域,所以这个例子的结果是 1。

看一个面试题:
JavaScript 复制代码
// case 1
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();

// case 2
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();

两段代码各自的执行结果是多少?

local scope

因为JavaScript采用的是词法作用域,函数的作用域基于函数创建的位置。

而引用《JavaScript权威指南》的回答就是:

JavaScript 函数的执行用到了作用域链,这个作用域链是在函数定义的时候创建的。嵌套的函数 f() 定义在这个作用域链里,其中的变量 scope 一定是局部变量,不管何时何地执行函数 f(),这种绑定在执行 f() 时依然有效。

但是在这里真正想让大家思考的是:

虽然两段代码执行的结果一样,但是两段代码究竟有哪些不同呢?

3.执行上下文

3.1 顺序执行

写过 JavaScript 的开发者都会有个直观的印象,那就是顺序执行:

JavaScript 复制代码
var foo = function () {

    console.log('foo1');

}

foo();  // foo1

var foo = function () {

    console.log('foo2');

}

foo(); // foo2

那这段呢?

JavaScript 复制代码
function foo() {

    console.log('foo1');

}

foo();  // foo2

function foo() {

    console.log('foo2');

}

foo(); // foo2

打印的结果却是两个 foo2。

这是因为 JavaScript 引擎并非一行一行地分析和执行程序,而是一段一段地分析执行。当执行一段代码的时候,会进行一个"准备工作",那这个"一段一段"中的"段"究竟是怎么划分的呢?

到底JavaScript引擎遇到一段怎样的代码时才会做"准备工作"呢?

JavaScript 复制代码
console.log(add2(1,1)); //输出2
function add2(a,b){
    return a+b;
}
console.log(add1(1,1));  //报错:add1 is not a function
var add1 = function(a,b){
    return a+b;
}

// 用函数语句创建的函数add2,函数名称和函数体均被提前,在声明它之前就使用它。
// 但是使用var表达式定义函数add1,只有变量声明提前了,变量初始化代码仍然在原来的位置,没法提前执行。
相关推荐
sunbyte4 分钟前
50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | DragNDrop(拖拽占用组件)
前端·javascript·css·vue.js·vue
柚子8166 分钟前
告别FLIP动画:View Transition API带来的革命性变革
前端·javascript
天涯学馆10 分钟前
JS 组合模式在组件化开发中的应用:从原理到实战
前端·javascript·面试
FogLetter11 分钟前
闭包:JavaScript中的魔法背包
前端·javascript
江城开朗的豌豆12 分钟前
Vuex中mutations和actions的那些事儿:为啥非要分家?
前端·javascript·vue.js
WTSolutions20 分钟前
Image Pixel RGBA Extractor:免费在线图像像素色彩提取工具全解析
javascript
轻语呢喃26 分钟前
Cookie详解:从原理到实战,彻底搞懂用户身份识别机制
javascript·http·html
旺旺大力包29 分钟前
【JS笔记】JS 和 noodjs 的常见操作(十)
开发语言·javascript·node.js·ecmascript
前端进阶者33 分钟前
地图坐标系转换JS库
前端·javascript
夏梦春蝉1 小时前
ES6从入门到精通:其他特性
前端·javascript·es6