继承模式、命名空间、对象枚举 【附:this指向问题、arguments、逗号操作符、对象克隆】
1、继承模式:【笔面试中碰到这种问题直接回答圣杯模式即可,共享原型/圣杯模式实现继承的是构造函数,这样操作,构造函数构造出的对象也都可以实现继承】
(1).传统形式(原型链): 使用原型链实现继承(其是不加选择的全继承),过多继承了自己不需要的属性;[也许只需要构造函数原型上的某些属性,但利用原型链其会把构造函数上的所有属性都继承]
(2).借用构造函数:call()/apply()方法的使用;[不是严格意义上的继承,没继承原型,但开发中却可灵活去使用];其借用别人的函数实现自己的功能,缺点:不能继承借用构造函数的原型(原型依旧是默认的构造函数原型),并且每次构造对象都要多运行函数;[其实现的继承也是不加选择的全继承]
【借用构造函数虽然代码篇幅少,编码效率更高,但性能却没有提升,因为其每次都需要多去执行函数,总之不能用代码篇幅/编码效率来衡量性能的高低,即使日常开发中尽量去降低代码的冗余】
(3).共享原型:希望去解决利用原型链实现继承的弊端,其只继承父辈原型上的属性就OK;使用共享原型直接操作构造函数,构造函数实现了其创建的对象也便实现;
----------->>>根据功能抽象封装成一个函数;
共享原型的缺点:实现A继承B,A.prototype = B.prototype,原型是对象,其是引用值(指向同一快存储空间),若是操作A.prototype,B.prototype必然也会变,Son.prototype={lastName:'wang'}; console.log(father.lastName);//'wang'; 其不能随便改动自己的原型;
--------------------->>>解决共享原型的bug,有了第四种圣杯模式
(4).圣杯模式:利用一个中间层来解决,使得儿子原型上属性更改不影响父亲的原型属性;
补充:雅虎YUI库也有关于圣杯模式的封装函数:【开源的大型JavaScript工具库 Yahoo User Interface library (YUI),如今官方宣布终止开发】
------------------->>> 私有化变量[闭包的第三点应用:可以实现封装、属性私有化];
2、命名空间:目的解决变量名、方法名冲突的问题;
管理变量,防止污染全局,模块化开发的两种方法:1.命名空间;2.利用闭包(闭包的第四点应用);
(1).命名空间:定义独立的空间(对象);【项目开发过程中,难免会有变量名/方法名等全局,局部冲突问题,最初解决方案是(名字+变量名)来区分:wangname/zname;后来使用命名空间,如今项目开发中基本不使用“命名空间”,常使用webpack,同步化开发软件】
(2).闭包:利用闭包形成私有化变量;【以后开发中基本都采用这种方式,执行初始化函数,里面的立即执行函数最终return 函数,函数可以多层嵌套操作,里面的变量都是私有化变量】
补充1:jQuery中可实现方法的连续调用(系统内部封装),js中对象没有该功能,可模仿实现方法的链式调用;【方法中若没有写return语句,系统默认return undefined;同时方法中的this指向为调用该方法的对象】
补充2:对象的两种调用方式;【obj["name"] 必须是字符串,obj[name]则表示其是变量,当然可以进行赋值,var name = "name"; obj[name];等同于访问对象的属性;其的优点便是可实现字符串的拼接,后续很多应用场景对象.的方式不能很好的解决,例如事件 on+click; on+mouse;等】
3.对象的枚举:[enumeration枚举 / 遍历 / 循环]
(1).for in : 对象的遍历方法(主要处理对象,遍历数组也可以);[相比于for循环的优势:for循环需要知道length来确定循环的圈数,并且也需要知道循环的是什么数据;开发过程中数据传输很可能不知道对象的属性是什么,有多少(length也不确定),使用for循环便不能实现遍历;for in 其是根据属性来确定循环圈数的,有多少属性就遍历几次]
【console.log(obj.prop);//undefined; 等同于console.log(obj [ "prop" ] ); 对象上没有该属性,当然返回undefined; prop在这里表示变量,在这里对象访问属性的方式必须是 obj [ prop ]; 绝对不能写obj[ "prop" ],写成这样就表示的是obj.prop】
【var arr = ['a','b','c']; for(var prop in arr ){ arr[prop]}; (prop指代的是索引值 prop ---->>>[0])】
(2).hasOwnProperty();过滤性方法,对象都可调用此方法;obj.hasOwnProperty(属性);判断该属性是否是对象上的属性,返回值是布尔值;[判断是否是自身的属性,不包括原型上以及原型链上的属性,不过其不会判断原型链顶端Object.prototype(判断到此停止)]
[property:特性,特性和属性也有区别,属性范围更大,后续会谈到,在此可以加深理解]
(3). in: 判断属性是否是该对象上的,包括原型以及原型链上继承的属性; "name" in obj; 判断返回值为布尔值;[使用场景很少,开发中几乎不使用,但作为与hasOwnProperty();的区别,考题中有]
(4).instanceof : A instanceof B : A对象是不是B构造函数构造出来的;【看A对象的原型链上有没有B的原型,拿这个判断最佳】,判断结果为布尔值;
function Person(){};var person = new Person(); var obj = {};
补充1:对象遍历中,必须使用obj[prop]遍历属性值;[prop表示一个变量,可使用其他名称,for(var key in obj);] 使用for in循环的方式最好加上hasOwnProperty()方法过滤掉原型上的属性;
【VSCode编辑器中可直接输入for in 自动感知;形式略有不同】
补充2:如何判断一个变量是对象还是数组?[]/{}
【建议使用第三种方法:因为后期涉及到父子域问题,例如页面中插入子页面(iframe框架的使用)(需要进行跨域处理),方法1/2判断会出错;方法三不受影响,什么时候都好使】
方法1:constructor属性;(判断其的构造函数,{}.constructor会报错,需要赋值变量才ok,系统还不能识别 =====>>> 回归:123.toString();错误原因类似,也需要赋值变量再访问,系统不能识别)
方法2:instanceof:
方法3:Object上的toString();方法,而不是重写的方法;
Object.prototype.toString = function(){ }; 使用时谁调用toString();方法,其会返回,相应的“ [ object xxx] ”的形式,说明内部一定有this,谁调用this就指向谁,根据this来确认返回值;所以可以使用call();/apply();改变this指向;
4、this指向问题:掌握五个方面就OK;
(1).全局中this指向window;(全局window以及预编译中生成的GO对象);
(2).预编译函数执行前生成的AO对象中this指向window;(AO对象中除了预编译过程中的变量和函数,里面还包含arguments, this)
(3).构造函数创建对象过程中(new的时候):this指向this{};
(4).call();/apply();中改变this指向;
(5).对象中的方法或者是函数,谁调用this便指向谁;
this笔面试题:
例题1:
例题2:
例题3:
例题4:
5、实参列表arguments:(1).其存储于预编译中的AO对象;(2).其是类数组,有两个属性length;callee; 使用方法同length相同;arguments.callee; 返回值为函数的引用function test(){};[es5.0严格模式下没有此属性]
补充:caller属性(每个函数都有该属性),指向函数被调用的那个函数的引用(它在那个函数中被执行),常常与arguments.callee属性一同考查,应用场景不多;
6、逗号操作符:可简化处理一些函数;[小知识点]
立即执行函数中不能放两个函数,而且不能不写逗号,不符合语法哈;
7、对象克隆:【克隆与继承还是有区别的,其有两种形式:浅层克隆和深层克隆】
(1).浅层克隆:对象中属性值可为原始值或引用值,克隆后的新对象,若是修改原始值属性,无影响,但若是修改引用值(共同指向同一块内存空间),则对原对象有影响;
容错模式解析:无论浅层克隆还是深度克隆,函数都有两个形参,但第二个形参是可以为空对象,好多人疑惑这一点,分析:因为最后把克隆后的值return了,所以可以不传第二个参数; var a = clone(b);a就是b克隆出来的;[ clone(b),把克隆的值return了,返回的值赋给了a ]
(2).深层克隆:解决浅层克隆的bug(若属性是引用值提供解决方案);
【 首先考虑:对象属性若为原始值则直接克隆,若是引用值需要分析其是对象还是数组(不考虑方法等其它引用值),再进行二次判断对象/数组里面的内容是原始值还是引用值,递归调用;主要解决的就是引用值克隆的bug】
伪代码:[1].遍历对象(for in); [2].判断是否是原始值(typeof ()); [3].判断是数组还是对象(判断方法有三种 constructor instanceof toString();建议用第三种;前两种跨域下无法判断); [4].建立相应的数组和对象; [5].递归调用;
版本1:Object.prototype.toString.call();//原型上的toString();调用call(); 必须使用方法名调用;不能是Object.prototype.toString().call();//报错;[call();方法可被任何函数调用改变this指向]
优化版: