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,只有变量声明提前了,变量初始化代码仍然在原来的位置,没法提前执行。
相关推荐
我是日安1 小时前
从零到一打造 Vue3 响应式系统 Day 23 - Watch:基础实现
前端·javascript·vue.js
FogLetter1 小时前
Map 与 WeakMap:内存管理的艺术与哲学
前端·javascript
前端伪大叔1 小时前
第15篇:Freqtrade策略不跑、跑错、跑飞?那可能是这几个参数没配好
前端·javascript·后端
課代表4 小时前
Acrobat DC 文本域表单验证中的 js 使用
javascript·正则表达式·表单验证·数据完整性·字段验证·事件对象·自定义验证
用户6387994773055 小时前
Next.js 多语言对决:next-intl vs next-i18next vs Intlayer
javascript
Keepreal4965 小时前
谈谈对javascript原型链的理解以及原型链的作用
前端·javascript
itslife5 小时前
vite 源码 - 配置
前端·javascript
麦兜*6 小时前
Redis多租户资源隔离方案:基于ACL的权限控制与管理
java·javascript·spring boot·redis·python·spring·缓存
rggrgerj6 小时前
VUE3+element plus 实现表格行合并
javascript·vue.js·elementui
fxshy6 小时前
Vue3和element plus在el-table中使用el-tree-select遇到的change事件坑
javascript·vue.js·elementui