持续输出js第7篇-(OOP和NEW的实现原理)

218 阅读7分钟

对象与类

对象:万物皆对象

类:对象的细分

实例:类中具体的事物

JS也是面向对象编程:类、实例

          实例->类
		  1 ->Number
		  'A' ->String
		  true ->Boolean
		  null ->Null(它的直属大类不是Object,是Null,但是最终还是属于Objectundefined ->Undefined     
		  [] ->Array                         它们最终隶属于这个大类 ->Object(大类)  
		  /^$/ ->RegExp
		  function(){}  ->Function
		  {} ->Object

每一个元素标签(元素对象)都有一个自己所属的大类,比如div标签

	div -> HTMLDivElement -> HTMLElement -> Element -> Node -> EventTarget -> Object  

每一个实例可以调用所属类(整条链)中的属性和方法

面向对象思维,实例->类

创建一个自定义类

创建一个函数(Function类的实例),直接执行就是普通函数,但是“new 执行”它则被称为一个自定义的类

普通函数执行

function func() {
			let x = 100;
			this.num = x +100;
	}
func(); //=>this:window  AO(FUNC):{x=100} ... 普通函数执行 
 形成一个全新的执行上下文EC
 
 形成一个AO变量对象
 
 声明其this指向
 
 初始化作用域链
 
 代码执行

NEW 函数执行

怎样才能使一个普通函数变成一个类,答案是给它new一个实例let f = new func();

function func() {
            // let obj={}; //=>这个对象就是实例对象名为f
			// this -> obj
			let x = 100;
			this.num = x +100; //=>相当于给创建的实例对象新增一个num的属性 obj.num=200 
			return obj;//本来是默认有return的,不用你写,但是如果你自己写了,而且写的是引用类型值那么就是看下面
			
			//用户自己返回内容,如果返回的是一个引用类型值,则会把默认返回的实例给覆盖掉(此时返回的值就不在是类的实例了) 
			
			如果是基本类型值就没事,还是返回那个实例对象,无影响
	}
let f = new func();//f是func这个类的实例 {num:200}

NEW

注意:因为具备普通函数执行的一面,所以只有this.xxx=xxx才和创建的实例有关系,此案例中的x只是AO中的私有变量

 形成一个全新的执行上下文EC
 
 形成一个AO变量对象
 
 ARGUMENTS
 
 形参赋值
 
 初始化作用域链
 
 [新]默认创建一个对象,而这个对象就是当前类的实例
 
 [新]声明其THIS指向,让其指向这个新创建的实例
 
 代码执行
 
 [新]不论其是否写RETURN,都会把新创建的实例返回(特殊点)
 

还有哦,每次 new创建的都是不同的实例,不同的堆

let f = new func();
let f2 = new func();
console.log(f === f2); //=>false 每一次new出来的都是一个新的实例对象(一个新的堆内存)

我们来检查一下

console.log(f instanceof func); //=>TRUE instanceof用来检测某一个实例是否属于这个类

内置NEW的实现原理

Func:操作的那个类

ARGS:NEW类的时候传递的实参集合

return 实例或者自己返回的对象

举个例子,首先创建一个函数

		function Dog(name) {
			this.name = name;
		}
		Dog.prototype.bark = function () {
			console.log('wangwang');
		};
		Dog.prototype.sayName = function () {
			console.log('my name is ' + this.name);
		};

通过内置new创建一个实例对象 涉及原型链 核心语句,

let obj = {};
obj.__proto__ = Func.prototype;

但是:IE大部门浏览器中不允许我们直接操作__proto__

不过我们可以这样,这一句代替上面两句

let obj = Object.create(Func.prototype);

意思,创建一个实例对象,让它的原型链指向Func.prototype(Func的原型)/让它变成Func类的实例

		function _new(Func, ...args) {
			//默认创建一个实例对象(而且是属于当前这个类的一个实例)
			//let obj = {};
			//obj.__proto__ = Func.prototype; //=>IE大部门浏览器中不允许我们直接操作__proto__
			
			let obj = Object.create(Func.prototype);
			//也会把类当做普通函数执行
			//执行的时候要保证函数中的this指向创建的实例
			let result = Func.call(obj, ...args);

			//若客户自己返回引用值,则以自己返回的为主,否则返回创建的实例
			if ((result !== null && typeof result === "object") || (typeof result === "function")) {
				return result;
			}
			return obj;
		}
		let sanmao = _new(Dog, '三毛');
		sanmao.bark(); //=>"wangwang"
		sanmao.sayName(); //=>"my name is 三毛"
		console.log(sanmao instanceof Dog); //=>true

为什么要加上result !== null,因为null的type 是 object

let result = Func.call(obj, ...args);通过.call改变this指向, 指向创建的实例

obj.proto = Func.prototype;有这行代码才能使obj真正属于这个类,不加的话,只能代表创建了一个空对象

两种变量对象

变量对象VO

变量对象VO是与执行上下文相关的特殊对象,用来存储上下文的函数声明,函数形参和变量。在global全局上下文中,变量对象也是全局对象自身,在函数上下文中,变量对象被表示为活动对象AO。

变量对象VO存储上下文中声明的以下内容

{
函数声明FD(如果在函数上下文中),—-不包含函数表达式
函数形参function arguments,
变量声明–注意b=10不是变量,但是var b = 10;是变量,有变量声明提升
alert(a); // undefined
alert(b); // “b” 没有声明

b = 10;
var a = 20;
}
var a = 10;

function test(x) {
  var b = 20;
};

test(30);

// 全局上下文的变量对象
VO(globalContext) = {
  a: 10,
  test: <reference to function>
};

// test函数上下文的变量对象
VO(test functionContext) = {
  x: 30,
  b: 20
};

变量对象VO分类

全局上下文的变量对象VO,函数上下文的变量对象VO

//全局上下文的变量对象VO就是全局对象
VO(globalContext) === global;

活动变量AO

当函数被调用后,这个特殊的活动对象就被创建了。它包含普通参数与特殊参数对象(具有索引属性的参数映射表)。活动对象在函数上下文中作为变量对象使用。

在函数执行上下文中,VO是不能直接访问的,此时由活动对象(activation object,缩写为AO)扮演VO的角色。

VO(functionContext) === AO;

Arguments对象是活动对象的一个属性,它包括如下属性:

  • callee — 指向当前函数的引用
  • length — 真正传递的参数个数
  • properties-indexes (字符串类型的整数) 属性的值就是函数的参数值(按参数列表从左到右排列)。
  • properties-indexes内部元素的个数等于arguments.length.
  • properties-indexes 的值和实际传递进来的参数之间是共享的。
function foo(x, y, z) {

  // 声明的函数参数数量arguments (x, y, z)
  alert(foo.length); // 3

  // 真正传进来的参数个数(only x, y)
  alert(arguments.length); // 2

  // 参数的callee是函数自身
  alert(arguments.callee === foo); // true

  // 参数共享

  alert(x === arguments[0]); // true
  alert(x); // 10

  arguments[0] = 20;
  alert(x); // 20

  x = 30;
  alert(arguments[0]); // 30

  // 不过,没有传进来的参数z,和参数的第3个索引值是不共享的

  z = 40;
  alert(arguments[2]); // undefined

  arguments[2] = 50;
  alert(z); // 40

}

foo(10, 20);

处理上下文代码的2个阶段

进入执行上下文和执行代码

进入执行上下文: 变量是进入上下文阶段放入VO中,也就是变量声明提升并且变量声明顺序上是在函数声明和形参声明后

if (true) {
  var a = 1;
} else {
  var b = 2;
}

alert(a); // 1
alert(b); // undefined,不是b没有声明,而是b的值是undefined
//变量声明在顺序上跟在函数声明和形式参数声明之后,而且在这个进入上下文阶段,变量声明不会干扰VO中已经存在的同名函数声明或形式参数声明
alert(x); // function

var x = 10;
alert(x); // 10

x = 20;

function x() {};

alert(x); // 20
function test(a, b) {
  var c = 10;
  function d() {}
  var e = function _e() {};
  (function x() {});
}

test(10); // call
当进入带有参数10的test函数上下文时,AO表现为如下:
//AO里并不包含函数“x”。这是因为“x” 是一个函数表达式(FunctionExpression, 缩写为 FE) 而不是函数声明,函数表达式不会影响VO
AO(test) = {
  a: 10,
  b: undefined,
  c: undefined,
  d: <reference to FunctionDeclaration "d">
  e: undefined
};

6月1号,儿童节快乐