js---圣杯模式 ,枚举,如何区分数组和对象,callee

本文深入探讨JavaScript中的四种经典继承模式:原型链继承、借用构造函数、公有原型和圣杯模式,并介绍了每种模式的特点及局限性。同时,文中还涉及了一些JavaScript的基础概念如枚举、instanceof操作符和callee属性。

1. 继承发展史(从a发展到d)

a 原型链继承:过多的继承没有用的属性

function Grand(){this.grand='grand';this.name='haha'}
function Father(){this.father='father'}
function Son(){this.son='son'}
var grand = new Grand();
Father.prototype=grand;
var father = new Father();
Son.prototype=father;
var son = new Son();
console.log(son.son,son.father,son.grand);//son father garnd  可以看出来形成了原型链,但是我可能不想要grand身上的name却也继承了

b 借用构造函数---不能借用构造函数的原型,每次都要多调用一次函数。降低效率

function Father(){
    this.address=123;this.familyCount=3;
}
function Son(){
    Father.call(this);  //--->  this.address=123;this.familyCount=3;//重复 
    this.name='xiaoming'
}

c 公有原型---明显的缺陷:修改子,会影响父

function Father(){
   this.familyCount=3;
}
Father.prototype.address=123;
function Son(){
    this.name = 'xiaoming';
}
Son.prototype = Father.prototype;//现在Father和Son的原型都是Father.prototype那个房间里的东西,一动都变
var father = new Father();
var son = new Son();
console.log(son.address,father.address);//123 123
Son.prototype.address = 234;
console.log(son.address,father.address);//234 234  如果子搬出去住了,想改属性的值,或者给自己加了一个属性,父的也会变!!

d 圣杯模式---利用中间函数构造出的对象作为中间过度原型,产生原型链

function Father(){
   this.familyCount=3;
}
function Son(){
    this.name = 'xiaoming';
}
Father.prototype.address='haha';
//封装一个继承函数 闭包的形式可以使F隐起来,不被访问到---闭包的私有化变量的使用方法
var inherit = (function(){
    var F=function(){};
    return function(target,source){
       F.prototype = source.prototype; //将source的原型和F的原型变成同一个       
       target.prototype = new F();  //创建一个__proto__指向source.prototype的{},并让target构造出的对象都继承自这个{}
       //此时,修改target.prototype相当于是在这个空的{}中做修改,不会有任何影响
       target.prototype.constuctor = target;  //修改原型会导致原型失去自己构造函数的指向
       target.prototype.uber = source.prototype; //超类:记录真正继承的的原型
    };
})();
inherit(Son,Father);
var son=new Son();
console.log(son.address);//haha
2. 枚举---就是遍历
function Person(name,age){
    this.name=name;
    this.age=age;
}
Person.prototype.secret='每个人都不一样';
var obj = new Person('xiaozhang',24);
for(var key in obj){
  //过滤不是当前对象自己的属性
   if(obj.hasOwnProperty(key)){
       console.log(key+'是当前对象自己的','值是:'+obj[key]);//必须用obj[key],不能用obj.key
                                                   //因为用.取属性会让系统自动转换为obj['key'],就是说系统默认点后就是字符串形式
    }else{
    //为false----会打印出原型链上所有人为设置的属性,系统自带的不会打印出来!!
         console.log(key+'是继承来的');
    }
}
'name' in obj;//true
'secret' in obj;//true     in不论是当前对象里的还是原型链上的人为设置的属性都是返回true

3. A instanceof B----A对象的原型链上 有没有 B的原型 A对象是不是可以继承B的原型  A对象 是不是 由B函数构造出来的

例子---区分数组和对象:

var arr=[1,2],obj={};

//法一: 由谁构造的---可能会不靠谱
console.log(arr.constructor,obj.constructor); //function Array(){}   function Object(){}

//法二: instanceof  原理差不多,原型链上有没有Array  ---可能不靠谱
console.log(arr instanceof Array,obj instanceof Array); //true false

//法三: 调用Object的toString方法 建议使用这种方式,其他两种方式在父子域的情况下会出错,就iframe引入一个子页面,在子页面[] instanceof Array结果是false,因为这里的Array等于取了父域中的Array,跨域
console.log(Object.prototype.toString.call(arr),Object.prototype.toString.call(obj)); //[Object Array] [Object Object]

