一. this指向问题
1. 普通调用,this指向为调用者 (谁调用,则this便指向谁)
(1)对象方法的调用,this指向该对象
var obj = {
a: 'this is obj',
test: function () {
console.log(this.a);
}
}
obj.test();
// this is obj(2)“单纯”函数调用,this指向window
var a = 'this is window'
function test () {
console.log(this.a);
}
test();
// this is window相当于
window.a = 'this is window'
window.test = function test () {
console.log(this.a);
// 此时是window为调用者,即this会指向window
}
window.test();(3)构造函数调用,this指向该构造函数的实例对象
function test () {
this.a = 'this is test';
console.log(this.a);
console.log(this);
}
test();
// this is test
// Window {}换成构造函数调用
function Test () {
this.a = 'this is test';
console.log(this.a);
console.log(this);
}
var test = new Test();
// this is test
// Test {a: 'this is test'}2. call/apply/bind调用,this指向为当前thisArg参数
(1)call调用
fun.call(thisArg, arg1, arg2, ...)
function Test () {
this.a = 'this is test';
console.log(this.a);
console.log(this);
}
function Test2 () {
Test.call(this)
}
var test = new Test2();
// this is test
// Test2 {a: 'this is test'}验证一个对象的类型
var obj = {};
Object.prototype.toString.call(obj)
//"[object Object]"类(伪)数组使用数组方法
var divElements = document.getElementsByTagName('div'); //虽然 divElements 有length属性,但是他是一个伪数组,不能使用数组里面的方法
Array.isArray(divElements);// false
var domNodes = Array.prototype.slice.call(document.getElementsByTagName('div'));
// 将数组对象Array里的this指向伪数组document.getElementsByTagName('div'),
//slice() 方法可从已有的数组中返回选定的元素,不传参数是,返回整个数组
Array.isArray(domNodes);// true(2)apply调用
fun.apply(thisArg, [argsArray])
var array1 = [12,'foo',{name:'Joe'},-2458];
var array2 = ['Doe' , 555 , 100];
Array.prototype.push.call(array1, array2);
// 这里用 call 第二个参数不会把 array2 当成一个数组,而是一个元素
//等价于array1.push(['Doe' , 555 , 100]);
//array1.length=5;
Array.prototype.push.apply(array1, array2); // 这里用 apply 第二个参数是一个数组
// 等价于: array1.push('Doe' , 555 , 100);
//array1.length=7;(3)bind调用
var bar = function(){
console.log(this.x);
}
var foo = {
x:3
}
bar(); // undefined
var func = bar.bind(foo); //此时this已经指向了foo,但是用bind()方法并不会立即执行,而是创建一个新函数,如果要直接调用的话 可以 bar.bind(foo)()func(); // 3bind里面的this指向,会永远指向bind到的当前的thisArg
a.bind(b).call(c),最终的this指向会是b
多次 bind() 是无效的
bind() 的实现,相当于使用函数在内部包了一个 call / apply ,第二次 bind() 相当于再包住第一次 bind() ,故第二次以后的 bind 是无法生效的
var bar = function(){
console.log(this.x);
}
var foo = {
x:3
}
var sed = {
x:4
}
var func = bar.bind(foo).bind(sed);
func(); //3
var fiv = {
x:5
}
var func = bar.bind(foo).bind(sed).bind(fiv);
func(); //33. 箭头函数,this指向为当前函数的this指向
function test () {
(() => {
console.log(this);
})()
}
test.call({a: 'this is thisArg'})
// Object {a: 'this is thisArg'}即使是定时器函数
function test () {
setTimeout(() => {
console.log(this);
}, 0)
}
test.call({a: 'this is obj'})
// Object {a: 'this is obj'}普通函数的定时器this指向window
function test () {
setTimeout(function () {
console.log(this);
}, 0)
}
test.call({a: 'this is obj'})
// Window {...}二. 原型&原型链
function Test () {
this.a = 'this is Test';
}
Test.prototype = {
b: function () {
console.log("this is Test's prototype");
}
}
function Test2 () {
this.a = 'this is Test2'
}
Test2.prototype = new Test();
var test = new Test2();
test.b();
//this is Test's prototype
console.log(test.prototype);
//undefined
console.log(test);
//Test2 {a: "this is Test2"}1. 普通对象与函数对象
o1 o2 o3 为普通对象,f1 f2 f3 为函数对象
凡是通过 new Function() 创建的对象都是函数对象,其他的都是普通对象
var o1 = {};
var o2 =new Object();
var o3 = new f1();
function f1(){};
var f2 = function(){};
var f3 = new Function('str','console.log(str)');
console.log(typeof Object); //function
console.log(typeof Function); //function
console.log(typeof f1); //function
console.log(typeof f2); //function
console.log(typeof f3); //function
console.log(typeof o1); //object
console.log(typeof o2); //object
console.log(typeof o3); //object
2. 构造函数
实例的构造函数属性(constructor)指向构造函数
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() { alert(this.name) }
}
var person1 = new Person('Zaxlct', 28, 'Software Engineer');
var person2 = new Person('Mick', 23, 'Doctor');
console.log(person1.constructor == Person); //true
console.log(person2.constructor == Person); //true
3. 原型对象
每个对象都有 __proto__ 属性,但只有函数对象才有 prototype 属性
function Person() {}
Person.prototype = {
name: 'Zaxlct',
age: 28,
job: 'Software Engineer',
sayName: function() {
alert(this.name);
}
}var person1 = new Person();
person1.sayName(); // 'Zaxlct'
var person2 = new Person();
person2.sayName(); // 'Zaxlct'
console.log(person1.sayName == person2.sayName); //true在默认情况下,所有的原型对象都会自动获得一个 constructor(构造函数)属性,这个属性(是一个指针)指向 prototype 属性所在的函数(Person)
Person.prototype.constructor == Person
person1.constructor == Person
结论:原型对象(Person.prototype)是 构造函数(Person)的一个实例
原型对象其实就是普通对象(但 Function.prototype 除外,它是函数对象,但它很特殊,他没有prototype属性(前面说道函数对象都有prototype属性))
var A = new Function ();
Function.prototype = A;function Person(){};
console.log(Person.prototype) //Person{}
console.log(typeof Person.prototype) //Object
console.log(typeof Function.prototype) // Function,这个特殊
console.log(typeof Object.prototype) // Object
console.log(typeof Function.prototype.prototype) //undefined原型对象主要作用是用于继承
var Person = function(name){
this.name = name; // tip: 当函数执行时这个 this 指的是谁?
};
Person.prototype.getName = function(){
return this.name; // tip: 当函数执行时这个 this 指的是谁?
}
var person1 = new person('Mick');
person1.getName(); //Mick
4. __proto__
JS 在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做__proto__ 的内置属性,用于指向创建它的构造函数的原型对象
person1.__proto__ == Person.prototype
Person.prototype.constructor == Person;
person1.__proto__ == Person.prototype;
person1.constructor == Person;5. 构造器
可以创建对象的构造器不仅仅有 Object,也可以是 Array,Date,Function
var obj = new Object()
obj.constructor === Object
obj.__proto__ === Object.prototype
var b = new Array();
b.constructor === Array;
b.__proto__ === Array.prototype;
var c = new Date();
c.constructor === Date;
c.__proto__ === Date.prototype;
var d = new Function();
d.constructor === Function;
d.__proto__ === Function.prototype;
6. 原型链
person1.__proto__ === Person.prototype
Person.__proto__ === Function.prototype
Person.prototype.__proto__ === Object.prototype
Object.__proto__ === Function.prototypeObject.prototype.__proto__ === null7. 函数对象
所有函数对象的__proto__都指向Function.prototype,它是一个空函数
Number.__proto__ === Function.prototype // true
Number.constructor == Function //true
Boolean.__proto__ === Function.prototype // true
Boolean.constructor == Function //true
String.__proto__ === Function.prototype // true
String.constructor == Function //true
// 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身
Object.__proto__ === Function.prototype // true
Object.constructor == Function // true
// 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身
Function.__proto__ === Function.prototype // true
Function.constructor == Function //true
Array.__proto__ === Function.prototype // true
Array.constructor == Function //true
RegExp.__proto__ === Function.prototype // true
RegExp.constructor == Function //true
Error.__proto__ === Function.prototype // true
Error.constructor == Function //true
Date.__proto__ === Function.prototype // true
Date.constructor == Function //true
JavaScript中有内置(build-in)构造器/对象共计12个(ES5中新加了JSON),这里列举了可访问的8个构造器
Global不能直接访问,Arguments仅在函数调用时由JS引擎创建
Math,JSON是以对象形式存在的,无需new,它们的proto是Object.prototype
Math.__proto__ === Object.prototype // true
Math.construrctor == Object // true
JSON.__proto__ === Object.prototype // true
JSON.construrctor == Object //true
所有的构造器也都是一个普通 JS 对象,可以给构造器添加/删除属性等。同时它也继承了Object.prototype上的所有方法:toString、valueOf、hasOwnProperty等
Function.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null
8. prototype
var Person = new Object()
Person 是 Object 的实例,所以 Person 继承了Object 的原型对象Object.prototype上所有的方法
var num = new Array()
num 是 Array 的实例,所以 num 继承了Array 的原型对象Array.prototype上所有的方法
Array.prototype //[]
var arrayAllKeys = Array.prototype; // [] 空数组
// 只得到 arrayAllKeys 这个对象里所有的属性名(不会去找 arrayAllKeys.prototype 中的属性)
console.log(Object.getOwnPropertyNames(arrayAllKeys));
/* 输出:
["length", "constructor", "toString", "toLocaleString", "join", "pop", "push",
"concat", "reverse", "shift", "unshift", "slice", "splice", "sort", "filter", "forEach",
"some", "every", "map", "indexOf", "lastIndexOf", "reduce", "reduceRight",
"entries", "keys", "copyWithin", "find", "findIndex", "fill"]
*///Array.prototype 虽然没这些方法,但是它有原型对象(__proto__)
var num = [1];
console.log(num.hasOwnPrototype()) // false (输出布尔值而不是报错)// 上面我们说了 Object.prototype 就是一个普通对象。
Array.prototype.__proto__ == Object.prototype当我们创建一个函数时
var f = new Function("x","return x*x;");
//当然你也可以这么创建 f = function(x){ return x*x }
console.log(f.arguments) // arguments 方法从哪里来的?
console.log(f.call(window)) // call 方法从哪里来的?
console.log(Function.prototype) // function() {} (一个空的函数)
console.log(Object.getOwnPropertyNames(Function.prototype));
/* 输出
["length", "name", "arguments", "caller", "constructor", "bind", "toString", "call", "apply"]
*/所有函数对象的 __proto__ 都指向 Function.prototype,它是一个空函数(Empty function)
所有对象的 __proto__ 都指向其构造器的 prototype JS 内置构造器
var obj = {name: 'jack'}
var arr = [1,2,3]
var reg = /hello/g
var date = new Date
var err = new Error('exception')
console.log(obj.__proto__ === Object.prototype) // true
console.log(arr.__proto__ === Array.prototype) // true
console.log(reg.__proto__ === RegExp.prototype) // true
console.log(date.__proto__ === Date.prototype) // true
console.log(err.__proto__ === Error.prototype) // true
自定义的构造器
function Person(name) {
this.name = name;
}
var p = new Person('jack')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // truefunction Person(name) {
this.name = name
}
// 修改原型
Person.prototype.getName = function() {}
var p = new Person('jack')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // truefunction Person(name) {
this.name = name
}
// 重写原型
Person.prototype = {
getName: function() {}
}
var p = new Person('jack')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // false
Person.prototype赋值的是一个对象直接量{getName: function(){}},使用对象直接量方式定义的对象其构造器(constructor)指向的是根构造器Object,Object.prototype是一个空对象{},{}自然与{getName: function(){}}不等var p = {}
console.log(Object.prototype) // 为一个空的对象{}
console.log(p.constructor === Object) // 对象直接量方式定义的对象其constructor为Object
console.log(p.constructor.prototype === Object.prototype) // 为true
9. 原型链
function Person(){}
var person1 = new Person();
console.log(person1.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype) //true
console.log(Object.prototype.__proto__) //null
Person.__proto__ == Function.prototype; //true
console.log(Function.prototype)// function(){} (空函数)
var num = new Array()
console.log(num.__proto__ == Array.prototype) // true
console.log( Array.prototype.__proto__ == Object.prototype) // true
console.log(Array.prototype) // [] (空数组)
console.log(Object.prototype.__proto__) //null
console.log(Array.__proto__ == Function.prototype)// true
原型和原型链是JS实现继承的一种模型
原型链的形成是真正是靠__proto__ 而非prototype
var animal = function(){};
var dog = function(){};
animal.price = 2000;
dog.prototype = animal;
var tidy = new dog();
console.log(dog.price) //undefined
console.log(tidy.price) // 2000 var dog = function(){};
dog.prototype.price = 2000;
var tidy = new dog();
console.log(tidy.price); // 2000
console.log(dog.price); //undefined var dog = function(){};
var tidy = new dog();
tidy.price = 2000;
console.log(dog.price); //undefined实例(tidy)和 原型对象(dog.prototype)存在一个连接。不过,要明确的真正重要的一点就是,这个连接存在于实例(tidy)与构造函数的原型对象(dog.prototype)之间,而不是存在于实例(tidy)与构造函数(dog)之间
三. 继承
1. 构造函数继承
function Test () {
this.a = 'this is test';
console.log(this.a);
console.log(this);
}
function Test2 () {
Test.apply(this)
// or Test.apply(this)
}
var test = new Test2();
// this is test
// Test2 {a: 'this is test'}2. 原型链继承
原型链继承,会通过子类prototype属性等于(指向)父类的实例
Child.prototype = new Parent();var obj = {}; // 初始化一个对象obj。
obj.__proto__ = Parent.prototype; // 将obj的__proto__原型指针指向父类Parent的prototype属性
Parent.call(obj); // 初始化Parent构造函数function Parent () {
this.a = 'this is Parent'
}
Parent.prototype = {
b: function () {
console.log(this.a);
}
}
function Child () {
this.a = 'this is Child'
}
Child.prototype = {
b: function () {
console.log('monkey patch');
Parent.prototype.b.call(this);
}
}
var test = new Child()
test.b()
// monkey patch
// this is Child四. 闭包
为了可以访问函数内的局部变量而定义的内部函数
每一个function内都有一个属于自己的执行上下文,即特定的context指向
var a = 'this is window'
function test () {
var b = 'this is test'
function test2 () {
var c = 'this is test2';
console.log(a);
console.log(b);
console.log(c);
}
test2();
}
test();
// this is window
// this is test
// this is test2变量访问的指向是当前context上下文的指向的相反方向,且不可逆
function test () {
var b = 'this is test';
}
console.log(b); // Uncaught ReferenceError: b is not definedfor循环配合setTimeout的异步任务
function test () {
for (var i = 0; i < 4; i++) {
setTimeout(function () {
console.log(i);
}, 0)
}
}
test();
//答案会打印4次4想依次打印0,1,2,3又该怎么做
function test () {
for (var i = 0; i < 4; i++) {
(function (e) {
setTimeout(function () {
console.log(e);
}, 0)
})(i)
}
}
test();
// 0 -> 1 -> 2 -> 3还有一种常见的方式可以实现上面的效果,即从自执行匿名函数中返回一个函数
function test () {
for(var i = 0; i < 4; i++) {
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 0)
}
}
test();