JS原型,作用域,this,闭包
一,原型链
1. JS是通过原型机制来实现面向对象的继承的,那JS是不是面向对象的语言呢?广义上说是的,但他并没有像C#、Java语言那么容易实现多态。
2. 每一个函数都有一个prototype,我们可以把那些不变(共用)的属性和方法,直接定义在prototype对象属性上。
3. JS中的普通函数(箭头函数除外)都可以作为构造器生成对象,函数也是一种特殊的对象,函数默认会有一个prototype属性,它的指向等于由这个函数生成对象的原型,假如有Person函数和它的对象p1,则 (Person.prototype === p1.__proto__)
function Person(){ } var p1 = new Person();
console.log(p1.__proto__ === Person.prototype); // true, 注意 __proto__是一个隐藏属性,没有被标准化,兼容性不好,生产中避免使用 console.log(Object.getPrototypeOf(p1) === Person.prototype); // true
console.log(Reflect.getPrototypeOf(p1) === Person.prototype); // true, ES6+后推荐写法
console.log(Person === Person.prototype.constructor); // true 原型上有一个构造器对象 指向函数。
console.log(p1.constructor=== Person.prototype.constructor); // true
console.log(p1.constructor=== Person); // true
console.log(p1.__proto__ === p1.constructor.prototype) // true
console.log(Person.__proto__ === Function.prototype); //true, pay attention to this, 所有函数的__proto__都指向Function.prototype
console.log(Object.getPrototypeOf(String) === Function.prototype) // true
console.log(Object.getPrototypeOf(Object) === Function.prototype) // true
Note
function Person(name) { this.name = name }
Person.prototype = { getName: function() {} } // 这里修改了原型指向 var p = new Person('jack') console.log(p.__proto__ === Person.prototype) // true console.log(p.__proto__ === p.constructor.prototype) // false,这里不相等了,因为p的构造函数还等于之前那个空对象。
给Person.prototype赋值的是一个对象直接量{getName: function(){}},使用对象直接量方式定义的对象其构造器(constructor)指向的是根构造器Object,Object.prototype是一个空对象{},{}自然与{getName: function(){}}不等。
var obj = {} 此处等价于 var obj = new Object() console.log(obj.__proto__ === Object.prototype)//true var obj = [] // 等价于 var arr= new Array(); console.log(obj.__proto__ === Array.prototype)//true
4. 所有函数的还有一个proto属性,它执行Function.prototype
Boolean.__proto__ === Function.prototype // true Boolean.constructor == Function //true String.__proto__ === Function.prototype // true String.constructor == Function //true // 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身 Object.__proto__ === Function.prototype // true Object.constructor == Function // true // 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身 Function.__proto__ === Function.prototype // true Function.constructor == Function //true Array.__proto__ === Function.prototype // true Array.constructor == Function //true RegExp.__proto__ === Function.prototype // true RegExp.constructor == Function //true Error.__proto__ === Function.prototype // true Error.constructor == Function //true Date.__proto__ === Function.prototype // true Date.constructor == Function //true
some day updates: the article is better to understand to know the prototype of JS, https://juejin.cn/post/6844903837623386126
二、作用域
JS中有3种主要的作用域,全局作用域,函数作用域,ES6中新增的块作用域 {},for(), while(), swith(), if()这几种表达式都会生成块作用域。
JS执行过程分编译和解释执行两个阶段(源码 -> 字节码 -> 二进制,AST协助ES6 -> ES5, JIT会缓存一些多次调用而产生的优化过后的二进制码 ),编译过程中会有变量提升(var 变量提升(此时等于undefined,可访问),let和const提升后不能访问(暂时性死区), 函数定义也会提升),后定义的会覆盖先定义的。
函数执行上线文中查询对象作用域范围大致如下
So,可以分析下面的代码
function bar() { var myName = "roy" let test1 = 100 if (1) { let myName = "Chrome浏览器" console.log(test) } } function foo() { var myName = "foo" let test = 2 { let test = 3 bar() } } var myName = "world" let myAge = 10 let test = 1 foo()
// 分析段代码,最终输出的是 1, 它是外出window定义的test值。
三、 this指针
1. 在全局环境中调用一个函数,函数内部的 this 指向的是全局变量 window。
function foo(){ console.log(this) } foo() // 非严格模式下输出window
2. 通过一个对象来调用其内部的一个方法,该方法的执行上下文中的 this 指向对象本身。
var myObj = { name : "roy", showThis: function(){ console.log(this) } } myObj.showThis() //输出 {name: "roy", showThis: ƒ}
//特别注意下面这样,this又指向window去了
var temp = myObj.showThis;
temp(); //这会儿输出Window了。
let bar = { myName : "roy", test1 : 1} function foo(){ this.myName = "Tim" } foo.call(bar), //还可以使用apply console.log(bar) // 输出Tim.
let bar = { myName : "roy", test1 : 1 } function foo(){ this.myName = "jerry"} var k=foo.bind(bar); k(); console.log(bar) // 输出jerry,所以bind方法会始终固定下this的指向。
function CreateObj(){ this.name = "Roy" } var myObj = new CreateObj() //本质上是下面这样 var tempObj = {} CreateObj.call(tempObj) return tempObj
this 的缺陷
a. 嵌套函数中的 this 不会从外层函数中继承
var myObj = { name : "Roy", showThis: function(){ console.log(this) //这里输出myObject这个对象 function bar(){ console.log(this) // 这里却输出Window对象 } bar() } } myObj.showThis()
how to fix it?
- 第一种是把 this 保存为一个 self 变量,再利用变量的作用域机制传递给嵌套函数。
- 第二种是继续使用 this,但是要把嵌套函数改为箭头函数,因为箭头函数没有自己的执行上下文,所以它会继承调用函数中的 this。
var myObj = { name : "Roy", showThis: function(){ console.log(this) var bar =()=> { this.name = "Jerry" console.log(this) } bar() } } myObj.showThis() //Roy console.log(myObj.name) //Jerry console.log(window.name) //这里也会输出Jerry,诡异。
this的坑,下面输出的实际上没有this的引用,小心掉坑。
var bar = { myName:"Roy", printName: function () { console.log(myName) } } function foo() { let myName = "Jerry" return bar.printName } let myName = "Jenifer" let printName = foo() printName() // Jenifer bar.printName() //Jenifer
第四 闭包
函数内部的函数引用了外部函数的局部变量,且返回这个内部函数。外部函数执行完了后,返回的函数还包含外部函数中定义的变量,这样就会创建一个外部函数的闭包。
所以这些变量都是保存在堆上的,so,通常如果引用闭包的函数是一个全局变量,那么闭包会一直存在直到页面关闭;但如果这个闭包以后不再使用的话,就会造成内存泄漏。如果引用闭包的函数是个局部变量,等函数销毁后,在下次 JavaScript 引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用了,那么 JavaScript 引擎的垃圾回收器就会回收这块内存。
作用一,封装对象的访问和写入,提供了一种外部可以读写函数内部的变量的机制,类似Java中的get,set访问器。
function foo() { var myName = "Roy" let test1 = 1 const test2 = 2 var innerBar = { getName:function(){ console.log(test1) return myName }, setName:function(newName){ myName = newName // 这里的myName的作用域链是,先setName方法内部看是否有myName有定义?没有就去闭包(foo的背包)中找?这里找到了,如果还是没有找打就要去全局执行上下文中找了。 } } return innerBar } var bar = foo() bar.setName("Jenifer") bar.getName() console.log(bar.getName()) // 这里输出Jenifer
-------------------我是简陋的分割线--------------
function Person(){
var name = "default";
return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
};
var john = Person();
print(john.getName());
john.setName("john");
print(john.getName());
另一个作用是就是让这些变量的值始终保持在内存中,做缓存。
//这里会形成一个foo的闭包closure,且a变量会保存在堆上的 function foo() { var a = 0 return function inner() { return a++ } }