this相關(guān)問題
apply、call 、bind的作用以及區(qū)別
以下代碼輸出什么?
var john = {
firstName: "John"
}
function func() {
alert(this.firstName + ": hi!")
}
john.sayHi = func
john.sayHi()
輸出:John: hi!
解析:john.sayHi()可理解為john.sayHi.call(john),即this指向john,輸出即為John.firstName + ":hi!"
下面代碼輸出什么,為什么
func()
function func() {
alert(this)
}
輸出:window對象
解析:func()可理解為func.call(null),瀏覽器里有一條規(guī)則:
如果你傳的 context 就 null 或者 undefined,那么 window 對象就是默認的 context(嚴格模式下默認 context 是 undefined)
因此上面的打印結(jié)果是 window。如果你希望這里的 this 不是 window,很簡單:
func.call(obj) // 那么里面的 this 就是 obj 對象了
下面代碼輸出什么
document.addEventListener('click', function(e){
console.log(this);
setTimeout(function(){
console.log(this);
}, 200);
}, false);
點擊頁面,依次輸出:document和window對象
解析:點擊頁面監(jiān)聽click事件屬于方法調(diào)用,this指向事件源DOM對象,即obj.fn.apply(obj),setTimeout內(nèi)的函數(shù)屬于回調(diào)函數(shù),可以這么理解,f1.call(null,f2),所以this指向window
下面代碼輸出什么,why
var john = {
firstName: "John"
}
function func() {
alert( this.firstName )
}
func.call(john)
解析:call中已傳入第一個參數(shù)john,即this指向John
輸出:John
以下代碼有什么問題,如何修改
var module= {
bind: function(){
$btn.on('click', function(){
console.log(this) //this指什么
this.showMsg();
})
},
showMsg: function(){
console.log('hello');
}
}
問題:this.showMsg()的this指向$btn,而$btn上沒有showMsg這個方法
解決辦法有多種:
- 將
this.showMsg()改為module.showMsg()或module.showMsg.call(module) - 在事件監(jiān)聽的回調(diào)函數(shù)后綁定this,即
this.showMsg();}.bind(this)),bind中的this指向的是module,所以直接寫bind(module)也可。 - 在異步操作之后this可能會發(fā)生改變,所以在這段代碼中,在事件執(zhí)行前將this的值保存為_this,最后通過
_this.showMsg()調(diào)用方法,也可以得到正確結(jié)果 - 用ES6語法中的箭頭函數(shù),就不用寫方法2中糟心的代碼了,既然要改就全改了吧:
var module = {
bind() {
$btn.on('click', () => {
console.log(this)
this.showMsg();
})
},
showMsg() {
console.log('hello');
}
}
原型鏈相關(guān)問題
有如下代碼,解釋Person、 prototype、proto、p、constructor之間的關(guān)聯(lián)。
function Person(name){
this.name = name;
}
Person.prototype.sayName = function(){
console.log('My name is :' + this.name);
}
var p = new Person("Dot")
p.sayName();
關(guān)系:
p.__proto__===Person.prototypePerson.prototype.constructor===Personp.constructor===Person
上例中,對對象 p可以這樣調(diào)用 p.toString()。toString是哪里來的? 畫出原型圖?并解釋什么是原型鏈。
前面講面向?qū)ο蟮牟┛屠?,我畫了這么張圖,放在這里也勉強合適:

