从几个题目看一下JavaScript的 共有、私有、静态属性和方法

本文主要涉及两方面的内容:

  • JavaScript的公有、私有、静态属性和方法
  • 运算符的优先级
  • 返回值
  • this指向

开胃菜

输出什么?

function A(x) {
	this.x = x;
}
A.prototype.x = 1;

function B(x) {
	this.x = x;
}
B.prototype = new A();
var a = new A(2),
	b = new B(3);
delete b.x;
console.log(a.x, b.x)

答案是 2 undefined

(❗️提示:下边说的实例化、公有属性,你听不懂没关系,这个文章就是解释这些的。但是原型啊什么的你听不懂,你就按照我的提示去补充知识吧。👻)

为什么a.x输出2

我们先看一下a本身的构造。
在这里插入图片描述
a是A的实例,那么a的隐式原型就指向A的显式原型,也就是说A的prototype和a的__proto__相等。输出一下,确实是相同的。(这里看不懂就去补原型的知识
在这里插入图片描述
那这个题的A就是这样的:

  • A.prototype.x = 1;是给A的原型上添加一个公有属性。
  • this.x = x;是A函数体里边的公有属性。

当函数体本身有某个方法或者属性的时候,就会把原型链里边的遮盖掉。如果函数体中没有某个方法或者属性,就回去原型链中找。
题目里实例化的对象直接使用.运算符,是直接访问A函数体里边的公有属性,而A函数体中有个x了,所以不会用到原型链里的x。想要访问到原型链里的x只能去原型链里找。

function A(x) {
	this.x = x;
}
A.prototype.x = 1
var a = new A(2)
console.log(a.x)
console.log(a.__proto__.x)

函数体中没有x的时候,就直接使用原型链里边的了。

function A(x) {
	// this.x = x;
}
A.prototype.x = 1
var a = new A(2)
console.log(a.x)
console.log(a.__proto__.x)
为什么b.x输出undefined

先分析一下B的代码:

function B(x) {
	this.x = x;
}
B.prototype = new A();

创建一个函数B,B.prototype = new A();这句话就是B.prototype是A的实例,也就是说B.prototype的隐式原型就是A的显式原型。
在这里插入图片描述
那对于var b = new B(3);现在的逻辑就是实例b用.运算符访问x,就是访问b中的共有属性x:

  • B函数体中有x,那就访问x
  • B函数体中没有x,那就访问B.prototype.x
  • B.prototype中没有x,那就访问B.prototype__proto__.x

捋清楚访问的逻辑了吧,那现在我们就来看一下输出什么:

function A(x) {
	this.x = x;
}
A.prototype.x = 1;
function B(x) {
	this.x = x;
}
B.prototype = new A();
var b = new B(3);
delete b.x;
console.log(b.x)

因为B函数体中有x,实例化b = new B(3)之后,b是B的实例,此时B函数体中的x = 3 。
b.x指的是B函数体中的x。
delete b.x;将函数体中的x删除了。此时b.x指的就是b原型链上的b了,也就是先看看B.prototype中有没有x。
那我们看看b的构造:b__proto__中也就是B.prototype中有个x!并且还是undefined,找到x了,直接输出undefined即可。
在这里插入图片描述
undefined哪里来的?
B.prototype = new A();B的显式原型是A是实例对象,A函数体中有x,B.prototype实例化的时候没有携带参数,也就是没给A的x赋值,所以输出undefined。
不信你就试一下,给B.prototype = new A();加上个值。
在这里插入图片描述


进入正题

刚才我一直在说共有属性公有属性,那还有私有的?
其实不仅有私有的,还有静态的嗷。

function User(id) {
	this.id = id; //公有属性
	var id = id; //私有属性
	function getId() { //私有方法
		console.log(this.id)
	}
}
User.prototype.getId = function() { //公有方法
	console.log(this.id)
}
User.id = 'Sian静态'; //静态属性
User.getId = function() { //静态方法
	console.log(this.id)
}
var sian = new User('SianOvO'); //实例化
  • 调用公有方法、公有属性,需要先实例化对象,也就是用new操作符,公有方法不能调用私有方法和静态方法的
    console.log(sian.id)		//输出SianOvO
    sian.getId()				//输出SianOvO
    
  • 调用静态方法、静态属性,无需实例化
    console.log(User.id)		//输出Sian静态
    User.getId()				//输出Sian静态
    
  • 私有方法、私有属性外部是不可以访问的

看懂了就做个题

看到刚才那里你可能觉得自己又懂了,那做个题吧。👏

请写出以下输出结果:

function Foo() {
    getName = function () { console.log(1); };
    return this;
}
Foo.getName = function () { console.log(2);};
Foo.prototype.getName = function () { console.log(3);};
var getName = function () { console.log(4);};
function getName() { console.log(5);}
 
//请写出以下输出结果:
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

直接告诉你答案:2 4 1 1 2 3 3
这个题综合了很多知识,看了答案不懂也没事,往下看解析。

整理代码

拿到这段代码之后,要进行预处理:变量提升(hoisting),先function声明的函数,再var声明的变量,所以提升以后代码逻辑如下:

var getName = undefined
function Foo() {
    getName = function () { console.log(1) }
    return this
}
function getName() { console.log(5) }

Foo.getName = function () { console.log(2) }
Foo.prototype.getName = function () { console.log(3) }
getName = function () { console.log(4) }

Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

看一下上边的5,6,10行代码做了什么

  1. var声明一个变量getName
  2. function声明一个函数getName
  3. 给getName赋值,把原来function声明的函数覆盖掉

所以覆盖之后代码逻辑如下:

var getName = undefined
function Foo() {
    getName = function () { console.log(1) }
    return this
}

Foo.getName = function () { console.log(2) }
Foo.prototype.getName = function () { console.log(3) }
getName = function () { console.log(4) }

Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

思路分析

整理完代码逻辑可以做题啦

第1问Foo.getName();

没实例化,直接使用构造函数 + .运算符,这是访问静态方法。
执行过程

  • 执行Foo的静态方法:Foo.getName = function () { console.log(2) }
  • 所以输出2
第2问getName();

执行过程

  • 调用全局的getName方法var getName = function () { console.log(4) }
  • 所以输出4
第3问Foo().getName();

先看一下Foo()函数:
在这里插入图片描述

  • getName = function () { console.log(1) }的作用就是给getName赋值一个函数。Foo()函数体中没有声明getName变量,因此往上一层作用域中找,找到了window,于是更改全局作用域(window)中的getName方法。
  • 函数直接调用,this指向window。return this就是返回window。

在这里插入图片描述
执行过程

  • 执行Foo()修改getName方法并将this指向window
  • 调用window的getName方法function () { console.log(4) }
  • 所以输出1
第4问getName();

跟上一题一样,就是window.getName();
在这里插入图片描述
执行过程

  • 调用window的getName方法function () { console.log(4) }
  • 所以输出1
第5问new Foo.getName();

一头雾水?这里是考察的是JS的运算符优先级问题。放个优先级表格(完整版戳👉MDN web docs 运算符优先级
在这里插入图片描述
本题中涉及到的运算符:

  • 19
    • 成员访问… . …
    • new (带参数)new … ( … )
    • 函数调用… ( … )
  • 18
    • new(无参数)new …

注意new Foo.getName();这个括号是函数的括号啊,跟表格里权重20的括号不一样,权重20的括号是 ( 1 + 3 ) / 2 (1+3)/2 (1+3)/2运算中的括号啊!!!

再看new Foo.getName(); 该怎么解析:
在这里插入图片描述
在这里插入图片描述
解析到new之后,继续往后解析,看看需要new什么东西,也就是new Foo。但是Foo后边却有个.运算符。此时就是没有new无参数列表(18)和.(19)比较,.的优先级高,所以先执行Foo.getName。那执行顺序就变为下图:
在这里插入图片描述
此时(Foo.getName)后边有个(),此时new无参数(18)就变成了new有参数(19),执行带参数列表的new。
再次提醒,这个()括号是调用函数时的括号,跟(Foo.getName)的运算符括号不同。
在这里插入图片描述
执行过程

  • Foo.getName就是Foo.getName = function () { console.log(2) }
  • new Foo.getName();就是把function () { console.log(2) }当作构造函数,创建其实例对象。
  • 因此输出2

Q:为什么有输出啊?我也没调用函数吧?
A:如果你有这个疑问,建议看一哈JavaScript中 构造函数的new都做了什么。new的第三步就是执行构造函数中的代码,因此不需要你调用,new 的过程中它自己就执行啦。

第6问new Foo().getName();

第五问搞明白之后。剩下的就好懂多了。
在这里插入图片描述
new ().的优先级是一样的,从左往右执行,所以先执行new Foo(),再次修改全局中的getName,然后返回一个this,这个this是指向new Foo()这个实例化对象的。然后执行实例化对象的getName方法。
注意!这个看似跟第3问一样,其实不一样,第三问中是直接调用Foo(),但是本问中进行了new实例化操作。
在这里插入图片描述
执行过程

  • 创建Foo的实例对象,this指向实例对象
  • 调用实例对象的getName方法(实例对象能调用构造函数的公有方法和属性)
    • 先看函数体中有没有getName方法,函数体中没有getName方法,需要去原型链中寻找。
      虽然我们看代码可以看到函数体有个getName ,但是那个getName是私有方法!对于代码执行来说外部是访问不到的。
    • 实例的原型链有找到了,Foo的prototype中有getName方法Foo.prototype.getName = function () { console.log(3) },因此执行getName方法
  • 所以输出3
第7问new new Foo().getName();

在这里插入图片描述
遇到new,继续向右解析,又遇到new,继续向右解析,遇到().new()优先级等于.,执行new()。下图左边是一个new,没参数列表,右边是带参数列表,执行右边的new。
在这里插入图片描述
在这里插入图片描述
执行过程

  • let 实例 = new Foo() 指针指向实例
  • let x = 实例.getName x 就是ƒ () { console.log(3) }
  • new x()就是将x实例化,实例化的过程中自动调用ƒ () { console.log(3) }
  • 所以输出3

再来个题自己测验一下

function Foo() {
	this.getName = function() {
		console.log(1) 
		return { getName: getName }
	}  
	getName = function() { console.log(2) } 
	return this
}
Foo.getName = function() { console.log(3) } 
Foo.prototype.getName = function() { console.log(4) } 
var getName = function() { console.log(5) } 
function getName() { console.log(6) } 

Foo.getName() 
getName()  
Foo().getName()  
getName()  
new Foo.getName()  
new Foo().getName()  
new Foo().getName().getName()  
new new Foo().getName() 

不详细将了,大致说一下思路。
预处理之后的代码:

var getName = undefined
function Foo() {
	this.getName = function() {
		console.log(1) 
		return { getName: getName }
	}  
	getName = function() { console.log(2) } 
	return this
}
Foo.getName = function() { console.log(3) } 
Foo.prototype.getName = function() { console.log(4) } 
getName = function() { console.log(5) } 
  1. Foo.getName()调用Foo的静态方法,输出3
  2. getName()调用全局变量的getName,输出5
  3. Foo().getName()直接调用Foo函数,this指向window,this.getName = function(){...}修改一次window中的getName,getName = function(){...}又修改一次window中的getName,输出2
    在这里插入图片描述
  4. getName()window中的getName,输出2
  5. new Foo.getName()–> new (Foo.getName)()输出3
  6. new Foo().getName()–>(new Foo()).getName()this.getName = function() {...}是公有方法,输出1
    如果没有this.getName = function() {...}这一句,就是输出4
  7. new Foo().getName().getName()–>((new Foo()).getName()).getName()
    到这里((new Foo()).getName())和第6问一模一样,但是后边又加了个.getName() ,这个.getName()是谁的?
    • 第6问调用的公有方法this.getName = function() {...},里边还有return { getName },返回了一个对象,并且给对象的getName属性赋值一个getName方法
    • 但是this.getName = function() {...}里边没getName方法,往上一层找,找到Foo里边的getName = function() { console.log(2) }输出2
    • 如果你把getName = function() { console.log(2) }这句注释掉。那就是this.getName里边没getName方法,往上一层找Foo里边也没有,继续往外找,在全局作用域中找到了,输出5。
  8. new new Foo().getName()–> new ((new Foo()).getName())
    ((new Foo()).getName())就是把this.getName = function() {...}当构造函数实例化,new的过程中执行代码,输出1

上边提到了this,那就再补充几个小题目

function Car() {
	this.make = "Lamborghini"
	return { make: "Maserati" }
}
const myCar = new Car()
console.log(myCar.make)

输出Maserati
myCar是Car的实例,但是Car有个返回值是返回到一个对象,所以调用myCar就是调用这个对象,输出的就是该对象的name “Maserati”

var status = "😎"
setTimeout(() => {
	const status = "😍"
	const data = {
		status: "🥑",
		getStatus() {
			return this.status
		}
	}
	console.log(data.getStatus())
	console.log(data.getStatus.call(this))
}, 0)

输出🥑和😎
在data中调用getStatus()方法,getStatus()的this是指向data的,因此第一个输出🥑。第二个使用call方法,将其绑定给this,这个this是指向全局的。


我尽力了,讲的很繁琐,但是应该讲的很明白了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ann's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值