以下是一些JS基础知识,是在我们学习react前需要了解的。
如果没能把以下的JS基础知识学懂,就去学习react,就是一种折磨。
1. bind call apply
bind call apply是Function.prototype中的方法,定义一个函数的时候,实例函数就能调用这些方法
我们都知道这些方法是可以改变this的指向
以下例子让我们重温一下 bind call apply 的不同点
let a = {
name: 'aaa',
fn(arg1,arg2){
console.log(this,arg1,arg2)
}
}
let c = {
name: 'ccc'
}
a.fn('xiaohei','xiaobai');//打印对象a和'xiaohei' 'xiaobai'
let fn = a.fn.bind(c,'xiaohei','xiaobai');//bind方法会返回一个改变了this指向的方法
fn(); //打印出对象c和'xiaohei' 'xiaobai'
a.fn.call(c,'xiaohei','xiaobai');
a.fn.apply(c,['xiaohei','xiaobai']);
执行a.fn()可打印出对象a
而执行fn()会打印出实例对象c和后面两个参数
因为bind方法会返回一个改变了this指向的方法
a.fn.bind(c,'xiaohei','xiaobai')() //不会执行,需要手动执行
a.fn.call(c,'xiaohei','xiaobai') //立即执行
a.fn.apply(c,['xiaohei','xiaobai']) //数组传参
以上三个方法可得到一样的结果
bind call apply 的不同点就是:
- bind 会返回一个改变this指向的方法,但不会执行
- call 会立即执行这个方法,相当于bind方法返回新的方法之后立即执行
- apply 和 call 用法相同,不同的是以一个数组的形式传参数
2. new一个对象时做了什么
js new一个对象时做了什么,以下是一个简单的例子
function Person (name,age) {
this.name = name;
this.age = age;
}
Person.prototype.getName = function(){
console.log('my name is ' + this.name);
}
let person = new Person("xiaohei",18);
console.log(person);
person.getName();//my name is xiaohei
使用关键字new创建新实例对象经过了以下几步:
- 创建一个新对象
- 将新对象的_proto_指向构造函数的prototype对象(让实例对象可以访问原型对象上的方法)
- 将构造函数的作用域赋值给新对象 (将this指向新对象)
- 执行构造函数中的代码(将属性添加到实例对象上面)
- 返回新的对象
function Person (name,age) {
this.name = name;
this.age = age;
}
Person.prototype.getName = function(){
console.log('my name is ' + this.name);
}
function createPerson(name,age){
let obj = {};// 创建一个新对象
obj.__proto__ = Person.prototype;//将新对象的_proto_指向构造函数的prototype对象
Person.call(obj,name,age,sex);//改变this指向,并执行函数内代码
return obj;//返回对象
}
let person = new Person("xiaohei", 18);
let person2 = createPerson("xiaobai", 19);
console.log(person,person2);
person.getName();//my name is xiaohei
person2.getName();//my name is xiaobai
3. 类
在JS中,class是一种语法,其本质就是构造函数和其prototype对象
先定义一个最简单的类,让我们了解类是怎么定义的
class A {
constructor(name){
this.name = name
}
//类中constructor方法就是构造函数方法
//在类中定义方法是不加逗号的
getName(){
console.log(this.name)
}
//类中的方法是绑定在类(构造函数)的原型对象上面的
}
如果把上面的这个A类,转换成构造函数和原型对象的形式是怎么样的?
function A(name){
this.name = name;
}
//类中constructor方法就是构造函数方法
A.prototype.getName = function(){
console.log(this.name)
}
//类中的方法是绑定在类(构造函数)的原型对象上面的
以上的构造函数和原型对象对应上面的类
需要注意的是:
- 在类中定义方法是不加逗号的
- 类中的方法是绑定在类(构造函数)的原型对象prototype上面的
- 类中constructor方法就是构造函数方法
- 类的内部指定了严格模式,在严格模式下不允许this指向window对象
4. 实例方法和原型对象上的方法
class A {
constructor(name){
this.name = name;
this.getName = function(){
console.log(11,this.name)
}
}
getName(){
console.log(22,this.name)
}
}
let a = new A('xiaohei');
a.getName(); // 11 xiaohei
以上代码,是调用了实例对象a上面的getName方法,打印出 11 xiaohei
let a = {
name: 'xiaohei',
getName: function(){
console.log(11,this.name)
},
__proto__: {
getName(){
console.log(22,this.name)
}
}
}
a.getName()
实例对象a先会调用getName方法
如果没有getName方法,才会调 __proto__对象的getName方法,也就是 构造函数A 的 原型对象A.prototype 上面找getName方法
5. 箭头函数
箭头函数是一种用来定义函数语法
与普通函数不同的是,箭头函数没有prototype属性,不能实例化对象
class A {
constructor(name){
this.name = name;
}
getName = () => {
console.log(this,this.name)
}
}
以上的代码,可使用以下伪代码实现
function A(name){
let _self = this;
this.getName = function(){
console.log(_self,_self.name)
}
this.name = name;
}
箭头函数不能实例化对象,只能被调用,而调用箭头函数时,this会指向由该类实例化的对象
以下是实例化对象后调用的结果:
class A {
constructor(name){
this.name = name;
}
getName = () => {
console.log(this,this.name)
}
}
let a = new A('xiaohei');
a.getName(); //直接由a对象调用,this指向a;
let fn = a.getName;
fn(); //在window调用,this指向仍是a
伪代码如下:
function A(name){
let _self = this;
this.getName = function(){
console.log(_self,_self.name)
}
this.name = name;
}
let a = new A('xiaohei');
a.getName(); //直接由a对象调用,this指向a;
let fn = a.getName;
fn(); //在window调用,this指向仍是a,而不是window
直接由a对象调用,this指向a对象
在window调用,this指向仍是a,而不是window(严格模式下为undefined)
6. this的指向
在调用方法时,如果不由实例去调用的话,this不指向实例对象
在react中,会用类似以下的方式去调用:
class A {
constructor(name){
this.name = name;
}
getName() {
console.log(this,this.name)
}
}
let a = new A('xiaohei');
let fn = a.getName;
fn();
在这样的方式调用getName方法,getName方法中的this不指向实例对象
要在window中调用方法时,并要将方法中的this指向该实例对象,可有以下的方法:
- 在调用方法前使用bind改变this指向,返回新的函数
class A {
constructor(name){
this.name = name;
}
getName() {
console.log(this,this.name)
}
}
let a = new A('xiaohei');
let fn = a.getName.bind(a);
fn();
- 在构造函数中,定义实例化对象时,为实例对象添加一个返回了 原形对象的getName方法改变this的指向的方法
class A {
constructor(name){
this.name = name;
this.getName = this.getName.bind(this);
}
getName() {
console.log(this,this.name)
}
}
let a = new A('xiaohei');
let fn = a.getName;
fn();
实例化的对象a 会有添加一个指向自身的getName方法
在该实例对象new的时候会拿到原型上的getName方法,将其this指向改变,返回出来的函数绑定在该实例对象的getName属性上
- 使用箭头函数
class A {
constructor(name){
this.name = name;
}
getName = () => {
console.log(this,this.name)
}
}
let a = new A('xiaohei');
let fn = a.getName;
fn();
由上面的箭头函数解析中可以知道,使用箭头函数来添加方法的话,在构造函数被调用(也就是new一个实例对象)的时候,就会将方法绑定在实例上面,而不是添加在原型上,同时该方法的this指向其实例对象,不会指向调用该方法的对象
伪代码如下:
function A(name){
let _self = this;
this.getName = function(){
console.log(_self,_self.name)
}
this.name = name;
}
let a = new A('xiaohei');
let fn = a.getName;
fn();
但使用该方式的缺点是会增加内存开销,因为每一个实例都会有一个getName方法,而不是在构造函数的原型上面