js相关知识
js函数定义的三种方式
在Javascript定义一个函数一般有如下三种方式:
- 函数声明
function fnMethodName(x){
alert(x)
}
- 函数表达式,又叫函数字面量
var fnMethodName = function(x){
alert(x)
}
- Function()构造函数
var sum3=new Function('n1','n2','return n1+n2')
console.log(sum3(2,3)) // 5
// 由Function构造函数的参数个数可变。最后一个参数写函数体,前面的参数写入参。 参数必须加引号
// var b = 10
function fn1(){
var b = 10
var a = new Function('x', 'y', 'console.info(b) return x+y') // 会报错,找不到b 除非全局变量里面有b
a(1,2)
}
fn1()
// 上面的函数里面是获取不了局部变量的, 因为它总是被当作*函数执行的,所以里面的变量指的都是全局变量
上面三种方法的区别:
函数声明 | 函数字面量 | Function()构造函数 |
---|---|---|
静态 | 静态 | 动态 |
预先解析加载 | 按顺序解析 | 按顺序解析 |
效率高 | 效率高 | 效率低 |
*作用域 |
- 第一种函数声明是最常用的。后两种都是把一个函数复制给变量fnMethodName,而这个函数是没有名字的,即匿名函数。
- 第一种解析器会
预先读取加载
,并使其在执行任何代码之前可以访问。则必须等到解析器执行到它所在的代码行才会真正被解释执行。
- Function()构造函数允许运行时Javascript代码
动态的创建和编译
。在这个方式上它类似全局函数eval()。前面2种是静态的。
- Function()构造函数每次执行时都会解析函数主体,并创建一个新的函数对象,所以当在一个循环或频繁执行的函数中调用Function()构造函数
效率是非常低的
。而函数字面量却不是每次遇到都会重新编译的。
- Function()构造函数创建一个函数时
并不遵循典型的作用域
,它一直把它当作是*函数
来执行。
函数的this指向
this总是指向调用者
。谁调用这个函数,函数里面的this就指向谁
下面的示例都以非严格模式下
讲解,因为非严格模式
下,顶层对象是window
, 严格模式下
顶层对象是undefined
。
ES6 默认是严格模式。
- 函数声明的情况
// add() // 正常执行
var name=10
function add(){
var name=20
console.log(this) //非严格模式指向window, 严格模式指向 undefined
console.log(this.name) //10
console.log(name) //20
}
add() // 等价于window.add()
- 函数表达式
// zjj() // 报错,必须放到函数下面执行
var name=10
var zjj=function(){
var name=30
console.log(this) //非严格模式指向window, 严格模式指向 undefined
console.log(this.name) //10
console.log(name) //30
}
zjj()
- 函数作为对象的属性去调用
var name='helixia'
var obj={
name:'张三',
age:80,
say:function(){
var name=40
console.log(this) //obj对象
console.log(this.name) //张三
}
}
obj.say()
- 构造函数中this的指向
var name=10
function Add(){
this.name=30
this.say=function(){
console.log(this)
console.log(this.name)
}
}
var obj1=new Add()
obj1.say() // 因为obj1调用的该方法,所以里面的this指向obj1
// 改一下
// var say = obj1.say
// say() // 因为全局调用,所以里面this指向window
- 原型对象中this的指向
var name = 10
function Add() {
console.log(this) // 指向obj
}
Add.prototype.name = 10
Add.prototype.say = function () {
console.log(this) // 指向obj
return function () {
console.log(this) // 执行window
}
}
var obj = new Add() //没传参数可以省略括号 new Add, 但最好不要
obj.say()()
总结,所以this总是指向调用者。
js中call()、apply()调用
JS函数调用的四种方法:方法调用模式,函数调用模式,构造器调用模式,apply,call调用模式
- 方法调用模式
先定义一个对象,然后在对象的属性中定义方法,通过myobject.property来执行方法,this即指当前的myobject对象。
var blogInfo={
id:123,
name:'张三',
showBlog:function(){
alert(this.id)
}
}
blogInfo.showBlog()
- 普通函数调用模式
定义一个函数,设置一个变量名保存函数,这时this指向到window对象。
var myfunc = function(a,b){
return a+b
}
alert(myfunc(3,4))
- 构造器调用模式
定义一个函数对象,在对象中定义属性,在其原型对象中定义方法。在使用prototype的方法时,必须实例化该对象才能调用其方法。
// 一般最好用来代表对象的用首字母大写, 小写也不会有问题
var Myfunc = function(a){
this.a = a
}
Myfunc.prototype.show = function(){
alert(this.a)
}
var newfunc = new Myfunc('123')
newfunc.show()
- call(),apply()调用模式
- 每个函数都默认有2个方法
call()
与apply()
-
call
与apply
最大作用就是可以改变函数里面的this
指向 -
call
与apply
区别是传参,call
传参是按顺序单个传,apply
传参传的是数组
var name = '张三'
let obj = {
name:'李四'
}
function test(x,y) {
consol.info(this.name)
console.info(x+y) // 3
}
// 正常调用是全局调用,test里面的this指向全局window, 注意,如果是es6里面,全局对象是空的,而不是window
test(1,2) // 张三
// 通过调用call,将test里面的this指向obj, 后面的是参数, 按顺序传参
// 内部大致实现是obj创建了一个临时方法指向test,然后立即调用那个临时方法,所以里面的this就指向了调用者obj, 调用完临时方法后,就将临时方法删除了
test.call(obj,1,2) // 李四
// 通过调用apply,将test里面的this指向obj, 后面的是参数,传数组
test.apply(obj,[1,2]) // 李四
普通对象与函数对象
JavaScript 中,对象分为普通对象
和函数对象
通过new Function创建的都是函数对象(其实就是一个函数,只是函数也是一个对象,所以叫函数对象),比如Function、Object、Array、Set、Map、Proxy、Symbol等,其他的对象都是普通对象
// 内置的函数对象
typeof Function // function
typeof Object // function
typeof Array // function
typeof Date // function
typeof Boolean // function
// Math 与 JSON比较特殊,是内置的普通对象
typeof Math // object
typeof JSON // object
// 所有通过Object创建出来的对象,是普通对象
// 所有通过Function创建出来的对象,是函数对象
let a = new Object()
typeof a
//"object"
function b(){}
typeof b
// 'function'
注意,Math和JSON是普通对象,而非函数对象。
Object 与 Fucntion区别
- 首先Object和Function都是构造函数,而所有的构造函数其实都是Function的实例对象. 因此Object是Function的实例对象
- Function.prototype是Object的实例对象
- 实例对象的原型(我们以__proto__来表示)会指向其构造函数的prototype属性, 因此
- Object.__proto__ === Function.prototype,
- Function.__proto__===Function.prototype,
- Function.prototype.__proto__ === Object.prototype
- 当我们访问一个属性值的时候, 它会沿着原型链向上查找, 直到找到或者到Object.prototype.__proto__(为null)截止.
var foo = {}
var F = function(){}
Object.prototype.a = 'value a'
Function.prototype.b = 'value b'
console.log(foo.a) // value a
console.log(foo.b) // undefined
console.log(F.a) // value a
console.log(F.b) // value b
图解构造器Function和Object的关系
typeof 与 instance 运算符
typeof 与 instance都可以用来检测对象类型。
typeof 只能用来大致区分对象,函数还是基本类型
instanceof 其实判断的是该对象是哪个构造函数
的实例
- typeof
typeof作用:用于判断一个表达式(对象或者原始值),返回一个字符串。
- 如果是基本类型: 可能返回string,number,boolean,undefined
- 如果是函数对象: 返回function
- 如果是普通对象: 返回object(null也返回object)
typeof Function // 'function'
typeof Object // 'function'
typeof Array // 'function'
// 所有的内置函数对象返回的都是function
typeof Math // 'object'
typeof JSON // 'object'
let a = new Object()
typeof a // 'object'
typeof null // 'object'
// Math与JSON都是内置的普通对象
// null比较特殊,是一个空对象
typeof '123' // 'string'
typeof 12 // 'number'
let b
typeof b // 'undefined'
- instanceof运算符
instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。返回布尔值
Function instanceof Function //true
Function instanceof Object //true
Object instanceof Function //true
Object instanceof Object //true
// Why ? 这个是怎么一回事呢?要从运算符instanceof说起。
// 假设instanceof运算符左边是L,右边是R
L instanceof R
// instanceof运算时,通过判断L的原型链上是否存在R.prototype
L.__proto__.__proto__ ..... === R.prototype ?
//如果存在返回true 否则返回false
Array instanceof Array // false
Array instanceof Function // true
Array instanceof Object // true
总结:instanceof检测左侧对象
的__proto__原型链上,是否存在右侧构造函数
的prototype原型。
原型与原型链
首先说一下什么是构造函数
构造函数:用来在创建对象时初始化对象。特点:构造函数名一般为大写字母开头;与new运算符一起使用来实例化对象。
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')
person1.sayName === person2.sayName // false
上面的例子中, person1和person2是构造函数Person的实例。 但是里面的属性与方法每创建一个实例时都是重新创建的,这样很浪费内存
那么如何才能让方法指向的是同一个呢? 就是利用原型
。可以把函数的原型属性(prototype)
当成一个公共对象,它用来存放公共的属性或方法
改写上面的代码:
function Person(name, age, job) {
this.name = name
this.age = age
this.job = job
}
Person.prototype.sayName = function() {
alert(this.name)
}
var person1 = new Person('Zaxlct', 28, 'Software Engineer')
var person2 = new Person('Mick', 23, 'Doctor')
person1.sayName === person2.sayName // true
// 此时,可以看到两个实例的方法指向的同一个
原型
理解原型对象
无论何时,只要创建了一个新函数
,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象(函数名.prototype)
。在默认情况下,所有原型对象都会自动获取一个constructor属性,指向自己的构造函数
每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由任何特定类型的所有实例共享的属性和方法。使用原型对象的好处是让所有对象实例共享它所包含的属性和方法。因此,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。
总结:构造函数的原型对象(如:Array.prototype)里面的属性和方法供所有该构造函数创建的实例所共享
- 每个
函数
都有自己的prototype属性,它里面默认有constructor与__proto__, constructor指向自己的构造函数 - 每个
实例对象
都有__proto__
Array.__proto__ === Function.prototype
let a = new Array()
a.__proto__ === Array.prototype
理解通过构造函数创建实例对象
构造函数创建对象本质
: 通过new创建对象时,会先创建一个空的实例对象
,产生虚拟的__proto__
属性,指向创建该实例的构造函数.prototype
。其次再调用该方法,将this指向该实例。 这样函数里面的this.xxx属性或方法就都挂到实例上面去了。而构造函数.prototype
就相当于放到了__proto__里面
当访问这个实例的属性或方法时,会先到自己的实例对象上面找,如果没有,就会去其原型链上面找。
__proto__一般是不支持直接访问的啊,可以通过getPrototypeOf
与 setPrototypeOf
来对原型操作
person1.__proto__ === Person.prototype // true
可以观察下: String/Number/Boolean/Function/Object/Array/Date等
如:
观察String.prototype
观察new String()
String.proto.proto // 指向Object
总结:每个对象的__proto__属性指向自身构造函数的prototype;
构造函数、原型对象、实例化对象三者的关系
原型链
原型链:每一个对象都有自己的原型对象(__proto__指向的对象),原型对象本身也是对象,原型对象也有自己的原型对象(__proto__),这样就形成了一个链式结构,叫做原型链。
那么到哪里结束呢?最底层的原型对象指向Object.prototype
, Object.prototype.__proto__ 指向null, 这样这条原型链就到终点了。
下面看一张简单的图
举例:
在上面这个例子中的p对象的原型链结构图如下:
p对象----->Person.prototype------->Object.prototype--------->null
- 对这个实例化对象而言,访问对象的属性,是首先在对象本身去找,如果没有,就会去他的原型对象中找,一直找到原型链的终点;
- 如果是修改对象的属性,如果这个实例化对象中有这个属性,就修改,没有这个属性就添加这个属性。
最后看一个完整的原型链图