《JavaScript程序设计》课堂交流区问题汇总(进阶篇)
2016-05-18 22:03
471 查看
本课程为网易云课堂 - - 前端开发工程师 - - 《JavaScript程序设计》学习总结
比如如下代码
修改对象obj2同时会改变obj1,那么如果我们需要克隆出一个独立但属性、方法完全一样的对象,该如何实现?
解答:
使用for in
参考:js中如何复制一个对象并获取其所有属性和属性对应的值
定义一个clone方法来实现
通过object原型扩展实现
参考:js对象复制
强类型定义语言:强制数据类型定义的语言。也就是说,一旦一个变量被指定了某个数据类型,如果不经过强制转换,那么它就永远是这个数据类型了。举个例子:如果你定义了一个整型变量a,那么程序根本不可能将a当作字符串类型处理。强类型定义语言是类型安全的语言。
弱类型定义语言:数据类型可以被忽略的语言。它与强类型定义语言相反, 一个变量可以赋不同数据类型的值。强类型定义语言在速度上可能略逊色于弱类型定义语言,但是强类型定义语言带来的严谨性能够有效的避免许多错误。
二者的区别,在于计算时是否可以不同类型之间对使用者透明地隐式转换。从使用者的角度来看,如果一个语言可以隐式转换它的所有类型,那么它的变量、表达式等在参与运算时,即使类型不正确,也能通过隐式转换来得到正确地类型,这对使用者而言,就好像所有类型都能进行所有运算一样,所以这样的语言被称作弱类型。
与此相对,强类型语言的类型之间不一定有隐式转换(比如C++是一门强类型语言,但C++中double和int可以互相转换,但double和任何类型的指针之间
四则运算
加法运算符+是双目运算符,只要其中一个是String类型,表达式的值便是一个String。
对于其他的四则运算,只有其中一个是Number类型,表达式的值便是一个Number。
对于非法字符的情况通常会返回NaN:
判断语句
判断语句中的判断条件需要是Boolean类型,所以条件表达式会被隐式转换为Boolean。
其转换规则同Boolean的构造函数。比如:
Native代码调用
JavaScript宿主环境都会提供大量的对象,它们往往不少通过JavaScript来实现的。 JavaScript给这些函数传入的参数也会进行隐式转换。例如BOM提供的alert方法接受String类型的参数:
==
JS的非严格匹配时会进行隐式类型转换。
typeof : 可以识别除null之外的基本类型及对象类型,不能识别具体对象;
instanceof : 可以识别Object类型和自定义类型,不能识别基本类型
constructor : 识别除null和undefined的内置类型及自定义类型
Object.prototype.toString().call(obj) : 可以识别标准类型以及内置对象类型;不能识别自定义对象类型;
组合封装函数
解析:
结果如下:
由于这两点,程序中第一个函数和第三个函数都被提升到了前面,由于函数名
相同,后一个覆盖前一个,所以起作用的只有第三个函数,同时第二个以表达式定义的函数的变量名add1也被提升到了程序的前面。
当第一次调用由于变量add1还没有被定义,函数定义就会覆盖掉变量add1的声明,所以执行结果是调用第三个add函数; 当调用第二个add1时,变量add1已经被定义了,所以覆盖掉了函数声明, 所以之后调用add1执行的都是 以函数表达式定义的函数add1了。
解析:
1. helper中的this指向Window全局变量
2. 不能。因为 myNumber.value并未增加
方法一:可以把helper调整为方法函数,这样helper就可以正确引用myNumber为this了。
方法二:使用闭包
方法三:使用方法调用模式,因为方法调用模式可以指向调用者
方法四:使用apply(call)调用模式,将当前helper方法借用给myNumber对象使用,这样this指向的就是myNumber对象
函数声明的执行环境为的上一层执行环境为window,函数表达式的上一层执行环境指向调用它的执行环境。
函数声明会是全局作用域,outer指向window,函数表达式在执行时才会解析,outer指向上一级函数环境。
闭包的应用场景主要有:保存函数的状态,性能优化和封装;
闭包有优点也有缺点,滥用闭包是非常不可取的,
使用闭包的注意点
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
原型继承和类继承是两种认知模式,本质上都是为了抽象(复用代码)。相对于类,原型更初级且更灵活。因此当一个系统内没有太多关联的事物的时候,用原型明显比用类更灵活便捷。
v原型继承的便捷性表现在系统中对象较少的时候,原型继承不需要构造额外的抽象类和接口就可以实现复用。(如系统里只有猫和狗两种动物的话,没必要再为它们构造一个抽象的“动物类”)
原型继承的灵活性还表现在复用模式更加灵活。由于原型和类的模式不一样,所以对复用的判断标准也就不一样,例如把一个红色皮球当做一个太阳的原型,当然是可以的(反过来也行),但显然不能将“恒星类”当做太阳和红球的公共父类(倒是可以用“球体”这个类作为它们的公共父类)。
解答:
解答:
问题一:复制对象
通过课程学习我们知道,对象作为引用类型,使用运算符=时,只是复制了对象的地址。比如如下代码
var obj1 = {a:1}; var obj2 = obj1; obj2.a = 2; // 此时obj1.a ===
修改对象obj2同时会改变obj1,那么如果我们需要克隆出一个独立但属性、方法完全一样的对象,该如何实现?
解答:
使用for in
var obj2=new Object(); for(var p in obj) { var name=p;//属性名称 var value=obj[p];//属性对应的值 obj2[name]=obj[p]; }
参考:js中如何复制一个对象并获取其所有属性和属性对应的值
定义一个clone方法来实现
function clone(myObj){ if(typeof(myObj) != 'object') return myObj; if(myObj == null) return myObj; var myNewObj = new Object(); for(var i in myObj) myNewObj[i] = clone(myObj[i]); return myNewObj; }
通过object原型扩展实现
Object.prototype.Clone = function() { var objClone; if ( this.constructor == Object ) objClone = new this.constructor(); else objClone = new this.constructor(this.valueOf()); for ( var key in this ) { if ( objClone[key] != this[key] ) { if ( typeof(this[key]) == 'object' ) { objClone[key] = this[key].Clone(); } else { objClone[key] = this[key]; } } } objClone.toString = this.toString; objClone.valueOf = this.valueOf; return objClone; }
参考:js对象复制
问题二:JS和强类型语言(比如C++)在类型方面的主要区别
解答:强类型定义语言:强制数据类型定义的语言。也就是说,一旦一个变量被指定了某个数据类型,如果不经过强制转换,那么它就永远是这个数据类型了。举个例子:如果你定义了一个整型变量a,那么程序根本不可能将a当作字符串类型处理。强类型定义语言是类型安全的语言。
弱类型定义语言:数据类型可以被忽略的语言。它与强类型定义语言相反, 一个变量可以赋不同数据类型的值。强类型定义语言在速度上可能略逊色于弱类型定义语言,但是强类型定义语言带来的严谨性能够有效的避免许多错误。
二者的区别,在于计算时是否可以不同类型之间对使用者透明地隐式转换。从使用者的角度来看,如果一个语言可以隐式转换它的所有类型,那么它的变量、表达式等在参与运算时,即使类型不正确,也能通过隐式转换来得到正确地类型,这对使用者而言,就好像所有类型都能进行所有运算一样,所以这样的语言被称作弱类型。
与此相对,强类型语言的类型之间不一定有隐式转换(比如C++是一门强类型语言,但C++中double和int可以互相转换,但double和任何类型的指针之间
问题三:隐式类型转换场景
解答:四则运算
加法运算符+是双目运算符,只要其中一个是String类型,表达式的值便是一个String。
对于其他的四则运算,只有其中一个是Number类型,表达式的值便是一个Number。
对于非法字符的情况通常会返回NaN:
'1' * 'a' // => NaN,这是因为parseInt(a)值为NaN,1 * NaN 还是 NaN
判断语句
判断语句中的判断条件需要是Boolean类型,所以条件表达式会被隐式转换为Boolean。
其转换规则同Boolean的构造函数。比如:
var obj = {};if(obj){ while(obj);}
Native代码调用
JavaScript宿主环境都会提供大量的对象,它们往往不少通过JavaScript来实现的。 JavaScript给这些函数传入的参数也会进行隐式转换。例如BOM提供的alert方法接受String类型的参数:
alert({a: 1}); // => [object Object]
==
JS的非严格匹配时会进行隐式类型转换。
问题四:识别类型方法
解答:typeof : 可以识别除null之外的基本类型及对象类型,不能识别具体对象;
typeof 1;//"number" typeof "1";//"string" typeof {};//"object" typeof [];//"object" typeof undefined;//"undefined"; typeof null;//"object" typeof true;//"boolean"
instanceof : 可以识别Object类型和自定义类型,不能识别基本类型
[] instanceof Array;//true ({}) instanceof Object;//true (1) instanceof Object;//false (1) instanceof Number;//false
constructor : 识别除null和undefined的内置类型及自定义类型
(1).constructor===Number;//true "1".constructor===String;//true
Object.prototype.toString().call(obj) : 可以识别标准类型以及内置对象类型;不能识别自定义对象类型;
Object.prototype.toString().call(null);//"[object Null]" Object.prototype.toString().call(undefined);//"[object Undefined]"
组合封装函数
/* * 获取对象构造函数名称 * 视频中关于getConstructorName函数写法存在bug,导致传入 0, false, "", NaN 这些值时,得到错误的返回结果。 * 1. 入参obj如果是undefined和null,则返回其自身; * 2. 入参obj如果是其他值,则返回obj.constructor&&obj.constructor.toString().match(/function\s*([^(]*)/)[1]的结果; */ function getConstructorName(obj){ return (obj===undefined||obj===null)?obj:(obj.constructor&&obj.constructor.toString().match(/function\s*([^(]*)/)[1]); }
问题五:函数声明和函数表达式定义同一个函数时,执行的是哪个?
// 以下代码执行时,三次打印分别输出什么?为什么? function add1(i){ console.log("函数声明:"+(i+1)); } add1(1); var add1 = function(i){ console.log("函数表达式:"+(i+10)); } add1(1); function add1(i) { console.log("函数声明:"+(i+100)); } add1(1);
解析:
function add1(i){ console.log("函数声明:"+(i+1)); } add1(1); //函数声明:101 因为函数声明会被预置到代码顶部,相同的声明后一个起作用,所以调用的是下页的那个函数声明 var add1 = function(i){ console.log("函数表达式:"+(i+10)); } add1(1); //函数表达式:11 因为函数声明已被同名函数表达式覆盖 function add1(i) { console.log("函数声明:"+(i+100)); } add1(1); //函数表达式:11 因为函数声明已被同名函数表达式覆盖
//预解析如下 var add1; function add1(i) { console.log("函数声明:"+(i+100)); } add1(1); add1 = function(i){ console.log("函数表达式:"+(i+10)); } add1(1); add1(1);
结果如下:
函数声明:101 函数表达式:11 函数表达式:11
由于这两点,程序中第一个函数和第三个函数都被提升到了前面,由于函数名
相同,后一个覆盖前一个,所以起作用的只有第三个函数,同时第二个以表达式定义的函数的变量名add1也被提升到了程序的前面。
当第一次调用由于变量add1还没有被定义,函数定义就会覆盖掉变量add1的声明,所以执行结果是调用第三个add函数; 当调用第二个add1时,变量add1已经被定义了,所以覆盖掉了函数声明, 所以之后调用add1执行的都是 以函数表达式定义的函数add1了。
问题六:对象方法中定义的子函数,子函数执行时this指向哪里?
三个问题:以下代码中打印的this是个什么对象? 这段代码能否实现使myNumber.value加1的功能? 在不放弃helper函数的前提下,有哪些修改方法可以实现正确的功能?
var myNumber = { value: 1, add: function(i){ var helper = function(i){ console.log(this); this.value += i; } helper(i); } } myNumber.add(1);
解析:
1. helper中的this指向Window全局变量
2. 不能。因为 myNumber.value并未增加
方法一:可以把helper调整为方法函数,这样helper就可以正确引用myNumber为this了。
var myNumber = { value:1, helper:function(i) { console.log(this); this.value +=i; }, add: function(i) { this.helper(i); } } myNumber.add(1); console.log(myNumber.value);
方法二:使用闭包
var myNumber = { value: 1, add: function(i){ var thisnew = this; // 构建闭包 var helper = function(i){ console.log(thisnew); thisnew.value += i; } helper(i); } }
方法三:使用方法调用模式,因为方法调用模式可以指向调用者
var myNumber = { value: 1, add: function(i){ var helper = function(i){ console.log(this); this.value += i; } // 新建一个o对象等于myNumber,将helper方法赋值给该对象, // 然后使用方法调用模式,这样可以让helper中的this指向调用者o,即myNumber var o = myNumber; o.fn = helper; o.fn(i); } }
方法四:使用apply(call)调用模式,将当前helper方法借用给myNumber对象使用,这样this指向的就是myNumber对象
var myNumber = { value: 1, add: function(i){ var helper = function(i){ console.log(this); this.value += i; } // myNumber对象借用helper方法,helper中的this将指向myNumber对象 helper.apply(myNumber,[i]); //apply方法 helper.call(myNumber,i); //call方法 } }
问题七:在变量作用域方面,函数声明和函数表达式有什么区别?
解答:函数声明的执行环境为的上一层执行环境为window,函数表达式的上一层执行环境指向调用它的执行环境。
函数声明会是全局作用域,outer指向window,函数表达式在执行时才会解析,outer指向上一级函数环境。
问题八:闭包的应用场景有哪些?
解答:闭包的应用场景主要有:保存函数的状态,性能优化和封装;
闭包有优点也有缺点,滥用闭包是非常不可取的,
使用闭包的注意点
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
问题九:原型继承和类继承有什么区别?
解答:原型继承和类继承是两种认知模式,本质上都是为了抽象(复用代码)。相对于类,原型更初级且更灵活。因此当一个系统内没有太多关联的事物的时候,用原型明显比用类更灵活便捷。
v原型继承的便捷性表现在系统中对象较少的时候,原型继承不需要构造额外的抽象类和接口就可以实现复用。(如系统里只有猫和狗两种动物的话,没必要再为它们构造一个抽象的“动物类”)
原型继承的灵活性还表现在复用模式更加灵活。由于原型和类的模式不一样,所以对复用的判断标准也就不一样,例如把一个红色皮球当做一个太阳的原型,当然是可以的(反过来也行),但显然不能将“恒星类”当做太阳和红球的公共父类(倒是可以用“球体”这个类作为它们的公共父类)。
问题十:实现一个Circle类
编程实现:a.创建一个圆(Circle)的类,并定义该类的一个属性(半径)和两个方法(周长和面积),其中圆的半径可以通过构造函数初始化 b.创建圆的一个对象,并调用该对象的方法计算圆的周长和面积
解答:
function circle(r){ var cir = new Object(); this.r = r; var perimeter = Math.PI * 2 * this.r; var area = Math.PI * this.r * this.r; cir.run = function(){ return "圆的周长:"+perimeter+" "+"圆的面积:"+area; }; return cir; } var cir1 = circle(2); cir1.run(); //"圆的周长:12.566370614359172 圆的面积:12.566370614359172" var cir2 = circle(3); cir2.run(); //"圆的周长:18.84955592153876 圆的面积:28.274333882308138"
问题十一:请使用Js代码写出一个类继承的模型
请使用Js代码写出一个类继承的模型,需包含以下实现:定义父类和子类,并创建父类和子类的属性和方法 子类继承父类的属性和方法 在创建子类对象时,调用父类构造函数
解答:
function Car(brand){//父类构造器 this.brand = brand;//定义父类属性 } Car.prototype.getBrand = function(){//定义父类方法 console.log(this.brand); }; function Audi(owner){//子类构造器 Car.call(this,"Audi");//继承并设置父类属性 this.owner = owner;//定义子类属性 } Audi.prototype = new Car();//设置子类原型为父类的一个实例,则子类原型的__proto__指向父类原型 Audi.prototype.constructor = Audi;//设置子类的constructor为子类构造器 Audi.prototype.getBrand = function(){ Car.prototype.getBrand.call(this);//继承父类方法 }; Audi.prototype.getOwner = function(){//定义子类方法 console.log(this.owner); } var a4 = new Audi("jhl"); a4.getBrand();//Audi a4.getOwner();//jhl
相关文章推荐
- Spark Streaming+Kafka+Hive+JSON实时增量计算示例
- JavaScript:Date类型
- JSP02
- JavaScript使用Sqlite数据库
- 报错信息 The jsp:param action must not be used outside the jsp:include, jsp:forward, or jsp:params elements 的原因及解决办法
- 5月17日 AJAX之JSON
- Js中的天坑----JS:parseInt("08")和“09”返回0
- Upload files in ASP.NET MVC with JavaScript and C#
- javaScript 基础
- js原型链
- Jsoup 教程
- jsp中EL表达式
- Js中的天坑----JS:parseInt("08")和“09”返回0
- jsp中的开头的作用
- Json 数据
- javaScript实现HTML页面分屏滚动效果
- 【Extjs学习笔记01】使用Sencha Cmd构建项目
- bzoj1822 [JSOI2010]Frozen Nova 冷冻波
- js获取隐藏元素宽高的方法
- JS跨域问题以及采用JSONP方式解决跨域问题