面试题——JS实例化过程总结(附原型链图解)
没有返回值的构造函数形式
function Person(name,age){ this.name = name; this.age = age; this.show = function(){ console.log('每个人的身上都有毛毛'); } } var person = new Person('LiMing',22); console.log(person); person.show();//如果不调用,不会输出 console.log(person instanceof Person); console.log(person.__proto__ == Person.prototype);
运行结果如下:
如果实例没有调用方法,图中圈出的部分不能被输出;
根据图中显示结果,我们可以将实例化过程总结为:
1.创建了一个object对象{};
2.将构造函数的静态属性传递给创造的对象;
3.实例对象额外添加了__proto__属性,指向了构造函数的prototype属性,也就是原型对象;
4.构造函数的原型对象有一个constructor方法,返回值为构造函数;
5.构造函数内的this指向为实例对象;
另外:如果实例没有调用构造函数的方法,不会输出图中画框的部分;
如果有返回值的情况1:返回值为基本数据类型
function Person1(name,age){ this.name = name; this.age = age; this.show = function(){ console.log('每个人的身上都有毛毛'); }; return 22 } var person1 = new Person1('LiMing',22); console.log(person1); person1.show(); console.log(person1 instanceof Person1); console.log(person1.__proto__ == Person1.prototype);
从结果看,基本的数据类型不影响实例过程;
如果有返回值的情况2:返回值为引用数据类型
function Person2(name,age){ this.name = name; this.age = age; this.show = function(){ console.log('每个人的身上都有毛毛'); }; return { name:"lihua", age:25, show:function () { console.log('你是我天边最美的云彩!') } } } var person2 = new Person2('LiMing',22); console.log(person2); person2.show(); console.log(person2 instanceof Person2); console.log(person2.__proto__ == Person2.prototype);
function Person3(name,age){ this.name = name; this.age = age; this.show = function(){ console.log('每个人的身上都有毛毛'); }; return { weight:70, height:185, show:function () { console.log('你是我天边最美的云彩!') } } } var person3 = new Person3('LiMing',22); console.log(person3); person3.show(); console.log(person3 instanceof Person3); console.log(person3.__proto__ == Person3.prototype);
function Person4(name,age){ this.name = name; this.age = age; this.show = function(){ console.log('每个人的身上都有毛毛'); }; return [12,23,'345',{name:'LiHua'}] } var person4 = new Person4('LiMing',22); console.log(person4); person4.show(); console.log(person4 instanceof Person4); console.log(person4.__proto__ == Person4.prototype);
从结果看,如果构造函数返回一个object的话,实例为返回的对象;
最后,附上一张本人觉得比较好的原型链图解
其中中间颜色加深部分,分为三种构造器,
1>自定义构造器;
2>对象构造器;
3>内置对象构造器(例如Array,String,Date,Match);
call、apply和bind区别
首先,call apply bind三个方法都可以用来改变函数的this指向,具体区别如下:
call( ) 是接收一个及其以上的参数,第一个参数表示this要指向的对象,其余参数表示调用函数需要传入的参数,返回调用函数的返回结果,属于立即执行函数;
apply( ) 是接收两个参数,第一个参数表示this要指向的对象,第二参数表示调用函数需要传入的参数所组成的数组,返回调用函数的返回结果,属于立即执行函数;
bind( ) 是接收一个及其以上的参数,和call()一致,但是其返回是一个函数,而不是调用函数的返回结果
call、apply、bind相同点:都是改变this的指向,传入的第一个参数都是绑定this的指向,在非严格模式中,如果第一个参数是nul或者undefined,会把全局对象(浏览器是window)作为this的值,要注意的是,在严格模式中,null 就是 null,undefined 就是 undefined
call和apply唯一的区别是:call传入的是参数列表,apply传入的是数组,也可以是类数组
bind和call、apply的区别: bind返回的是一个改变了this指向的函数,便于稍后调用,不像call和apply会立即调用;bind和call很像,传入的也是参数列表,但是可以多次传入,不需要像call,一次传入
值得注意:当 bind 返回的函数 使用new作为构造函数时,绑定的 this 值会失效,this指向实例对象,但传入的参数依然生效 (new调用的优先级 > bind调用)
call的使用方法
const person = { name: 'Alice', sayHello: function () { console.log(`Hello, ${this.name}!`) }, } const person2 = { name: 'Bob',} person.sayHello.call(person2) // 输出:Hello, Bob!
apply的使用方法
const person = { name: 'Alice', sayHello: function (greeting) { console.log(`${greeting}, ${this.name}!`) }, } const person2 = { name: 'Bob',} person.sayHello.apply(person2, ['Hi']) // 输出:Hi, Bob!
bind的使用方法
const person = { name: 'Alice', sayHello: function () { console.log(`Hello, ${this.name}!`) }, } const person2 = { name: 'Bob',} const sayHelloToBob = person.sayHello.bind(person2) sayHelloToBob() // 输出:Hello, Bob!
const person = { name: 'Alice', sayHello: function (greeting, punctuation) { console.log(`${greeting}, ${this.name}, ${punctuation}`) }, } const person2 = { name: 'Bob',} const sayHelloToBob = person.sayHello.bind(person2); sayHelloToBob(1,2); // 输出:1, Bob, 2
this.x = 9; // 在浏览器中,this 指向全局的 "window" 对象var module = { x: 81, getX: function() { return this.x; } }; module.getX(); // 81 var retrieveX = module.getX; retrieveX();// 返回 9 - 因为函数是在全局作用域中调用的/ / 创建一个新函数,把 'this' 绑定到 module 对象 // 新手可能会将全局变量 x 与 module 的属性 x 混淆 var boundGetX = retrieveX.bind(module); boundGetX(); // 81