p是Person構(gòu)造函數(shù)的實例,p首先會查找自身有沒有toString()這個方法,顯然是沒有的,所以會順著proto原型鏈逐級向上查找,直到Object.prototype為止,如果還沒有找到就返回null,期間找到了就調(diào)用該方法。
記?。?/p>
- 當 new 一個構(gòu)造函數(shù)的時候會創(chuàng)建一個實例,
構(gòu)造函數(shù).prototype === 實例.__proto__ - 一切函數(shù)都是由 Function 這個函數(shù)創(chuàng)建的,所以
Function.prototype === 被創(chuàng)建的函數(shù).__proto__ - 一切函數(shù)的原型對象都是由 Object 這個函數(shù)創(chuàng)建的,所以
Object.prototype === 一切函數(shù).prototype.__proto__
表述關(guān)系為:
-
p.__proto__===Person.prototype,找到構(gòu)造函數(shù)的原型,沒有toString()方法于是繼續(xù)查找 -
p.__proto__.__proto__===Object.prototype,找到Object的原型對象 -
p.__proto__.__proto__.toString()===Object.prototype.toString()
最后在Object.prototype中找到了toString()方法,所以此時就可以調(diào)用toString()方法了,這也叫做繼承方法。
原型鏈:
每個構(gòu)造函數(shù)都有一個原型對象,原型對象都包含指向其構(gòu)造函數(shù)的指針,而實例都包含一個指向原型對象的proto指針,我們讓原型對象等于另一個類型的實例,此時的原型對象將包含一個指向另一個原型的指針,以此為依據(jù),層層推進,就構(gòu)成了實例與原型的鏈條,稱為原型鏈。
在訪問對象的屬性時,如果在對象本身中沒有找到,則會去原型鏈中逐級向上查找,找到則返回該屬性,如果遍歷整個鏈都沒有找到則返回undefined。
原型鏈一般實現(xiàn)為一個鏈表,這樣就可以按照一定的順序來查找,原型鏈是實現(xiàn)繼承的主要方法。
對String做擴展,實現(xiàn)如下方式獲取字符串中頻率最高的字符
var str = 'ahbbccdeddddfg';
var ch = str.getMostOften();
console.log(ch); //d , 因為d 出現(xiàn)了5次
增加如下代碼:
String.prototype.getMostOften = function () {
var res = this.split('')
.reduce((acc, cur) => {
if (acc[cur]) {
acc[cur]++
return acc
} else {
acc[cur] = 1
return acc
}
}, {})
var max = ['', 0]
for (var key in res) {
if (res[key] > max[1]) {
max = [key, res[key]]
}
}
console.log(max)
return max[0]
}
instanceOf有什么作用?內(nèi)部邏輯是如何實現(xiàn)的?
- 作用:判斷一個對象是不是某個類型的實例
- 實現(xiàn):A instanceof B的判斷規(guī)則是:沿著A的proto這條線來找,同時沿著B的prototype這條線來找,如果兩條線逐級向上查找能找到同一個引用,返回true,證明A是B類型的實例,否則返回false。
代碼如下:
function instance(obj, type) {
while (obj.__proto__) {
// 以下判斷條件換為 obj.__proto__.constructor === type 也可
if (obj.__proto__ === type.prototype) {
return true
} else {
// 以下return語句換乘 obj = obj.__proto__ 也可
return instance(obj.__proto__, type)
}
}
return false
}
console.log(instance([], Array))
console.log(instance(/.\d/, RegExp))
console.log(instance({}, Object))
繼承相關(guān)問題
繼承有什么作用?
繼承機制使得不同的實例可以共享構(gòu)造函數(shù)的原型對象上的屬性和方法,提高了代碼的復用性。
下面兩種寫法有什么區(qū)別?
//方法1
function People(name, sex){
this.name = name;
this.sex = sex;
this.printName = function(){
console.log(this.name);
}
}
var p1 = new People('Dot', 2)
//方法2
function Person(name, sex){
this.name = name;
this.sex = sex;
}
Person.prototype.printName = function(){
console.log(this.name);
}
var p1 = new Person('Dot', 2);
首先要知道構(gòu)造函數(shù)里定義的都是實例的屬性和方法。
方法1和方法2的區(qū)別在于printName方法所在的位置,方法1中的printName方法是實例的方法,也就是說每生成一個實例之后,實例的printName就會占用內(nèi)存;方法2中的printName方法定義在構(gòu)造函數(shù)的原型對象上(前面說過的實例.__proto__ === 構(gòu)造函數(shù).prototype),生成的所有實例都會共享原型對象上的所有方法,節(jié)省內(nèi)存,這也印證了一個結(jié)論:公共方法寫在原型對象上比較好。
Object.create 有什么作用?兼容性如何?
- 作用:
Object.create()接收兩個參數(shù),作用是創(chuàng)建接收到的第一個參數(shù)的副本,第二個參數(shù)是可選的、額外傳入副本里的屬性,以第二個參數(shù)指定的任何屬性都會傳入副本中并覆蓋已有的同名屬性,但原型對象上的同名屬性不會被改變。
也就是說使用此方法時是先clone再在子類上添加自己的屬性和方法,以此實現(xiàn)原型式繼承。
有代碼如下:
var person = {
name: 'dot',
friends: ['a', 'b', 'c']
}
var anotherPerson = Object.create(person, {
name: {
value: 'dolby'
}
})
console.log(anotherPerson.name)//dolby
console.log(person.name)//dot
- 兼容性:各大瀏覽器的最新版本(包括IE9)都部署了這個方法,可以寫一個polyfill解決低版本瀏覽器問題:
if(!Object.create){
Object.create = function(obj){
function F(){}
F.prototype = obj
return new F()
}
}
hasOwnProperty有什么作用? 如何使用?
- 作用:檢測一個屬性到底存在于原型中還是實例中,這個方法從Object繼承得來,只有在屬性存在于實例中才返回true
- 使用:
實例.hasOwnProperty('屬性名'),返回true則屬性存在于實例中,false則屬性存在于原型中。
function Person() { }
Person.prototype = {
name: 'dot',
sex: 'female',
age: 2,
sayName() {
console.log(this.name)
}
}
var person1 = new Person()
var person2 = new Person()
console.log(person1.hasOwnProperty('name'))//false
person1.name = 'dolby'
console.log(person1.name)//dolby,來自實例
console.log(person1.hasOwnProperty('name'))//true
console.log(person2.name)//dot,來自原型
console.log(person2.hasOwnProperty('name'))//false
delete person1.name//刪除實例屬性,恢復實例與原型的連接
console.log(person1.name)//dot,來自原型
console.log(person1.hasOwnProperty('name'))//false
如下代碼中call的作用是什么?
function Person(name, sex){
this.name = name;
this.sex = sex;
}
function Male(name, sex, age){
Person.call(this, name, sex); //這里的 call 有什么作用
this.age = age;
}
作用:借用構(gòu)造函數(shù)實現(xiàn)對實例屬性的繼承,這樣既實現(xiàn)了函數(shù)復用,又保證每個實例具有自己的屬性。本例中將this指向Person,實現(xiàn)在Male中繼承Person的屬性
補全代碼,實現(xiàn)繼承
function Person(name, sex){
// todo ...
}
Person.prototype.getName = function(){
// todo ...
};
function Male(name, sex, age){
//todo ...
}
//todo ...
Male.prototype.getAge = function(){
//todo ...
};
var ruoyu = new Male('若愚', '男', 27);
ruoyu.printName();
function inherit(superType, subType) {
var _prototype = superType.prototype
_prototype.constructor = subType// 修改constructor指向
subType.prototype = _prototype
}
function Person(name, sex) {
this.name = name
this.sex = sex
}
Person.prototype.getName = function () {
console.log(this.name)
}
function Female(name, sex, age) {
Person.call(this,name, sex)
this.age = age
}
inherit(Person, Female)// Female繼承Person
// 在繼承函數(shù)之后寫自己的方法,否則會被覆蓋
Female.prototype.getAge = function () {
console.log(this.age)
}
var dot = new Female('Dot', '女', 2)
dot.getName()