問題1: apply、call 、bind有什么作用,什么區(qū)別
在實際編程過程中,this的動態(tài)切換很容易讓人混淆它的指向,為了把this固定下來,避免意想不到的情況,javascript提供了apply,call,bind三種方法來切換/ 固定this的指向。
function.prototype.apply()
函數(shù)實例的call方法,可以指定該函數(shù)內(nèi)部this的指向,即函數(shù)執(zhí)行時所在的作用域,然后在所指定的作用域中,調(diào)用該函數(shù)
var obj = {}
function foo(){
return this
}
foo() === this //true
foo.call(obj) === obj //true
上面代碼中,在全局環(huán)境運行函數(shù)foo時,this指向全局環(huán)境;call方法可以改變this的指向,指定this指向?qū)ο髈bj,然后
在對象obj的作用域中運行函數(shù)foo
call方法的參數(shù),應該是一個對象。如果參數(shù)為空、null和undefined,則默認傳入全局對象。
var n = 123
var obj = {n: 456}
function a(){
console.log(this.n)
}
a.call() // 123
a.call(null)//123
a.call(undefined)//123
a.call(window)//123
a.call(obj) // 456
如果call方法的參數(shù)是一個原始值,那么這個原始值會自動轉(zhuǎn)成對應的包裝對象,然后傳入call方法
var f = function(){
return this
}
f.call(5)
5是一個原始值,他的包裝對象為Number,相當于f.call(Number),返回Number{[[primitive]] : 5}
call方法還可以接受多個參數(shù)。
func.call(thisValue,arg1,arg2,...)
call的第一個參數(shù)就是this所要指向的對象,后面的參數(shù)則是函數(shù)調(diào)用時所需的參數(shù)。
function add(a,b){
return a+b
}
add.call(this,1,2) // 3
call方法的一個應用是調(diào)用對象的原生方法。
var obj = {}
obj.hasOwnProperty('toString')//false
//覆蓋掉繼承的hasOwnProperty方法
obj.hasOwnProperty = function(){
return true
}
obj.hasOwnProperty('toString')//true,傳入任何參數(shù)都返回true
Object.prototype.hasOwnproperty.call(obj,'toString') // false
function.prototype.apply()
apply方法的作用與call類似,也是改變this的指向,然后再調(diào)用該函數(shù)。唯一的區(qū)別是,它接收一個數(shù)組作為函數(shù)執(zhí)行時
的參數(shù),使用格式如下
func.apply(thisValue,[arg1,arg2,...])
利用這一點,可以做一些有用的應用。
1. 找出數(shù)組最大元素
javascript不提供找出數(shù)組最大元素的函數(shù)。結(jié)合使用apply方法和Math.max方法,就可以返回數(shù)組的最大元素.
var a = [2,4,7,10,56]
Math.max.apply(null,a)//56
2.轉(zhuǎn)換類似數(shù)組的對象
利用數(shù)組對象的slice方法,可以將一個類似數(shù)組的對象(比如arguments對象)轉(zhuǎn)為真正的數(shù)組。
Array.prototype.slice.apply({0:1,length:1})//[1],類數(shù)組對象有個length的屬性
Array.prototype.slice.apply({0:1})//[],沒有l(wèi)ength屬性,默認不被識別為類數(shù)組對象,返回空數(shù)組
function.prototype.bind()
bind方法用于將函數(shù)體內(nèi)的this綁定到某個對象,然后返回一個新函數(shù)。
var d = new Date()
d.getTime() // 1491058448289
var print = d.getTime
print()
上面代碼中,我們將d.getTime方法賦給變量print,然后調(diào)用print就報錯了。這是因為getTime方法內(nèi)部的this,綁定
Date對象的實例,當把getTime方法賦給變量print以后,在全部環(huán)境中調(diào)用print函數(shù),內(nèi)部的this已經(jīng)不指向Date對象的實例了
bind方法可以解決這個問題,讓log方法綁定console對象。
var print = d.getTime.bind(d)
print() //1491058927995
下面是一個更清晰的例子
var counter = {
count: 0,
inc: function(){
this.count++
}
}
counter.count // 0
counter.inc()
counter.count // 1
上面代碼中,counter.inc內(nèi)部的this,默認指向counter對象,如果將這個方法賦值給另一個變量,就會報錯
var counter = {
count: 0,
inc: function(){
this.count++
}
}
var func = counter.inc // 相當于 var func = function(){this.count++}
func()
counter.count // 0
count // NaN全局環(huán)境中并沒有count這個變量
上面代碼中,函數(shù)func是在全局環(huán)境中運行的,這時inc內(nèi)部的this指向頂層對象window,所以counter.count是不會
變的,反而創(chuàng)建了一個全局變量count。因為window.count原來等于undefined,進行遞增運算后undefined++就等于
NaN.
為了解決這個問題,可以使用bind方法,將inc內(nèi)部的this綁定到counter對象
var func = counter.inc.bind(couonter)
func()
coounter.count // 1
問題2: 以下代碼輸出什么?
var john = {
firstName: "John"
}
function func() {
alert(this.firstName + ": hi!")
}
john.sayHi = func
john.sayHi()
輸出一個彈框,‘John:hi’
當把func函數(shù)賦值給john.sayHi時,相當于給對象john添加了一個sayHi的
方法,當john調(diào)用這個方法時,這個方法內(nèi)部的this會指向john
問題3: 下面代碼輸出什么,為什么
func()
function func() {
alert(this)
}
輸出window對象,因為func調(diào)用時處在全局環(huán)境中,它內(nèi)部的this指向全局對象window
問題4:下面代碼輸出什么
document.addEventListener('click', function(e){
console.log(this);
setTimeout(function(){
console.log(this);
}, 200);
}, false);
分別輸出document和window
綁定事件的函數(shù)中,this指向事件源
setTimeout中的this指向全局對象window
問題5:下面代碼輸出什么,why
var john = {
firstName: "John"
}
function func() {
alert( this.firstName )
}
func.call(john)
輸出John,因為通過call方法,把函數(shù)func中的this指向?qū)ο骿ohn,所以會輸出John
問題6: 以下代碼有什么問題,如何修改
var module= {
bind: function(){
$btn.on('click', function(){
console.log(this) //this指什么
this.showMsg();
})
},
showMsg: function(){
console.log('饑人谷');
}
}
不應該在事件綁定的函數(shù)中使用this指向Module,因為有事件綁定的回調(diào)
函數(shù),它里面的this指向事件源,所以console.log(this)中的this指向事件源
$btn,this.showMsg()中的this也是指向$btn.
修改方法:
var module= {
bind: function(){
var _this = this//在this可以指向module的函數(shù)中先保存this
$btn.on('click', function(){
console.log(this) //this指什么
_this.showMsg();
})
},
showMsg: function(){
console.log('饑人谷');
}
}
原型鏈相關問題
問題7:有如下代碼,解釋Person、 prototype、proto、p、constructor之間的關聯(lián)。
function Person(name){
this.name = name;
}
Person.prototype.sayName = function(){
console.log('My name is :' + this.name);
}
var p = new Person("若愚")
p.sayName();
問題8: 上例中,對對象 p可以這樣調(diào)用 p.toString()。toString是哪里來的? 畫出原型圖?并解釋什么是原型鏈。
當對對象p調(diào)用toString方法時,首先他會在自己本身中查找有沒有這個方法,如果沒有,則沿著他的p.ptoto這個指針去查找他的構造函數(shù)的原型對象中有沒有,即Person.prototype有沒有,沒有的話繼續(xù)順著Person.prototype.proto這個指針繼續(xù)向上查找,也就是說只要存在proto這個指針,在對應的屬性和方法沒有查到之前,查找不會停下,直到?jīng)]有proto為止,也就是到達null為止。我們把這個由proto指針串起來的直到Object.prototype.proto為 null的鏈叫做原型鏈。
問題9:對String做擴展,實現(xiàn)如下方式獲取字符串中頻率最高的字符
String.prototype.getMostOften = function(){
var res,
count = {},
times = 0
this.split('').forEach(function(item){
if(count.hasOwnProperty(item)){
count[item]++
}else {
count[item] = 1
}
})
for(key in count){
if(count[key]>times){
times = count[key]
res = key
}
}
return res + '因為' + res + '出現(xiàn)了' + times + '次'
}
var str = 'ahbbccdeddddfg';
var ch = str.getMostOften();
console.log(ch); //d , 因為d 出現(xiàn)了5次
問題10: instanceof有什么作用?內(nèi)部邏輯是如何實現(xiàn)的?
The instanceof operator tests whether an object has in its prototype chain the prototype property of a constructor.
instanceof運算符用來判斷一個對象的原型鏈中是否存在某個構造函數(shù)的原型對象
function _instanceof(obj,func){
if(obj.__proto__ === func.prototype){//先判斷該對象的直接原型
對象是否等于構造函數(shù)的prototype屬性,如果是,返回true
return true
}else {//如果不是,把obj的原型鏈上升一層,繼續(xù)判斷它的原型
對象的原型對象是否等于某個構造函數(shù)的prototype屬性,進行一個遞歸的判斷
return _instanceof(obj.__proto__,func)
}
}
繼承相關問題
問題11:繼承有什么作用?
首先什么是繼承:繼承是指一個對象可以直接使用另一個對象的屬性和方法
繼承提高了代碼的可重用性,因為子類擁有了父類的屬性和方法,修改代碼時只需修改父類的屬性和方法,那么子類的也會隨之修改
說到繼承,不得不提多態(tài),多態(tài)是指針對同一個方法,子類之間可以有不同的表現(xiàn),也就是說子類可以重寫或者覆蓋父類的方法,但又不影響父類本身,也可以對子類本身原型對象進行一些屬性或方法的補充和擴展。
問題12: 下面兩種寫法有什么區(qū)別?
//方法1
function People(name, sex){
this.name = name;
this.sex = sex;
this.printName = function(){
console.log(this.name);
}
}
var p1 = new People('饑人谷', 2)
//方法2
function Person(name, sex){
this.name = name;
this.sex = sex;
}
Person.prototype.printName = function(){
console.log(this.name);
}
var p1 = new Person('若愚', 27);
第一種寫法把構造函數(shù)的所有屬性和方法都寫到了它本身,那么每次把該構造函數(shù)實例化為對象的時候,都會把所有的屬性和方法都執(zhí)行一遍,內(nèi)存中存儲了很多相同的方法
第二種寫法把構造函數(shù)的方法寫到了它的prototype屬性里,每次把該構造函數(shù)實例化的時候,方法存在了其原型對象中,相當于創(chuàng)建了一份公共代碼,節(jié)約了內(nèi)存,提高了性能
問題13: Object.create 有什么作用?兼容性如何?
Object.create方法用于從原型對象生成新的實例對象,可以替代new
命令
它接受一個對象作為參數(shù),返回一個新對象,后者完全繼承前者的屬
性,即原有對象成為新對象的原型。
var A = {
print: function(){
console.log('hello')
}
}
var B = Object.create(A)
B.print()//hello
B.print === A.print // true
上面代碼中,object.create方法在A的基礎上生成了B。此時,A就成
了B的原型,B就繼承了A的所有屬性和方法。這段代碼等同于下面的代碼
var A = function(){}
A.prototype.print = function(){
console.log('hello')
}
var B = new A()
B.print === A.prototype.print
實際上,Object.create方法可以用下面的代碼代替。如果老師瀏覽器
不支持Object.create方法,就可以用這段代碼自己部署
問題14: hasOwnProperty有什么作用? 如何使用?
The hasOwnProperty() method returns a boolean indicating whether the object has the specified property as own (not inherited) property.
hasOwnProperty方法返回一個布爾值,判斷一個對象是否包含自定義屬性和方法,而不是原型鏈上的屬性和方法
function C(name,age){
this.name = name
this.age = age
}
C.prototype.sayName = function(){
console.log(this.name)
}
var d = new C('jack',10)
d.hasOwnProperty('name') // true
d.hasOwnProperty('sayName')//false
問題15:如下代碼中call的作用是什么?
function Person(name, sex){
this.name = name;
this.sex = sex;
}
function Male(name, sex, age){
Person.call(this, name, sex); //這里的call函數(shù)把Person中的this指向Male,并傳入?yún)?shù)name和sex
this.age = age;
}
問題16: 補全代碼,實現(xiàn)繼承
function Person(name, sex){
this.name = name
this.sex = sex
}
Person.prototype.getName = function(){
console.log(this.name)
};
function Male(name, sex, age){
Person.call(this,name,sex)//引用Person方法,把Person中的this指向Male,然后賦參數(shù)name和sex
this.age = age
}
Male.prototype = Object.create(Person.prototype)//以Person.prototype屬性為原型,創(chuàng)建一個新的對象,添加到Male.prototype
屬性里,相當于創(chuàng)建一個空對象,這個空對象的__proto__指向Person.prototype
Male.prototype.constructor = Male//將Male的prototype屬性中的constructor改為他自己
Male.prototype.getAge = function(){
console.log(this.age)
};
var ruoyu = new Male('若愚', '男', 27);
ruoyu.getName();
ruoyu.getAge();