Javascript高级进阶内容学习笔记(1)

115 阅读5分钟

近期学习回顾JS高阶部分,深刻理解Javascript

基础不牢地动山摇!!

原型&原型链

构造函数

说起原型和原型链,我们需要先引入一个概念--构造函数

function Person() {

}
var person = new Person();
person.name = 'aa';
console.log(person.name) //aa

在这里,Person就是一个构造函数,可对Person进行实例化,使用new来创建一个实例对象person。在这个person对象中新建一个name属性赋值为'a'。

prototype

每个函数都会有个prototype属性

function Person() {

}
// 虽然写在注释里,但是你要注意:
// prototype是函数才会有的属性
Person.prototype.name = 'a';

var person1 = new Person();
var person2 = new Person();

console.log(person1.name) // a
console.log(person2.name) // a

我们可以在Person的prototype属性上新增一个name并赋值,那么实例对象person1、person2都可以获取到这个name的值。

Person的这个prototype属性就是指向了一个对象,这个对象也正是我们调用了该构造函数创建出来的person实例的原型,也就是person1、person2的原型。什么是原型?可以理解成:每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。此时

Peron(构造函数) ----- propotype -----> Person.prototype (实例的原型)

Person.prototype 和 person实例的关系就得引出新的点__proto__

__proto__

每个javascript对象(除了null)都带有一个默认的隐藏属性__proto__,此时这个属性就是会指向上面所说的实例原型Person.prototype,即 person.__proto__ === Person.prototype

构造函数有prototypeperson对象有__proto__都可以指向这个所谓的原型,那原型如何指向构造函数Person和实例person呢?

constructor

原型指向实例倒是没有,因为一个构造函数可以生成多个实例,但是原型指向构造函数是有的:通过constructor,每个原型都有一个 constructor 属性指向关联的构造函数,即 Perosn === Person.prototype.constructor

function Person() {

}
var person = new Person();

console.log(person.constructor === Person); // true

当获取 person.constructor 时,其实 person 中并没有 constructor 属性,当不能读取到constructor 属性时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性,所以person.constructor === Person.prototype.constructor

综上所述,我们可以得到

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

原型.png

原型链

原型的原型是什么?

原型对象就是通过 Object 构造函数生成的,结合之前所讲,实例的 __proto__ 指向构造函数的 prototype

此时Peron.prototype的__proto__属性是指向Object的prototype,而Object.prototype的__proto__为null。

这种一连串通过对象的__proto__属性指向原型对象的关系指向性结构就是原型链

image.png

词法作用域&动态作用域

作用域

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

静态作用域

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

var value = 1

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

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

bar(); //1

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

执行 foo 函数,先从 foo 函数内部查找是否有局部变量 value,如果没有,就根据书写的位置,查找上面一层的代码,也就是 value 等于 1,所以结果会打印 1。

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

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

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

动态作用域

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

执行上下文

this this, 上下文 context 起指代作用。在执行时动态读取上下文决定,而并非创建时。

this指向:

1、在函数里直接调用this,this指向全局window

2、隐式绑定:当执行时为对象调用,则指向对应的object

面试题:

第一个

    function fn(){
        console.log("指向",this)
    }
    fn();
    
    //打印 window内容,此处引文fn是直接在window下执行,则this指向执行时的上下文

第二个

    function fn(){
        console.log("指向",this.a)
    }
     const obj = {
         a:1,
         fn
     }
    obj.fn()
    
    //打印 1 ,此处是obj进行调用fn,fn执行在obj上下文,this指代obj执行上下文

第三个经典面试题!!!!

    const foo = {
        a:1,
        fn:function (){
            console.log(this.a)
            console.log(this)
        }
    }
    let f1 = foo.fn
    f1()
    
    //此处打印 为 undefined Window


根据经验不应该是谁调用就指向谁吗?

解析:因为把foo.fn拿出来赋值给了f1,但f1执行时是在window中执行,this指的上下文为执行时,所以this指向了window

追问1:那如何改变属性的指向呢?

由此引出

3、显示绑定:改变this指向。通常使用bind、apply、call

面试1:call、apply、bind区别?

1、call,apply的传参方式不同。如果是多个传参,call的写法是call(a,b),依次书写逗号隔离。apply是通过数组形式依次传入,apply([a,b])

2、bind直接返回方式不同

面试2:bind原理,并且手写bind方法?