近期学习回顾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
构造函数有prototype、person对象有__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
原型链
原型的原型是什么?
原型对象就是通过 Object 构造函数生成的,结合之前所讲,实例的 __proto__ 指向构造函数的 prototype
此时Peron.prototype的__proto__属性是指向Object的prototype,而Object.prototype的__proto__为null。
这种一连串通过对象的__proto__属性指向原型对象的关系指向性结构就是原型链
词法作用域&动态作用域
作用域
作用域就是指程序代码中定义变量的区域,作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。
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方法?