4. callee---属于arguments 的一个函数,就是自己

例子---立即执行函数没有名字,但是要写递归的时候:

var num=(function(n){  //没函数名
   if(n==1){return 1}
   return n*arguments.callee(n-1);//这里回调自己
}(5));
console.log(num);//120

5. 写个小栗子---this

var foo = 123;
function print(){
   this.foo = 234;
   console.log(foo);
}
new print();//this是{}---> this.foo=234---> {foo:234}---> 打印的是foo,不是this.foo---> foo是个变量,请在作用域链上找,只有全局有个foo是123
print(); //this是window ---> window.foo = 234 ---> 所以foo从123变成了234 



<think>首先,用户的问题是关于程序调用过程中,被调用函数(callee)如何判断从栈中弹出的是callee-saved寄存器还是其他内容。用户提供了引用[1][2],内容涉及函数调用、栈帧、寄存器调用约定。 关键点: - 调用约定(calling convention)规定了函数调用时如何传递参数、返回地址、保存寄存器等。 - 在x86架构中,call指令将返回地址压栈,ret指令弹出返回地址。 - Callee-saved寄存器(也称为非易失寄存器)在函数调用后必须由被调用函数恢复其值。 - 调用者(caller)被调用者(callee)有各自的规则:caller保存易失寄存器,callee保存非易失寄存器。 用户的问题是:callee如何判断从栈中pop的是callee-saved寄存器还是其他内容? 分析: - 在函数调用期间,栈用于存储返回地址、参数、本地变量保存的寄存器。 -callee开始执行时,它通常会保存callee-saved寄存器的值到自己栈帧中。 - 在函数返回前,callee会恢复这些寄存器的值。 - 栈的内容不是由callee随意判断的;而是由调用约定函数序言/尾声(prologue/epilogue)管理。 callee不直接"判断"栈中内容是什么;它知道自己的栈帧布局,因为它自己设置了栈帧。 步骤: 1. **函数序言(Prologue)**:当callee被调用时,它执行序言代码,通常包括保存callee-saved寄存器到栈上。 2. **函数体**:执行函数逻辑。 3. **函数尾声(Epilogue)**:在返回前,恢复保存的寄存器值,包括从栈中弹出它们。 4. **ret指令**:最后弹出返回地址并返回。 所以,callee在尾声时pop寄存器值,是因为它知道这些值是自己保存的callee-saved寄存器。 具体到用户问题: - Callee-saved寄存器的值是callee自己保存的,所以它知道何时pop它们。 - 返回地址是由call指令压入的,callee在ret指令时弹出它。 - 其他内容如参数或临时变量,callee根据调用约定函数定义知道如何处理。 在标准调用约定中: - 例如,在cdecl或System V ABI中,callee负责保存恢复特定寄存器(如EBX, EBP, ESI, EDI in x86)。 - Callee在序言中用push指令保存这些寄存器。 - 在尾声中用pop指令恢复它们。 因此,callee不需要"判断"栈中内容是callee-saved寄存器还是其他;它通过代码结构知道: - 在尾声代码中,pop指令是针对callee-saved寄存器的。 - 返回地址是通过ret指令处理的。 引用中的相关内容: - 引用[1]提到编译器可能利用栈帧保存临时结果或callee-saved寄存器。 - 引用[2]描述了callret指令的操作,以及调用规则:caller保存caller-saved寄存器,callee保存callee-saved寄存器。 回答问题结构: - 系统指令要求使用中文回答。 - 行内数学表达式用$...$,但我这里可能不需要数学表达式。 - 独立公式用$$...$$,但问题不涉及数学公式。 - 生成相关问题。 - 末尾添加引用标识,如[^1]。 回答结构: 1. 解释callee如何管理栈。 2. 强调调用约定的作用。 3. 说明callee不需要动态判断,因为栈帧布局是固定的。 4. 引用提供的引用。 最终回答: - 澄清:callee从栈中pop值是基于函数序言尾声的代码序列。在尾声,callee弹出callee-saved寄存器值,然后弹出返回地址(通过ret指令)。 - 栈中内容不是混合的;callee保存的寄存器返回地址是分开的。 相关问题生成: - 根据用户兴趣,生成关于函数调用、栈帧、寄存器保存的问题。 </think>在程序调用过程中,被调用函数(callee)并不需要动态“判断”从栈中弹出的是callee-saved寄存器还是其他内容,因为栈帧的布局是由调用约定(calling convention)预先定义的,并通过函数序言(prologue)尾声(epilogue)代码序列管理。以下是详细解释,基于调用规则引用内容: ### 1. **栈帧布局的固定性** - 每个函数的栈帧结构在编译时已确定(根据调用约定),包括返回地址、参数、本地变量**callee-saved寄存器的保存区域**。被调用函数在入口处(序言)主动保存callee-saved寄存器(如x86中的EBX、EBP、ESI、EDI),在返回前(尾声)恢复它们。 - 例如,在x86架构的标准调用约定(如System V ABI)中: - **函数序言**:callee通过`push`指令将所有callee-saved寄存器保存到栈上。 - **函数尾声**:callee通过`pop`指令按相反顺序恢复这些寄存器。 - 因此,callee在`pop`操作时,完全知道哪些栈位置对应callee-saved寄存器,因为这些是由callee自己保存的。 ### 2. **callee-saved寄存器的恢复过程** - 根据引用[2],call指令在调用时将返回地址压栈,而callee则负责保存恢复callee-saved寄存器。具体流程如下: - 在函数序言中,callee执行类似以下汇编代码: ```assembly push ebp ; 保存callee-saved寄存器EBP mov ebp, esp ; 设置新栈帧基址 push ebx ; 保存callee-saved寄存器EBX ; ...其他保存操作 ``` - 这里,callee明确知道`push ebx`是将寄存器值保存到栈顶。 - 在函数尾声(返回前),callee执行: ```assembly pop ebx ; 恢复EBX(callee-saved寄存器) pop ebp ; 恢复EBP ret ; 弹出返回地址并返回 ``` - `pop ebx``pop ebp`操作的目标寄存器是固定的,callee无需额外判断。栈中内容在此阶段**一定是callee自己保存的值**,因为调用规则保证了栈帧的一致性[^2]。 - **关键点**:callee不会“混淆”callee-saved寄存器返回地址,因为: - 返回地址由call指令压入,位于callee栈帧的最底部(或顶部,取决于栈增长方向)。 - callee-saved寄存器的保存区域位于返回地址之上,由callee管理。 - ret指令专门处理返回地址的弹出(不与寄存器恢复冲突)。 ### 3. **调用规则的作用** - 调用约定严格区分callercallee的责任(引用[1][2]): - **Caller规则**:保存caller-saved寄存器(易失寄存器),并传递参数。 - **Callee规则**:保存并恢复callee-saved寄存器(非易失寄存器),并在ret前清理栈帧。 - 因此,callee无需动态检测栈内内容类型。栈帧是“自描述”的: - Callee的汇编代码显式指定了pop操作的对象(例如`pop ebx`),编译器根据约定生成确定代码。 - 如果调用约定被违反(如错误的手写汇编),行为是未定义的,但这在标准编程中罕见。 ### 4. **为什么callee不需要判断** - 栈内存的访问是顺序结构化的:callee在尾声按FIFO(先进后出)顺序弹出值,且弹出顺序必须与保存顺序相反。 - 示例场景:假设callee保存了EBXEBP。在尾声,`pop ebx`一定对应之前`push ebx`的值,因为栈指针(ESP)直接指向该保存位置。其他内容(如参数或临时结果)由caller管理或位于栈帧不同区域,callee不会在恢复寄存器时访问它们[^1][^2]。 总之,callee通过调用约定固定代码序列(而非运行时判断)确保pop操作仅针对callee-saved寄存器。实现依赖于编译器汇编代码的正确性。 [^1]: 编译器可能会利用栈帧的一部分空间来保存一些临时计算结果,或者根据调用约定,在调用其他函数前保存某些需要被调用者保存的寄存器(Callee-saved registers),以确保在子函数返回后,这些寄存器的值能够被恢复。 [^2]: call指令首先将当前执行指令地址入栈,然后无条件转移到由标签指示的指令。ret指令弹出栈中保存的指令地址,然后无条件转移到保存的指令地址执行。调用规则规定函数调用及返回的规则,调用者规则被调用者规则确保栈布局一致性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值